From aecf8c2a624d45d1f881caa53c960708cc0f655a Mon Sep 17 00:00:00 2001 From: varkor Date: Thu, 21 Jun 2018 14:04:53 +0100 Subject: [PATCH 01/16] Add trim_start, trim_end, trim_start_matches and trim_end_matches --- src/liballoc/tests/lib.rs | 1 + src/liballoc/tests/str.rs | 58 +++++++++++++ src/libcore/str/mod.rs | 178 ++++++++++++++++++++++++++++++++++---- 3 files changed, 221 insertions(+), 16 deletions(-) diff --git a/src/liballoc/tests/lib.rs b/src/liballoc/tests/lib.rs index 618aff963f2..0dcbcf50fb4 100644 --- a/src/liballoc/tests/lib.rs +++ b/src/liballoc/tests/lib.rs @@ -25,6 +25,7 @@ #![feature(unboxed_closures)] #![feature(exact_chunks)] #![feature(repeat_generic_slice)] +#![feature(trim_direction)] extern crate alloc_system; extern crate core; diff --git a/src/liballoc/tests/str.rs b/src/liballoc/tests/str.rs index 6275c7bb112..a1940c894b8 100644 --- a/src/liballoc/tests/str.rs +++ b/src/liballoc/tests/str.rs @@ -726,6 +726,36 @@ fn test_is_char_boundary() { } } +#[test] +fn test_trim_start_matches() { + let v: &[char] = &[]; + assert_eq!(" *** foo *** ".trim_start_matches(v), " *** foo *** "); + let chars: &[char] = &['*', ' ']; + assert_eq!(" *** foo *** ".trim_start_matches(chars), "foo *** "); + assert_eq!(" *** *** ".trim_start_matches(chars), ""); + assert_eq!("foo *** ".trim_start_matches(chars), "foo *** "); + + assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11"); + let chars: &[char] = &['1', '2']; + assert_eq!("12foo1bar12".trim_start_matches(chars), "foo1bar12"); + assert_eq!("123foo1bar123".trim_start_matches(|c: char| c.is_numeric()), "foo1bar123"); +} + +#[test] +fn test_trim_end_matches() { + let v: &[char] = &[]; + assert_eq!(" *** foo *** ".trim_end_matches(v), " *** foo *** "); + let chars: &[char] = &['*', ' ']; + assert_eq!(" *** foo *** ".trim_end_matches(chars), " *** foo"); + assert_eq!(" *** *** ".trim_end_matches(chars), ""); + assert_eq!(" *** foo".trim_end_matches(chars), " *** foo"); + + assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar"); + let chars: &[char] = &['1', '2']; + assert_eq!("12foo1bar12".trim_end_matches(chars), "12foo1bar"); + assert_eq!("123foo1bar123".trim_end_matches(|c: char| c.is_numeric()), "123foo1bar"); +} + #[test] fn test_trim_left_matches() { let v: &[char] = &[]; @@ -771,6 +801,26 @@ fn test_trim_matches() { assert_eq!("123foo1bar123".trim_matches(|c: char| c.is_numeric()), "foo1bar"); } +#[test] +fn test_trim_start() { + assert_eq!("".trim_start(), ""); + assert_eq!("a".trim_start(), "a"); + assert_eq!(" ".trim_start(), ""); + assert_eq!(" blah".trim_start(), "blah"); + assert_eq!(" \u{3000} wut".trim_start(), "wut"); + assert_eq!("hey ".trim_start(), "hey "); +} + +#[test] +fn test_trim_end() { + assert_eq!("".trim_end(), ""); + assert_eq!("a".trim_end(), "a"); + assert_eq!(" ".trim_end(), ""); + assert_eq!("blah ".trim_end(), "blah"); + assert_eq!("wut \u{3000} ".trim_end(), "wut"); + assert_eq!(" hey".trim_end(), " hey"); +} + #[test] fn test_trim_left() { assert_eq!("".trim_left(), ""); @@ -1518,12 +1568,20 @@ fn trim_ws() { "a \t "); assert_eq!(" \t a \t ".trim_right_matches(|c: char| c.is_whitespace()), " \t a"); + assert_eq!(" \t a \t ".trim_start_matches(|c: char| c.is_whitespace()), + "a \t "); + assert_eq!(" \t a \t ".trim_end_matches(|c: char| c.is_whitespace()), + " \t a"); assert_eq!(" \t a \t ".trim_matches(|c: char| c.is_whitespace()), "a"); assert_eq!(" \t \t ".trim_left_matches(|c: char| c.is_whitespace()), ""); assert_eq!(" \t \t ".trim_right_matches(|c: char| c.is_whitespace()), ""); + assert_eq!(" \t \t ".trim_start_matches(|c: char| c.is_whitespace()), + ""); + assert_eq!(" \t \t ".trim_end_matches(|c: char| c.is_whitespace()), + ""); assert_eq!(" \t \t ".trim_matches(|c: char| c.is_whitespace()), ""); } diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index 86b8349fa3c..eda35519636 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -3587,6 +3587,78 @@ impl str { self.trim_matches(|c: char| c.is_whitespace()) } + /// Returns a string slice with leading whitespace removed. + /// + /// 'Whitespace' is defined according to the terms of the Unicode Derived + /// Core Property `White_Space`. + /// + /// # Text directionality + /// + /// A string is a sequence of bytes. `start` in this context means the first + /// position of that byte string; for a left-to-right language like English or + /// Russian, this will be left side; and for right-to-left languages like + /// like Arabic or Hebrew, this will be the right side. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let s = " Hello\tworld\t"; + /// + /// assert_eq!("Hello\tworld\t", s.trim_start()); + /// ``` + /// + /// Directionality: + /// + /// ``` + /// let s = " English"; + /// assert!(Some('E') == s.trim_start().chars().next()); + /// + /// let s = " עברית"; + /// assert!(Some('ע') == s.trim_start().chars().next()); + /// ``` + #[unstable(feature = "trim_direction", issue = "30459")] + pub fn trim_start(&self) -> &str { + self.trim_start_matches(|c: char| c.is_whitespace()) + } + + /// Returns a string slice with trailing whitespace removed. + /// + /// 'Whitespace' is defined according to the terms of the Unicode Derived + /// Core Property `White_Space`. + /// + /// # Text directionality + /// + /// A string is a sequence of bytes. `end` in this context means the last + /// position of that byte string; for a left-to-right language like English or + /// Russian, this will be right side; and for right-to-left languages like + /// like Arabic or Hebrew, this will be the left side. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let s = " Hello\tworld\t"; + /// + /// assert_eq!(" Hello\tworld", s.trim_end()); + /// ``` + /// + /// Directionality: + /// + /// ``` + /// let s = "English "; + /// assert!(Some('h') == s.trim_end().chars().rev().next()); + /// + /// let s = "עברית "; + /// assert!(Some('ת') == s.trim_end().chars().rev().next()); + /// ``` + #[unstable(feature = "trim_direction", issue = "30459")] + pub fn trim_end(&self) -> &str { + self.trim_end_matches(|c: char| c.is_whitespace()) + } + /// Returns a string slice with leading whitespace removed. /// /// 'Whitespace' is defined according to the terms of the Unicode Derived @@ -3619,8 +3691,9 @@ impl str { /// assert!(Some('ע') == s.trim_left().chars().next()); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_deprecated(reason = "superseded by `trim_start`", since = "1.33.0")] pub fn trim_left(&self) -> &str { - self.trim_left_matches(|c: char| c.is_whitespace()) + self.trim_start() } /// Returns a string slice with trailing whitespace removed. @@ -3655,8 +3728,9 @@ impl str { /// assert!(Some('ת') == s.trim_right().chars().rev().next()); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_deprecated(reason = "superseded by `trim_end`", since = "1.33.0")] pub fn trim_right(&self) -> &str { - self.trim_right_matches(|c: char| c.is_whitespace()) + self.trim_end() } /// Returns a string slice with all prefixes and suffixes that match a @@ -3725,14 +3799,14 @@ impl str { /// Basic usage: /// /// ``` - /// assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11"); - /// assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123"); + /// assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11"); + /// assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123"); /// /// let x: &[_] = &['1', '2']; - /// assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12"); + /// assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12"); /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn trim_left_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str { + #[unstable(feature = "trim_direction", issue = "30459")] + pub fn trim_start_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str { let mut i = self.len(); let mut matcher = pat.into_searcher(self); if let Some((a, _)) = matcher.next_reject() { @@ -3744,6 +3818,85 @@ impl str { } } + /// Returns a string slice with all suffixes that match a pattern + /// repeatedly removed. + /// + /// The pattern can be a `&str`, [`char`], or a closure that + /// determines if a character matches. + /// + /// [`char`]: primitive.char.html + /// + /// # Text directionality + /// + /// A string is a sequence of bytes. 'Right' in this context means the last + /// position of that byte string; for a language like Arabic or Hebrew + /// which are 'right to left' rather than 'left to right', this will be + /// the _left_ side, not the right. + /// + /// # Examples + /// + /// Simple patterns: + /// + /// ``` + /// assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar"); + /// assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar"); + /// + /// let x: &[_] = &['1', '2']; + /// assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar"); + /// ``` + /// + /// A more complex pattern, using a closure: + /// + /// ``` + /// assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo"); + /// ``` + #[unstable(feature = "trim_direction", issue = "30459")] + pub fn trim_end_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str + where P::Searcher: ReverseSearcher<'a> + { + let mut j = 0; + let mut matcher = pat.into_searcher(self); + if let Some((_, b)) = matcher.next_reject_back() { + j = b; + } + unsafe { + // Searcher is known to return valid indices + self.get_unchecked(0..j) + } + } + + /// Returns a string slice with all prefixes that match a pattern + /// repeatedly removed. + /// + /// The pattern can be a `&str`, [`char`], or a closure that determines if + /// a character matches. + /// + /// [`char`]: primitive.char.html + /// + /// # Text directionality + /// + /// A string is a sequence of bytes. 'Left' in this context means the first + /// position of that byte string; for a language like Arabic or Hebrew + /// which are 'right to left' rather than 'left to right', this will be + /// the _right_ side, not the left. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11"); + /// assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123"); + /// + /// let x: &[_] = &['1', '2']; + /// assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12"); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_deprecated(reason = "superseded by `trim_start_matches`", since = "1.33.0")] + pub fn trim_left_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str { + self.trim_start_matches(pat) + } + /// Returns a string slice with all suffixes that match a pattern /// repeatedly removed. /// @@ -3777,18 +3930,11 @@ impl str { /// assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo"); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_deprecated(reason = "superseded by `trim_end_matches`", since = "1.33.0")] pub fn trim_right_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str where P::Searcher: ReverseSearcher<'a> { - let mut j = 0; - let mut matcher = pat.into_searcher(self); - if let Some((_, b)) = matcher.next_reject_back() { - j = b; - } - unsafe { - // Searcher is known to return valid indices - self.get_unchecked(0..j) - } + self.trim_end_matches(pat) } /// Parses this string slice into another type. From 33067ad2011ce97e80a247c5a1a9bdb15fc4f376 Mon Sep 17 00:00:00 2001 From: varkor Date: Thu, 2 Aug 2018 22:00:13 +0100 Subject: [PATCH 02/16] Add #![feature(trim_direction)] to doc comments --- src/libcore/str/mod.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index eda35519636..00e296be4f2 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -3604,14 +3604,17 @@ impl str { /// Basic usage: /// /// ``` - /// let s = " Hello\tworld\t"; + /// #![feature(trim_direction)] /// + /// let s = " Hello\tworld\t"; /// assert_eq!("Hello\tworld\t", s.trim_start()); /// ``` /// /// Directionality: /// /// ``` + /// #![feature(trim_direction)] + /// /// let s = " English"; /// assert!(Some('E') == s.trim_start().chars().next()); /// @@ -3640,14 +3643,17 @@ impl str { /// Basic usage: /// /// ``` - /// let s = " Hello\tworld\t"; + /// #![feature(trim_direction)] /// + /// let s = " Hello\tworld\t"; /// assert_eq!(" Hello\tworld", s.trim_end()); /// ``` /// /// Directionality: /// /// ``` + /// #![feature(trim_direction)] + /// /// let s = "English "; /// assert!(Some('h') == s.trim_end().chars().rev().next()); /// @@ -3799,6 +3805,8 @@ impl str { /// Basic usage: /// /// ``` + /// #![feature(trim_direction)] + /// /// assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11"); /// assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123"); /// @@ -3838,6 +3846,8 @@ impl str { /// Simple patterns: /// /// ``` + /// #![feature(trim_direction)] + /// /// assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar"); /// assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar"); /// @@ -3848,6 +3858,8 @@ impl str { /// A more complex pattern, using a closure: /// /// ``` + /// #![feature(trim_direction)] + /// /// assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo"); /// ``` #[unstable(feature = "trim_direction", issue = "30459")] From 3e10ffcb81e8f887289904e5aaf272e872a9cb2c Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 21:51:40 +0100 Subject: [PATCH 03/16] Make features stable and clarify examples --- src/liballoc/tests/lib.rs | 1 - src/libcore/str/mod.rs | 30 ++++++++---------------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/liballoc/tests/lib.rs b/src/liballoc/tests/lib.rs index 0dcbcf50fb4..618aff963f2 100644 --- a/src/liballoc/tests/lib.rs +++ b/src/liballoc/tests/lib.rs @@ -25,7 +25,6 @@ #![feature(unboxed_closures)] #![feature(exact_chunks)] #![feature(repeat_generic_slice)] -#![feature(trim_direction)] extern crate alloc_system; extern crate core; diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index 00e296be4f2..0caab089a3b 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -3604,8 +3604,6 @@ impl str { /// Basic usage: /// /// ``` - /// #![feature(trim_direction)] - /// /// let s = " Hello\tworld\t"; /// assert_eq!("Hello\tworld\t", s.trim_start()); /// ``` @@ -3613,15 +3611,13 @@ impl str { /// Directionality: /// /// ``` - /// #![feature(trim_direction)] - /// - /// let s = " English"; + /// let s = " English "; /// assert!(Some('E') == s.trim_start().chars().next()); /// - /// let s = " עברית"; + /// let s = " עברית "; /// assert!(Some('ע') == s.trim_start().chars().next()); /// ``` - #[unstable(feature = "trim_direction", issue = "30459")] + #[stable(feature = "trim_direction", since = "1.30.0")] pub fn trim_start(&self) -> &str { self.trim_start_matches(|c: char| c.is_whitespace()) } @@ -3643,8 +3639,6 @@ impl str { /// Basic usage: /// /// ``` - /// #![feature(trim_direction)] - /// /// let s = " Hello\tworld\t"; /// assert_eq!(" Hello\tworld", s.trim_end()); /// ``` @@ -3652,15 +3646,13 @@ impl str { /// Directionality: /// /// ``` - /// #![feature(trim_direction)] - /// - /// let s = "English "; + /// let s = " English "; /// assert!(Some('h') == s.trim_end().chars().rev().next()); /// - /// let s = "עברית "; + /// let s = " עברית "; /// assert!(Some('ת') == s.trim_end().chars().rev().next()); /// ``` - #[unstable(feature = "trim_direction", issue = "30459")] + #[stable(feature = "trim_direction", since = "1.30.0")] pub fn trim_end(&self) -> &str { self.trim_end_matches(|c: char| c.is_whitespace()) } @@ -3805,15 +3797,13 @@ impl str { /// Basic usage: /// /// ``` - /// #![feature(trim_direction)] - /// /// assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11"); /// assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123"); /// /// let x: &[_] = &['1', '2']; /// assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12"); /// ``` - #[unstable(feature = "trim_direction", issue = "30459")] + #[stable(feature = "trim_direction", since = "1.30.0")] pub fn trim_start_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str { let mut i = self.len(); let mut matcher = pat.into_searcher(self); @@ -3846,8 +3836,6 @@ impl str { /// Simple patterns: /// /// ``` - /// #![feature(trim_direction)] - /// /// assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar"); /// assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar"); /// @@ -3858,11 +3846,9 @@ impl str { /// A more complex pattern, using a closure: /// /// ``` - /// #![feature(trim_direction)] - /// /// assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo"); /// ``` - #[unstable(feature = "trim_direction", issue = "30459")] + #[stable(feature = "trim_direction", since = "1.30.0")] pub fn trim_end_matches<'a, P: Pattern<'a>>(&'a self, pat: P) -> &'a str where P::Searcher: ReverseSearcher<'a> { From ff7967056916f68f2df5214e47e9e72414b44fc3 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 19 Aug 2018 22:36:59 +0100 Subject: [PATCH 04/16] Remove old tests --- src/liballoc/tests/str.rs | 50 --------------------------------------- 1 file changed, 50 deletions(-) diff --git a/src/liballoc/tests/str.rs b/src/liballoc/tests/str.rs index a1940c894b8..a5fa7f0c4d9 100644 --- a/src/liballoc/tests/str.rs +++ b/src/liballoc/tests/str.rs @@ -756,36 +756,6 @@ fn test_trim_end_matches() { assert_eq!("123foo1bar123".trim_end_matches(|c: char| c.is_numeric()), "123foo1bar"); } -#[test] -fn test_trim_left_matches() { - let v: &[char] = &[]; - assert_eq!(" *** foo *** ".trim_left_matches(v), " *** foo *** "); - let chars: &[char] = &['*', ' ']; - assert_eq!(" *** foo *** ".trim_left_matches(chars), "foo *** "); - assert_eq!(" *** *** ".trim_left_matches(chars), ""); - assert_eq!("foo *** ".trim_left_matches(chars), "foo *** "); - - assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11"); - let chars: &[char] = &['1', '2']; - assert_eq!("12foo1bar12".trim_left_matches(chars), "foo1bar12"); - assert_eq!("123foo1bar123".trim_left_matches(|c: char| c.is_numeric()), "foo1bar123"); -} - -#[test] -fn test_trim_right_matches() { - let v: &[char] = &[]; - assert_eq!(" *** foo *** ".trim_right_matches(v), " *** foo *** "); - let chars: &[char] = &['*', ' ']; - assert_eq!(" *** foo *** ".trim_right_matches(chars), " *** foo"); - assert_eq!(" *** *** ".trim_right_matches(chars), ""); - assert_eq!(" *** foo".trim_right_matches(chars), " *** foo"); - - assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar"); - let chars: &[char] = &['1', '2']; - assert_eq!("12foo1bar12".trim_right_matches(chars), "12foo1bar"); - assert_eq!("123foo1bar123".trim_right_matches(|c: char| c.is_numeric()), "123foo1bar"); -} - #[test] fn test_trim_matches() { let v: &[char] = &[]; @@ -821,26 +791,6 @@ fn test_trim_end() { assert_eq!(" hey".trim_end(), " hey"); } -#[test] -fn test_trim_left() { - assert_eq!("".trim_left(), ""); - assert_eq!("a".trim_left(), "a"); - assert_eq!(" ".trim_left(), ""); - assert_eq!(" blah".trim_left(), "blah"); - assert_eq!(" \u{3000} wut".trim_left(), "wut"); - assert_eq!("hey ".trim_left(), "hey "); -} - -#[test] -fn test_trim_right() { - assert_eq!("".trim_right(), ""); - assert_eq!("a".trim_right(), "a"); - assert_eq!(" ".trim_right(), ""); - assert_eq!("blah ".trim_right(), "blah"); - assert_eq!("wut \u{3000} ".trim_right(), "wut"); - assert_eq!(" hey".trim_right(), " hey"); -} - #[test] fn test_trim() { assert_eq!("".trim(), ""); From 347c6b76a001a73c994b0abd5f1f7205cd922ac2 Mon Sep 17 00:00:00 2001 From: Charlie Andrews Date: Fri, 31 Aug 2018 14:42:01 -0500 Subject: [PATCH 05/16] Make json test output formatter represent "test_count" as num --- src/libtest/formatters/json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtest/formatters/json.rs b/src/libtest/formatters/json.rs index f7e0788a884..fc5ce5fc291 100644 --- a/src/libtest/formatters/json.rs +++ b/src/libtest/formatters/json.rs @@ -50,7 +50,7 @@ impl JsonFormatter { impl OutputFormatter for JsonFormatter { fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { self.write_message(&*format!( - r#"{{ "type": "suite", "event": "started", "test_count": "{}" }}"#, + r#"{{ "type": "suite", "event": "started", "test_count": {} }}"#, test_count )) } From b9de64a0e68748899d6eb1902d0ac312faf20d71 Mon Sep 17 00:00:00 2001 From: Charlie Andrews Date: Fri, 31 Aug 2018 14:57:19 -0500 Subject: [PATCH 06/16] Make json test output formatter represent "filtered_out" as num --- src/libtest/formatters/json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtest/formatters/json.rs b/src/libtest/formatters/json.rs index fc5ce5fc291..3c803ad8233 100644 --- a/src/libtest/formatters/json.rs +++ b/src/libtest/formatters/json.rs @@ -136,7 +136,7 @@ impl OutputFormatter for JsonFormatter { \"allowed_fail\": {}, \ \"ignored\": {}, \ \"measured\": {}, \ - \"filtered_out\": \"{}\" }}", + \"filtered_out\": {} }}", if state.failed == 0 { "ok" } else { "failed" }, state.passed, state.failed + state.allowed_fail, From 1c3dc9aca2ad06623af7667701c52b8ff3677edc Mon Sep 17 00:00:00 2001 From: Charlie Andrews Date: Fri, 31 Aug 2018 16:30:05 -0500 Subject: [PATCH 07/16] Fix tests for json formatting --- src/test/run-make-fulldeps/libtest-json/output.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/run-make-fulldeps/libtest-json/output.json b/src/test/run-make-fulldeps/libtest-json/output.json index 235f8cd7c72..d8169ece89b 100644 --- a/src/test/run-make-fulldeps/libtest-json/output.json +++ b/src/test/run-make-fulldeps/libtest-json/output.json @@ -1,4 +1,4 @@ -{ "type": "suite", "event": "started", "test_count": "4" } +{ "type": "suite", "event": "started", "test_count": 4 } { "type": "test", "event": "started", "name": "a" } { "type": "test", "name": "a", "event": "ok" } { "type": "test", "event": "started", "name": "b" } @@ -7,4 +7,4 @@ { "type": "test", "name": "c", "event": "ok" } { "type": "test", "event": "started", "name": "d" } { "type": "test", "name": "d", "event": "ignored" } -{ "type": "suite", "event": "failed", "passed": 2, "failed": 1, "allowed_fail": 0, "ignored": 1, "measured": 0, "filtered_out": "0" } +{ "type": "suite", "event": "failed", "passed": 2, "failed": 1, "allowed_fail": 0, "ignored": 1, "measured": 0, "filtered_out": 0 } From fc497d00263be103ca9d009206963d4a5763392d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 Aug 2018 22:41:17 -0700 Subject: [PATCH 08/16] rustc: Prepare the `atomics` feature for wasm This commit adds a few changes for atomic instructions on the `wasm32-unknown-unknown` target. Atomic instructions are not yet stable in WebAssembly itself but there are multiple implementations and LLVM has support for the proposed instruction set, so let's work on exposing it! Here there are a few inclusions: * The `atomics` feature was whitelisted for LLVM, allowing code in Rust to enable/disable/gate on this. * The `singlethread` option is turned off for wasm when the `atomics` feature is enabled. This means that by default wasm won't be lowering with atomics, but when atomics are enabled globally we'll turn off single-threaded mode to actually codegen atomics. This probably isn't what we'll want in the long term but for now it should work. * Finally the maximum atomic width is increased to 64 to reflect the current wasm spec. --- src/librustc_codegen_llvm/back/write.rs | 17 +++++++++++++---- src/librustc_codegen_llvm/llvm_util.rs | 1 + .../spec/wasm32_unknown_unknown.rs | 3 +-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs index 2373428d68c..c88a1809cab 100644 --- a/src/librustc_codegen_llvm/back/write.rs +++ b/src/librustc_codegen_llvm/back/write.rs @@ -171,13 +171,22 @@ pub fn target_machine_factory(sess: &Session, find_features: bool) None => llvm::CodeModel::None, }; - let singlethread = sess.target.target.options.singlethread; + let features = attributes::llvm_target_features(sess).collect::>(); + let mut singlethread = sess.target.target.options.singlethread; + + // On the wasm target once the `atomics` feature is enabled that means that + // we're no longer single-threaded, or otherwise we don't want LLVM to + // lower atomic operations to single-threaded operations. + if singlethread && + sess.target.target.llvm_target.contains("wasm32") && + features.iter().any(|s| *s == "+atomics") + { + singlethread = false; + } let triple = SmallCStr::new(&sess.target.target.llvm_target); let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); - let features = attributes::llvm_target_features(sess) - .collect::>() - .join(","); + let features = features.join(","); let features = CString::new(features).unwrap(); let is_pie_binary = !find_features && is_pie_binary(sess); let trap_unreachable = sess.target.target.options.trap_unreachable; diff --git a/src/librustc_codegen_llvm/llvm_util.rs b/src/librustc_codegen_llvm/llvm_util.rs index 9fcc33d82cf..4f172c9f292 100644 --- a/src/librustc_codegen_llvm/llvm_util.rs +++ b/src/librustc_codegen_llvm/llvm_util.rs @@ -173,6 +173,7 @@ const MIPS_WHITELIST: &[(&str, Option<&str>)] = &[ const WASM_WHITELIST: &[(&str, Option<&str>)] = &[ ("simd128", Some("wasm_target_feature")), + ("atomics", Some("wasm_target_feature")), ]; /// When rustdoc is running, provide a list of all known features so that all their respective diff --git a/src/librustc_target/spec/wasm32_unknown_unknown.rs b/src/librustc_target/spec/wasm32_unknown_unknown.rs index 6c368e09003..c0455ceb839 100644 --- a/src/librustc_target/spec/wasm32_unknown_unknown.rs +++ b/src/librustc_target/spec/wasm32_unknown_unknown.rs @@ -36,8 +36,7 @@ pub fn target() -> Result { dll_suffix: ".wasm".to_string(), linker_is_gnu: false, - // A bit of a lie, but "eh" - max_atomic_width: Some(32), + max_atomic_width: Some(64), // Unwinding doesn't work right now, so the whole target unconditionally // defaults to panic=abort. Note that this is guaranteed to change in From 289da84381cb227be7c7189c2ba704ac4f503664 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Tue, 4 Sep 2018 09:55:41 -0600 Subject: [PATCH 09/16] Restore lldb build commit 6c10142251 ("Update LLVM submodule") disabled the lldb build. This patch updates the lldb and clang submodules to once again build against the LLVM that is included in the Rust tree, and reverts the .travis.yml changes from that patch. --- .gitmodules | 4 ++-- .travis.yml | 6 +++--- src/tools/clang | 2 +- src/tools/lldb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1631daac76c..5753e9ed45e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -59,8 +59,8 @@ [submodule "src/tools/lldb"] path = src/tools/lldb url = https://github.com/rust-lang-nursery/lldb/ - branch = rust-release-70 + branch = rust-release-80-v1 [submodule "src/tools/clang"] path = src/tools/clang url = https://github.com/rust-lang-nursery/clang/ - branch = release_70 + branch = rust-release-80-v1 diff --git a/.travis.yml b/.travis.yml index 4a7c8f4b7cf..b1701e4a654 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: - env: > RUST_CHECK_TARGET=dist - RUST_CONFIGURE_ARGS="--enable-extended --enable-profiler" + RUST_CONFIGURE_ARGS="--enable-extended --enable-profiler --enable-lldb" SRC=. DEPLOY_ALT=1 RUSTC_RETRY_LINKER_ON_SEGFAULT=1 @@ -87,7 +87,7 @@ matrix: # OSX 10.7 and `xcode7` is the latest Xcode able to compile LLVM for 10.7. - env: > RUST_CHECK_TARGET=dist - RUST_CONFIGURE_ARGS="--build=i686-apple-darwin --enable-full-tools --enable-profiler" + RUST_CONFIGURE_ARGS="--build=i686-apple-darwin --enable-full-tools --enable-profiler --enable-lldb" SRC=. DEPLOY=1 RUSTC_RETRY_LINKER_ON_SEGFAULT=1 @@ -101,7 +101,7 @@ matrix: - env: > RUST_CHECK_TARGET=dist - RUST_CONFIGURE_ARGS="--target=aarch64-apple-ios,armv7-apple-ios,armv7s-apple-ios,i386-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler" + RUST_CONFIGURE_ARGS="--target=aarch64-apple-ios,armv7-apple-ios,armv7s-apple-ios,i386-apple-ios,x86_64-apple-ios --enable-full-tools --enable-sanitizers --enable-profiler --enable-lldb" SRC=. DEPLOY=1 RUSTC_RETRY_LINKER_ON_SEGFAULT=1 diff --git a/src/tools/clang b/src/tools/clang index 2a284a70e26..d0fc1788123 160000 --- a/src/tools/clang +++ b/src/tools/clang @@ -1 +1 @@ -Subproject commit 2a284a70e26997273c296afe06586ffdf3a142fd +Subproject commit d0fc1788123de9844c8088b977cd142021cea1f2 diff --git a/src/tools/lldb b/src/tools/lldb index 3dbe998969d..3be47d264e9 160000 --- a/src/tools/lldb +++ b/src/tools/lldb @@ -1 +1 @@ -Subproject commit 3dbe998969d457c5cef245f61b48bdaed0f5c059 +Subproject commit 3be47d264e91844544d56078a18182b8a7418086 From 9b27de41d4e00cb6c23df270572472fd4c6f47f8 Mon Sep 17 00:00:00 2001 From: John Renner Date: Fri, 20 Jul 2018 18:04:02 -0700 Subject: [PATCH 10/16] Introduce Custom Test Frameworks --- src/Cargo.lock | 1 + src/librustc_lint/builtin.rs | 69 ++- src/librustc_lint/lib.rs | 2 +- src/librustc_resolve/macros.rs | 4 + src/libsyntax/ast.rs | 2 +- src/libsyntax/config.rs | 8 +- src/libsyntax/ext/expand.rs | 48 +- src/libsyntax/feature_gate.rs | 12 + src/libsyntax/test.rs | 582 ++++-------------- src/libsyntax_ext/Cargo.toml | 1 + src/libsyntax_ext/lib.rs | 10 +- src/libsyntax_ext/test.rs | 328 ++++++++++ src/libtest/lib.rs | 3 +- src/libtest/stats.rs | 3 +- .../incremental/issue-49595/issue_49595.rs | 5 +- src/test/ui/cfg-non-opt-expr.rs | 3 +- src/test/ui/cfg-non-opt-expr.stderr | 12 +- src/test/ui/custom-test-frameworks-simple.rs | 32 + .../auxiliary/dynamic_runner.rs | 45 ++ .../auxiliary/example_runner.rs | 20 + src/test/ui/custom_test_frameworks/dynamic.rs | 45 ++ src/test/ui/custom_test_frameworks/full.rs | 38 ++ .../ui/custom_test_frameworks/mismatch.rs | 19 + .../ui/custom_test_frameworks/mismatch.stderr | 11 + .../ui/feature-gate-custom_test_frameworks.rs | 13 + ...feature-gate-custom_test_frameworks.stderr | 11 + src/test/ui/inaccessible-test-modules.stderr | 2 +- src/test/ui/issues/issue-11692-2.rs | 2 +- src/test/ui/issues/issue-11692-2.stderr | 2 +- src/test/ui/issues/issue-12997-2.stderr | 2 +- src/test/ui/lint/test-inner-fn.rs | 6 +- src/test/ui/lint/test-inner-fn.stderr | 10 +- .../termination-trait-test-wrong-type.stderr | 2 +- src/test/ui/test-on-macro.rs | 23 + src/test/ui/test-on-macro.stderr | 6 + 35 files changed, 806 insertions(+), 576 deletions(-) create mode 100644 src/libsyntax_ext/test.rs create mode 100644 src/test/ui/custom-test-frameworks-simple.rs create mode 100644 src/test/ui/custom_test_frameworks/auxiliary/dynamic_runner.rs create mode 100644 src/test/ui/custom_test_frameworks/auxiliary/example_runner.rs create mode 100644 src/test/ui/custom_test_frameworks/dynamic.rs create mode 100644 src/test/ui/custom_test_frameworks/full.rs create mode 100644 src/test/ui/custom_test_frameworks/mismatch.rs create mode 100644 src/test/ui/custom_test_frameworks/mismatch.stderr create mode 100644 src/test/ui/feature-gate-custom_test_frameworks.rs create mode 100644 src/test/ui/feature-gate-custom_test_frameworks.stderr create mode 100644 src/test/ui/test-on-macro.rs create mode 100644 src/test/ui/test-on-macro.stderr diff --git a/src/Cargo.lock b/src/Cargo.lock index 951745bf5bb..a4f9082c284 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -2739,6 +2739,7 @@ name = "syntax_ext" version = "0.0.0" dependencies = [ "fmt_macros 0.0.0", + "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "proc_macro 0.0.0", "rustc_data_structures 0.0.0", "rustc_errors 0.0.0", diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 822590450e2..40662bafa1b 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -1835,43 +1835,56 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { } declare_lint! { - UNNAMEABLE_TEST_FUNCTIONS, + UNNAMEABLE_TEST_ITEMS, Warn, - "detects an function that cannot be named being marked as #[test]" + "detects an item that cannot be named being marked as #[test_case]", + report_in_external_macro: true } -pub struct UnnameableTestFunctions; +pub struct UnnameableTestItems { + boundary: ast::NodeId, // NodeId of the item under which things are not nameable + items_nameable: bool, +} -impl LintPass for UnnameableTestFunctions { - fn get_lints(&self) -> LintArray { - lint_array!(UNNAMEABLE_TEST_FUNCTIONS) +impl UnnameableTestItems { + pub fn new() -> Self { + Self { + boundary: ast::DUMMY_NODE_ID, + items_nameable: true + } } } -impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestFunctions { +impl LintPass for UnnameableTestItems { + fn get_lints(&self) -> LintArray { + lint_array!(UNNAMEABLE_TEST_ITEMS) + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems { fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { - match it.node { - hir::ItemKind::Fn(..) => { - for attr in &it.attrs { - if attr.name() == "test" { - let parent = cx.tcx.hir.get_parent(it.id); - match cx.tcx.hir.find(parent) { - Some(Node::Item(hir::Item {node: hir::ItemKind::Mod(_), ..})) | - None => {} - _ => { - cx.struct_span_lint( - UNNAMEABLE_TEST_FUNCTIONS, - attr.span, - "cannot test inner function", - ).emit(); - } - } - break; - } - } + if self.items_nameable { + if let hir::ItemKind::Mod(..) = it.node {} + else { + self.items_nameable = false; + self.boundary = it.id; } - _ => return, - }; + return; + } + + if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") { + cx.struct_span_lint( + UNNAMEABLE_TEST_ITEMS, + attr.span, + "cannot test inner items", + ).emit(); + } + } + + fn check_item_post(&mut self, _cx: &LateContext, it: &hir::Item) { + if !self.items_nameable && self.boundary == it.id { + self.items_nameable = true; + } } } diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index 46c5b0092a2..2c32cbdd00f 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -149,7 +149,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) { MutableTransmutes: MutableTransmutes, UnionsWithDropFields: UnionsWithDropFields, UnreachablePub: UnreachablePub, - UnnameableTestFunctions: UnnameableTestFunctions, + UnnameableTestItems: UnnameableTestItems::new(), TypeAliasBounds: TypeAliasBounds, UnusedBrokenConst: UnusedBrokenConst, TrivialConstraints: TrivialConstraints, diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index 879b4ea3fe5..0d3e615b446 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -462,6 +462,10 @@ impl<'a, 'cl> Resolver<'a, 'cl> { return def; } + if kind == MacroKind::Attr && *&path[0].as_str() == "test" { + return Ok(self.macro_prelude.get(&path[0].name).unwrap().def()) + } + let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false); let result = if let Some((legacy_binding, _)) = legacy_resolution { Ok(legacy_binding.def()) diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 72f1791ef7c..9851749be37 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -1587,7 +1587,7 @@ impl TyKind { if let TyKind::ImplicitSelf = *self { true } else { false } } - crate fn is_unit(&self) -> bool { + pub fn is_unit(&self) -> bool { if let TyKind::Tup(ref tys) = *self { tys.is_empty() } else { false } } } diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs index 0e52434ec01..900d830b4c0 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -119,7 +119,7 @@ impl<'a> StripUnconfigured<'a> { pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool { attrs.iter().all(|attr| { // When not compiling with --test we should not compile the #[test] functions - if !self.should_test && is_test_or_bench(attr) { + if !self.should_test && is_test(attr) { return false; } @@ -249,7 +249,7 @@ impl<'a> StripUnconfigured<'a> { // // NB: This is intentionally not part of the fold_expr() function // in order for fold_opt_expr() to be able to avoid this check - if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test_or_bench(a)) { + if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) { let msg = "removing an expression is not supported in this position"; self.sess.span_diagnostic.span_err(attr.span, msg); } @@ -353,6 +353,6 @@ fn is_cfg(attr: &ast::Attribute) -> bool { attr.check_name("cfg") } -pub fn is_test_or_bench(attr: &ast::Attribute) -> bool { - attr.check_name("test") || attr.check_name("bench") +pub fn is_test(att: &ast::Attribute) -> bool { + att.check_name("test_case") } diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 6e38f820586..956e086b01b 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -12,10 +12,9 @@ use ast::{self, Block, Ident, NodeId, PatKind, Path}; use ast::{MacStmtStyle, StmtKind, ItemKind}; use attr::{self, HasAttrs}; use source_map::{ExpnInfo, MacroBang, MacroAttribute, dummy_spanned, respan}; -use config::{is_test_or_bench, StripUnconfigured}; +use config::StripUnconfigured; use errors::{Applicability, FatalError}; use ext::base::*; -use ext::build::AstBuilder; use ext::derive::{add_derived_markers, collect_derives}; use ext::hygiene::{self, Mark, SyntaxContext}; use ext::placeholders::{placeholder, PlaceholderExpander}; @@ -37,7 +36,6 @@ use visit::{self, Visitor}; use rustc_data_structures::fx::FxHashMap; use std::fs::File; use std::io::Read; -use std::iter::FromIterator; use std::{iter, mem}; use std::rc::Rc; use std::path::PathBuf; @@ -1366,51 +1364,25 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { self.cx.current_expansion.directory_ownership = orig_directory_ownership; result } - // Ensure that test functions are accessible from the test harness. + + // Ensure that test items can be exported by the harness generator. // #[test] fn foo() {} // becomes: // #[test] pub fn foo_gensym(){} - // #[allow(unused)] - // use foo_gensym as foo; - ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => { - if self.tests_nameable && item.attrs.iter().any(|attr| is_test_or_bench(attr)) { - let orig_ident = item.ident; - let orig_vis = item.vis.clone(); - + ast::ItemKind::Const(..) + | ast::ItemKind::Static(..) + | ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => { + if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") { // Publicize the item under gensymed name to avoid pollution + // This means #[test_case] items can't be referenced by user code item = item.map(|mut item| { item.vis = respan(item.vis.span, ast::VisibilityKind::Public); item.ident = item.ident.gensym(); item }); - - // Use the gensymed name under the item's original visibility - let mut use_item = self.cx.item_use_simple_( - item.ident.span, - orig_vis, - Some(orig_ident), - self.cx.path(item.ident.span, - vec![keywords::SelfValue.ident(), item.ident])); - - // #[allow(unused)] because the test function probably isn't being referenced - use_item = use_item.map(|mut ui| { - ui.attrs.push( - self.cx.attribute(DUMMY_SP, attr::mk_list_item(DUMMY_SP, - Ident::from_str("allow"), vec![ - attr::mk_nested_word_item(Ident::from_str("unused")) - ] - )) - ); - - ui - }); - - OneVector::from_iter( - self.fold_unnameable(item).into_iter() - .chain(self.fold_unnameable(use_item))) - } else { - self.fold_unnameable(item) } + + self.fold_unnameable(item) } _ => self.fold_unnameable(item), } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 14781dd8e24..35cb5aa5475 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -515,6 +515,10 @@ declare_features! ( // unsized rvalues at arguments and parameters (active, unsized_locals, "1.30.0", Some(48055), None), + + // #![test_runner] + // #[test_case] + (active, custom_test_frameworks, "1.30.0", Some(50297), None), ); declare_features! ( @@ -775,6 +779,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("no_link", Normal, Ungated), ("derive", Normal, Ungated), ("should_panic", Normal, Ungated), + ("test_case", Normal, Gated(Stability::Unstable, + "custom_test_frameworks", + "Custom test frameworks are experimental", + cfg_fn!(custom_test_frameworks))), ("ignore", Normal, Ungated), ("no_implicit_prelude", Normal, Ungated), ("reexport_test_harness_main", Normal, Ungated), @@ -1156,6 +1164,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("no_builtins", CrateLevel, Ungated), ("recursion_limit", CrateLevel, Ungated), ("type_length_limit", CrateLevel, Ungated), + ("test_runner", CrateLevel, Gated(Stability::Unstable, + "custom_test_frameworks", + "Custom Test Frameworks is an unstable feature", + cfg_fn!(custom_test_frameworks))), ]; // cfg(...)'s that are feature gated diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 49ab0c2256e..91c63227d30 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -22,7 +22,7 @@ use std::vec; use attr::{self, HasAttrs}; use syntax_pos::{self, DUMMY_SP, NO_EXPANSION, Span, SourceFile, BytePos}; -use source_map::{self, SourceMap, ExpnInfo, MacroAttribute, dummy_spanned}; +use source_map::{self, SourceMap, ExpnInfo, MacroAttribute, dummy_spanned, respan}; use errors; use config; use entry::{self, EntryPointType}; @@ -43,29 +43,21 @@ use symbol::{self, Symbol, keywords}; use ThinVec; use rustc_data_structures::small_vec::ExpectOne; -enum ShouldPanic { - No, - Yes(Option), -} - struct Test { span: Span, - path: Vec , - bench: bool, - ignore: bool, - should_panic: ShouldPanic, - allow_fail: bool, + path: Vec, } struct TestCtxt<'a> { span_diagnostic: &'a errors::Handler, path: Vec, ext_cx: ExtCtxt<'a>, - testfns: Vec, + test_cases: Vec, reexport_test_harness_main: Option, is_libtest: bool, ctxt: SyntaxContext, features: &'a Features, + test_runner: Option, // top-level re-export submodule, filled out after folding is finished toplevel_reexport: Option, @@ -87,9 +79,13 @@ pub fn modify_for_testing(sess: &ParseSess, attr::first_attr_value_str_by_name(&krate.attrs, "reexport_test_harness_main"); + // Do this here so that the test_runner crate attribute gets marked as used + // even in non-test builds + let test_runner = get_test_runner(span_diagnostic, &krate); + if should_test { generate_test_harness(sess, resolver, reexport_test_harness_main, - krate, span_diagnostic, features) + krate, span_diagnostic, features, test_runner) } else { krate } @@ -107,13 +103,13 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate { let mut folded = fold::noop_fold_crate(c, self); - // Add a special __test module to the crate that will contain code - // generated for the test harness - let (mod_, reexport) = mk_test_module(&mut self.cx); - if let Some(re) = reexport { - folded.module.items.push(re) - } - folded.module.items.push(mod_); + // Create a main function to run our tests + let test_main = { + let unresolved = mk_main(&mut self.cx); + self.cx.ext_cx.monotonic_expander().fold_item(unresolved).pop().unwrap() + }; + + folded.module.items.push(test_main); folded } @@ -124,41 +120,18 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { } debug!("current path: {}", path_name_i(&self.cx.path)); - if is_test_fn(&self.cx, &i) || is_bench_fn(&self.cx, &i) { - match i.node { - ast::ItemKind::Fn(_, header, _, _) => { - if header.unsafety == ast::Unsafety::Unsafe { - let diag = self.cx.span_diagnostic; - diag.span_fatal( - i.span, - "unsafe functions cannot be used for tests" - ).raise(); - } - if header.asyncness.is_async() { - let diag = self.cx.span_diagnostic; - diag.span_fatal( - i.span, - "async functions cannot be used for tests" - ).raise(); - } - } - _ => {}, - } + let mut item = i.into_inner(); + if is_test_case(&item) { + debug!("this is a test item"); - debug!("this is a test function"); let test = Test { - span: i.span, + span: item.span, path: self.cx.path.clone(), - bench: is_bench_fn(&self.cx, &i), - ignore: is_ignored(&i), - should_panic: should_panic(&i, &self.cx), - allow_fail: is_allowed_fail(&i), }; - self.cx.testfns.push(test); - self.tests.push(i.ident); + self.cx.test_cases.push(test); + self.tests.push(item.ident); } - let mut item = i.into_inner(); // We don't want to recurse into anything other than mods, since // mods or tests inside of functions will break things if let ast::ItemKind::Mod(module) = item.node { @@ -190,6 +163,8 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac } } +/// A folder used to remove any entry points (like fn main) because the harness +/// generator will provide its own struct EntryPointCleaner { // Current depth in the ast depth: usize, @@ -242,6 +217,10 @@ impl fold::Folder for EntryPointCleaner { fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac } } +/// Creates an item (specifically a module) that "pub use"s the tests passed in. +/// Each tested submodule will contain a similar reexport module that we will export +/// under the name of the original module. That is, `submod::__test_reexports` is +/// reexported like so `pub use submod::__test_reexports as submod`. fn mk_reexport_mod(cx: &mut TestCtxt, parent: ast::NodeId, tests: Vec, @@ -279,12 +258,14 @@ fn mk_reexport_mod(cx: &mut TestCtxt, (it, sym) } +/// Crawl over the crate, inserting test reexports and the test main function fn generate_test_harness(sess: &ParseSess, resolver: &mut dyn Resolver, reexport_test_harness_main: Option, krate: ast::Crate, sd: &errors::Handler, - features: &Features) -> ast::Crate { + features: &Features, + test_runner: Option) -> ast::Crate { // Remove the entry points let mut cleaner = EntryPointCleaner { depth: 0 }; let krate = cleaner.fold_crate(krate); @@ -298,19 +279,20 @@ fn generate_test_harness(sess: &ParseSess, span_diagnostic: sd, ext_cx: ExtCtxt::new(sess, econfig, resolver), path: Vec::new(), - testfns: Vec::new(), + test_cases: Vec::new(), reexport_test_harness_main, // NB: doesn't consider the value of `--crate-name` passed on the command line. is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false), toplevel_reexport: None, ctxt: SyntaxContext::empty().apply_mark(mark), features, + test_runner }; mark.set_expn_info(ExpnInfo { call_site: DUMMY_SP, def_site: None, - format: MacroAttribute(Symbol::intern("test")), + format: MacroAttribute(Symbol::intern("test_case")), allow_internal_unstable: true, allow_internal_unsafe: false, local_inner_macros: false, @@ -344,216 +326,64 @@ enum BadTestSignature { ShouldPanicOnlyWithNoArgs, } -fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool { - let has_test_attr = attr::contains_name(&i.attrs, "test"); - - fn has_test_signature(_cx: &TestCtxt, i: &ast::Item) -> HasTestSignature { - let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic"); - match i.node { - ast::ItemKind::Fn(ref decl, _, ref generics, _) => { - // If the termination trait is active, the compiler will check that the output - // type implements the `Termination` trait as `libtest` enforces that. - let has_output = match decl.output { - ast::FunctionRetTy::Default(..) => false, - ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false, - _ => true - }; - - if !decl.inputs.is_empty() { - return No(BadTestSignature::NoArgumentsAllowed); - } - - match (has_output, has_should_panic_attr) { - (true, true) => No(BadTestSignature::ShouldPanicOnlyWithNoArgs), - (true, false) => if !generics.params.is_empty() { - No(BadTestSignature::WrongTypeSignature) - } else { - Yes - }, - (false, _) => Yes - } - } - _ => No(BadTestSignature::NotEvenAFunction), - } - } - - let has_test_signature = if has_test_attr { - let diag = cx.span_diagnostic; - match has_test_signature(cx, i) { - Yes => true, - No(cause) => { - match cause { - BadTestSignature::NotEvenAFunction => - diag.span_err(i.span, "only functions may be used as tests"), - BadTestSignature::WrongTypeSignature => - diag.span_err(i.span, - "functions used as tests must have signature fn() -> ()"), - BadTestSignature::NoArgumentsAllowed => - diag.span_err(i.span, "functions used as tests can not have any arguments"), - BadTestSignature::ShouldPanicOnlyWithNoArgs => - diag.span_err(i.span, "functions using `#[should_panic]` must return `()`"), - } - false - } - } - } else { - false - }; - - has_test_attr && has_test_signature -} - -fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool { - let has_bench_attr = attr::contains_name(&i.attrs, "bench"); - - fn has_bench_signature(_cx: &TestCtxt, i: &ast::Item) -> bool { - match i.node { - ast::ItemKind::Fn(ref decl, _, _, _) => { - // NB: inadequate check, but we're running - // well before resolve, can't get too deep. - decl.inputs.len() == 1 - } - _ => false - } - } - - let has_bench_signature = has_bench_signature(cx, i); - - if has_bench_attr && !has_bench_signature { - let diag = cx.span_diagnostic; - - diag.span_err(i.span, "functions used as benches must have signature \ - `fn(&mut Bencher) -> impl Termination`"); - } - - has_bench_attr && has_bench_signature -} - -fn is_ignored(i: &ast::Item) -> bool { - attr::contains_name(&i.attrs, "ignore") -} - -fn is_allowed_fail(i: &ast::Item) -> bool { - attr::contains_name(&i.attrs, "allow_fail") -} - -fn should_panic(i: &ast::Item, cx: &TestCtxt) -> ShouldPanic { - match attr::find_by_name(&i.attrs, "should_panic") { - Some(attr) => { - let sd = cx.span_diagnostic; - if attr.is_value_str() { - sd.struct_span_warn( - attr.span(), - "attribute must be of the form: \ - `#[should_panic]` or \ - `#[should_panic(expected = \"error message\")]`" - ).note("Errors in this attribute were erroneously allowed \ - and will become a hard error in a future release.") - .emit(); - return ShouldPanic::Yes(None); - } - match attr.meta_item_list() { - // Handle #[should_panic] - None => ShouldPanic::Yes(None), - // Handle #[should_panic(expected = "foo")] - Some(list) => { - let msg = list.iter() - .find(|mi| mi.check_name("expected")) - .and_then(|mi| mi.meta_item()) - .and_then(|mi| mi.value_str()); - if list.len() != 1 || msg.is_none() { - sd.struct_span_warn( - attr.span(), - "argument must be of the form: \ - `expected = \"error message\"`" - ).note("Errors in this attribute were erroneously \ - allowed and will become a hard error in a \ - future release.").emit(); - ShouldPanic::Yes(None) - } else { - ShouldPanic::Yes(msg) - } - }, - } - } - None => ShouldPanic::No, - } -} - -/* - -We're going to be building a module that looks more or less like: - -mod __test { - extern crate test (name = "test", vers = "..."); - fn main() { - test::test_main_static(&::os::args()[], tests, test::Options::new()) - } - - static tests : &'static [test::TestDescAndFn] = &[ - ... the list of tests in the crate ... - ]; -} - -*/ - -fn mk_std(cx: &TestCtxt) -> P { - let id_test = Ident::from_str("test"); - let sp = ignored_span(cx, DUMMY_SP); - let (vi, vis, ident) = if cx.is_libtest { - (ast::ItemKind::Use(P(ast::UseTree { - span: DUMMY_SP, - prefix: path_node(vec![id_test]), - kind: ast::UseTreeKind::Simple(None, ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID), - })), - ast::VisibilityKind::Public, keywords::Invalid.ident()) - } else { - (ast::ItemKind::ExternCrate(None), ast::VisibilityKind::Inherited, id_test) - }; - P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident, - node: vi, - attrs: vec![], - vis: dummy_spanned(vis), - span: sp, - tokens: None, - }) -} - +/// Creates a function item for use as the main function of a test build. +/// This function will call the `test_runner` as specified by the crate attribute fn mk_main(cx: &mut TestCtxt) -> P { // Writing this out by hand with 'ignored_span': // pub fn main() { // #![main] - // use std::slice::AsSlice; - // test::test_main_static(::std::os::args().as_slice(), TESTS, test::Options::new()); + // test::test_main_static(::std::os::args().as_slice(), &[..tests]); // } - let sp = ignored_span(cx, DUMMY_SP); let ecx = &cx.ext_cx; - - // test::test_main_static - let test_main_path = - ecx.path(sp, vec![Ident::from_str("test"), Ident::from_str("test_main_static")]); + let test_id = ecx.ident_of("test").gensym(); // test::test_main_static(...) - let test_main_path_expr = ecx.expr_path(test_main_path); - let tests_ident_expr = ecx.expr_ident(sp, Ident::from_str("TESTS")); + let mut test_runner = cx.test_runner.clone().unwrap_or( + ecx.path(sp, vec![ + test_id, ecx.ident_of("test_main_static") + ])); + + test_runner.span = sp; + + let test_main_path_expr = ecx.expr_path(test_runner.clone()); let call_test_main = ecx.expr_call(sp, test_main_path_expr, - vec![tests_ident_expr]); + vec![mk_tests_slice(cx)]); let call_test_main = ecx.stmt_expr(call_test_main); + // #![main] let main_meta = ecx.meta_word(sp, Symbol::intern("main")); let main_attr = ecx.attribute(sp, main_meta); + + // extern crate test as test_gensym + let test_extern_stmt = ecx.stmt_item(sp, ecx.item(sp, + test_id, + vec![], + ast::ItemKind::ExternCrate(Some(Symbol::intern("test"))) + )); + // pub fn main() { ... } let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![])); - let main_body = ecx.block(sp, vec![call_test_main]); + + // If no test runner is provided we need to import the test crate + let main_body = if cx.test_runner.is_none() { + ecx.block(sp, vec![test_extern_stmt, call_test_main]) + } else { + ecx.block(sp, vec![call_test_main]) + }; + let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], ast::FunctionRetTy::Ty(main_ret_ty)), ast::FnHeader::default(), ast::Generics::default(), main_body); + + // Honor the reexport_test_harness_main attribute + let main_id = Ident::new( + cx.reexport_test_harness_main.unwrap_or(Symbol::gensym("main")), + sp); + P(ast::Item { - ident: Ident::from_str("main"), + ident: main_id, attrs: vec![main_attr], id: ast::DUMMY_NODE_ID, node: main, @@ -561,71 +391,7 @@ fn mk_main(cx: &mut TestCtxt) -> P { span: sp, tokens: None, }) -} -fn mk_test_module(cx: &mut TestCtxt) -> (P, Option>) { - // Link to test crate - let import = mk_std(cx); - - // A constant vector of test descriptors. - let tests = mk_tests(cx); - - // The synthesized main function which will call the console test runner - // with our list of tests - let mainfn = mk_main(cx); - - let testmod = ast::Mod { - inner: DUMMY_SP, - items: vec![import, mainfn, tests], - }; - let item_ = ast::ItemKind::Mod(testmod); - let mod_ident = Ident::with_empty_ctxt(Symbol::gensym("__test")); - - let mut expander = cx.ext_cx.monotonic_expander(); - let item = expander.fold_item(P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident: mod_ident, - attrs: vec![], - node: item_, - vis: dummy_spanned(ast::VisibilityKind::Public), - span: DUMMY_SP, - tokens: None, - })).pop().unwrap(); - let reexport = cx.reexport_test_harness_main.map(|s| { - // building `use __test::main as ;` - let rename = Ident::with_empty_ctxt(s); - - let use_path = ast::UseTree { - span: DUMMY_SP, - prefix: path_node(vec![mod_ident, Ident::from_str("main")]), - kind: ast::UseTreeKind::Simple(Some(rename), ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID), - }; - - expander.fold_item(P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident: keywords::Invalid.ident(), - attrs: vec![], - node: ast::ItemKind::Use(P(use_path)), - vis: dummy_spanned(ast::VisibilityKind::Inherited), - span: DUMMY_SP, - tokens: None, - })).pop().unwrap() - }); - - debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item)); - - (item, reexport) -} - -fn nospan(t: T) -> source_map::Spanned { - source_map::Spanned { node: t, span: DUMMY_SP } -} - -fn path_node(ids: Vec) -> ast::Path { - ast::Path { - span: DUMMY_SP, - segments: ids.into_iter().map(|id| ast::PathSegment::from_ident(id)).collect(), - } } fn path_name_i(idents: &[Ident]) -> String { @@ -640,184 +406,46 @@ fn path_name_i(idents: &[Ident]) -> String { path_name } -fn mk_tests(cx: &TestCtxt) -> P { - // The vector of test_descs for this crate - let test_descs = mk_test_descs(cx); +/// Creates a slice containing every test like so: +/// &[path::to::test1, path::to::test2] +fn mk_tests_slice(cx: &TestCtxt) -> P { + debug!("building test vector from {} tests", cx.test_cases.len()); + let ref ecx = cx.ext_cx; - // FIXME #15962: should be using quote_item, but that stringifies - // __test_reexports, causing it to be reinterned, losing the - // gensym information. - let sp = ignored_span(cx, DUMMY_SP); - let ecx = &cx.ext_cx; - let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"), - ecx.ident_of("test"), - ecx.ident_of("TestDescAndFn")])); - let static_lt = ecx.lifetime(sp, keywords::StaticLifetime.ident()); - // &'static [self::test::TestDescAndFn] - let static_type = ecx.ty_rptr(sp, - ecx.ty(sp, ast::TyKind::Slice(struct_type)), - Some(static_lt), - ast::Mutability::Immutable); - // static TESTS: $static_type = &[...]; - ecx.item_const(sp, - ecx.ident_of("TESTS"), - static_type, - test_descs) + ecx.expr_vec_slice(DUMMY_SP, + cx.test_cases.iter().map(|test| { + ecx.expr_addr_of(test.span, + ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path)))) + }).collect()) } -fn mk_test_descs(cx: &TestCtxt) -> P { - debug!("building test vector from {} tests", cx.testfns.len()); - - P(ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprKind::AddrOf(ast::Mutability::Immutable, - P(ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprKind::Array(cx.testfns.iter().map(|test| { - mk_test_desc_and_fn_rec(cx, test) - }).collect()), - span: DUMMY_SP, - attrs: ThinVec::new(), - })), - span: DUMMY_SP, - attrs: ThinVec::new(), - }) -} - -fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { - // FIXME #15962: should be using quote_expr, but that stringifies - // __test_reexports, causing it to be reinterned, losing the - // gensym information. - - let span = ignored_span(cx, test.span); - let ecx = &cx.ext_cx; - let self_id = ecx.ident_of("self"); - let test_id = ecx.ident_of("test"); - - // creates self::test::$name - let test_path = |name| { - ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)]) - }; - // creates $name: $expr - let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr); - - // path to the #[test] function: "foo::bar::baz" - let path_string = path_name_i(&test.path[..]); - - debug!("encoding {}", path_string); - - let name_expr = ecx.expr_str(span, Symbol::intern(&path_string)); - - // self::test::StaticTestName($name_expr) - let name_expr = ecx.expr_call(span, - ecx.expr_path(test_path("StaticTestName")), - vec![name_expr]); - - let ignore_expr = ecx.expr_bool(span, test.ignore); - let should_panic_path = |name| { - ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)]) - }; - let fail_expr = match test.should_panic { - ShouldPanic::No => ecx.expr_path(should_panic_path("No")), - ShouldPanic::Yes(msg) => { - match msg { - Some(msg) => { - let msg = ecx.expr_str(span, msg); - let path = should_panic_path("YesWithMessage"); - ecx.expr_call(span, ecx.expr_path(path), vec![msg]) - } - None => ecx.expr_path(should_panic_path("Yes")), - } - } - }; - let allow_fail_expr = ecx.expr_bool(span, test.allow_fail); - - // self::test::TestDesc { ... } - let desc_expr = ecx.expr_struct( - span, - test_path("TestDesc"), - vec![field("name", name_expr), - field("ignore", ignore_expr), - field("should_panic", fail_expr), - field("allow_fail", allow_fail_expr)]); - +/// Creates a path from the top-level __test module to the test via __test_reexports +fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec{ let mut visible_path = vec![]; - if cx.features.extern_absolute_paths { - visible_path.push(keywords::Crate.ident()); - } match cx.toplevel_reexport { Some(id) => visible_path.push(id), None => { - let diag = cx.span_diagnostic; - diag.bug("expected to find top-level re-export name, but found None"); + cx.span_diagnostic.bug("expected to find top-level re-export name, but found None"); } - }; - visible_path.extend_from_slice(&test.path[..]); - - // Rather than directly give the test function to the test - // harness, we create a wrapper like one of the following: - // - // || test::assert_test_result(real_function()) // for test - // |b| test::assert_test_result(real_function(b)) // for bench - // - // this will coerce into a fn pointer that is specialized to the - // actual return type of `real_function` (Typically `()`, but not always). - let fn_expr = { - // construct `real_function()` (this will be inserted into the overall expr) - let real_function_expr = ecx.expr_path(ecx.path_global(span, visible_path)); - // construct path `test::assert_test_result` - let assert_test_result = test_path("assert_test_result"); - if test.bench { - // construct `|b| {..}` - let b_ident = Ident::with_empty_ctxt(Symbol::gensym("b")); - let b_expr = ecx.expr_ident(span, b_ident); - ecx.lambda( - span, - vec![b_ident], - // construct `assert_test_result(..)` - ecx.expr_call( - span, - ecx.expr_path(assert_test_result), - vec![ - // construct `real_function(b)` - ecx.expr_call( - span, - real_function_expr, - vec![b_expr], - ) - ], - ), - ) - } else { - // construct `|| {..}` - ecx.lambda( - span, - vec![], - // construct `assert_test_result(..)` - ecx.expr_call( - span, - ecx.expr_path(assert_test_result), - vec![ - // construct `real_function()` - ecx.expr_call( - span, - real_function_expr, - vec![], - ) - ], - ), - ) - } - }; - - let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; - - // self::test::$variant_name($fn_expr) - let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]); - - // self::test::TestDescAndFn { ... } - ecx.expr_struct(span, - test_path("TestDescAndFn"), - vec![field("desc", desc_expr), - field("testfn", testfn_expr)]) + } + visible_path.extend_from_slice(path); + visible_path +} + +fn is_test_case(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "test_case") +} + +fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option { + let test_attr = attr::find_by_name(&krate.attrs, "test_runner")?; + if let Some(meta_list) = test_attr.meta_item_list() { + if meta_list.len() != 1 { + sd.span_fatal(test_attr.span(), + "#![test_runner(..)] accepts exactly 1 argument").raise() + } + Some(meta_list[0].word().as_ref().unwrap().ident.clone()) + } else { + sd.span_fatal(test_attr.span(), + "test_runner must be of the form #[test_runner(..)]").raise() + } } diff --git a/src/libsyntax_ext/Cargo.toml b/src/libsyntax_ext/Cargo.toml index 8dba34583be..5a691bde3ec 100644 --- a/src/libsyntax_ext/Cargo.toml +++ b/src/libsyntax_ext/Cargo.toml @@ -17,3 +17,4 @@ syntax_pos = { path = "../libsyntax_pos" } rustc_data_structures = { path = "../librustc_data_structures" } rustc_target = { path = "../librustc_target" } smallvec = { version = "0.6.5", features = ["union"] } +log = "0.4" diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs index 84436b4e4ea..bbbf338c4f3 100644 --- a/src/libsyntax_ext/lib.rs +++ b/src/libsyntax_ext/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(stage0), feature(nll))] #![cfg_attr(not(stage0), feature(infer_outlives_requirements))] #![feature(str_escape)] - +#![feature(quote)] #![feature(rustc_diagnostic_macros)] extern crate fmt_macros; @@ -32,6 +32,8 @@ extern crate rustc_errors as errors; extern crate rustc_target; #[macro_use] extern crate smallvec; +#[macro_use] +extern crate log; mod diagnostics; @@ -51,6 +53,7 @@ mod format_foreign; mod global_asm; mod log_syntax; mod trace_macros; +mod test; pub mod proc_macro_registrar; @@ -59,7 +62,7 @@ pub mod proc_macro_impl; use rustc_data_structures::sync::Lrc; use syntax::ast; -use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension}; +use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension, MultiModifier}; use syntax::ext::hygiene; use syntax::symbol::Symbol; @@ -130,6 +133,9 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver, assert: assert::expand_assert, } + register(Symbol::intern("test"), MultiModifier(Box::new(test::expand_test))); + register(Symbol::intern("bench"), MultiModifier(Box::new(test::expand_bench))); + // format_args uses `unstable` things internally. register(Symbol::intern("format_args"), NormalTT { diff --git a/src/libsyntax_ext/test.rs b/src/libsyntax_ext/test.rs new file mode 100644 index 00000000000..d9d0f3d0a32 --- /dev/null +++ b/src/libsyntax_ext/test.rs @@ -0,0 +1,328 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// The expansion from a test function to the appropriate test struct for libtest +/// Ideally, this code would be in libtest but for efficiency and error messages it lives here. + +use syntax::ext::base::*; +use syntax::ext::build::AstBuilder; +use syntax::ext::hygiene::{self, Mark, SyntaxContext}; +use syntax::attr; +use syntax::ast; +use syntax::print::pprust; +use syntax::symbol::Symbol; +use syntax_pos::{DUMMY_SP, Span}; +use syntax::source_map::{ExpnInfo, MacroAttribute}; +use std::iter; + +pub fn expand_test( + cx: &mut ExtCtxt, + attr_sp: Span, + _meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + expand_test_or_bench(cx, attr_sp, item, false) +} + +pub fn expand_bench( + cx: &mut ExtCtxt, + attr_sp: Span, + _meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + expand_test_or_bench(cx, attr_sp, item, true) +} + +pub fn expand_test_or_bench( + cx: &mut ExtCtxt, + attr_sp: Span, + item: Annotatable, + is_bench: bool +) -> Vec { + // If we're not in test configuration, remove the annotated item + if !cx.ecfg.should_test { return vec![]; } + + let item = + if let Annotatable::Item(i) = item { i } + else { + cx.parse_sess.span_diagnostic.span_fatal(item.span(), + "#[test] attribute is only allowed on fn items").raise(); + }; + + if let ast::ItemKind::Mac(_) = item.node { + cx.parse_sess.span_diagnostic.span_warn(item.span, + "#[test] attribute should not be used on macros. Use #[cfg(test)] instead."); + return vec![Annotatable::Item(item)]; + } + + // has_*_signature will report any errors in the type so compilation + // will fail. We shouldn't try to expand in this case because the errors + // would be spurious. + if (!is_bench && !has_test_signature(cx, &item)) || + (is_bench && !has_bench_signature(cx, &item)) { + return vec![Annotatable::Item(item)]; + } + + let (sp, attr_sp) = { + let mark = Mark::fresh(Mark::root()); + mark.set_expn_info(ExpnInfo { + call_site: DUMMY_SP, + def_site: None, + format: MacroAttribute(Symbol::intern("test")), + allow_internal_unstable: true, + allow_internal_unsafe: false, + local_inner_macros: false, + edition: hygiene::default_edition(), + }); + (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)), + attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark))) + }; + + // Gensym "test" so we can extern crate without conflicting with any local names + let test_id = cx.ident_of("test").gensym(); + + // creates test::$name + let test_path = |name| { + cx.path(sp, vec![test_id, cx.ident_of(name)]) + }; + + // creates test::$name + let should_panic_path = |name| { + cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)]) + }; + + // creates $name: $expr + let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr); + + let test_fn = if is_bench { + // A simple ident for a lambda + let b = cx.ident_of("b"); + + cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![ + // |b| self::test::assert_test_result( + cx.lambda1(sp, + cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![ + // super::$test_fn(b) + cx.expr_call(sp, + cx.expr_path(cx.path(sp, vec![item.ident])), + vec![cx.expr_ident(sp, b)]) + ]), + b + ) + // ) + ]) + } else { + cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![ + // || { + cx.lambda0(sp, + // test::assert_test_result( + cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![ + // $test_fn() + cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![]) + // ) + ]) + // } + ) + // ) + ]) + }; + + let mut test_const = cx.item(sp, item.ident.gensym(), + // #[test_case] + vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))], + // const $ident: test::TestDescAndFn = + ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), + // test::TestDescAndFn { + cx.expr_struct(sp, test_path("TestDescAndFn"), vec![ + // desc: test::TestDesc { + field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![ + // name: "path::to::test" + field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")), + vec![ + cx.expr_str(sp, Symbol::intern(&item_path( + // skip the name of the root module + &cx.current_expansion.module.mod_path[1..], + &item.ident + ))) + ])), + // ignore: true | false + field("ignore", cx.expr_bool(sp, should_ignore(&item))), + // allow_fail: true | false + field("allow_fail", cx.expr_bool(sp, should_fail(&item))), + // should_panic: ... + field("should_panic", match should_panic(cx, &item) { + // test::ShouldPanic::No + ShouldPanic::No => cx.expr_path(should_panic_path("No")), + // test::ShouldPanic::Yes + ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")), + // test::ShouldPanic::YesWithMessage("...") + ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp, + cx.expr_path(should_panic_path("YesWithMessage")), + vec![cx.expr_str(sp, sym)]), + }), + // }, + ])), + // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...) + field("testfn", test_fn) + // } + ]) + // } + )); + test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc}); + + // extern crate test as test_gensym + let test_extern = cx.item(sp, + test_id, + vec![], + ast::ItemKind::ExternCrate(Some(Symbol::intern("test"))) + ); + + debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); + + vec![ + // Access to libtest under a gensymed name + Annotatable::Item(test_extern), + // The generated test case + Annotatable::Item(test_const), + // The original item + Annotatable::Item(item) + ] +} + +fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String { + mod_path.iter().chain(iter::once(item_ident)) + .map(|x| x.to_string()).collect::>().join("::") +} + +enum ShouldPanic { + No, + Yes(Option), +} + +fn should_ignore(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "ignore") +} + +fn should_fail(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "allow_fail") +} + +fn should_panic(cx: &ExtCtxt, i: &ast::Item) -> ShouldPanic { + match attr::find_by_name(&i.attrs, "should_panic") { + Some(attr) => { + let ref sd = cx.parse_sess.span_diagnostic; + if attr.is_value_str() { + sd.struct_span_warn( + attr.span(), + "attribute must be of the form: \ + `#[should_panic]` or \ + `#[should_panic(expected = \"error message\")]`" + ).note("Errors in this attribute were erroneously allowed \ + and will become a hard error in a future release.") + .emit(); + return ShouldPanic::Yes(None); + } + match attr.meta_item_list() { + // Handle #[should_panic] + None => ShouldPanic::Yes(None), + // Handle #[should_panic(expected = "foo")] + Some(list) => { + let msg = list.iter() + .find(|mi| mi.check_name("expected")) + .and_then(|mi| mi.meta_item()) + .and_then(|mi| mi.value_str()); + if list.len() != 1 || msg.is_none() { + sd.struct_span_warn( + attr.span(), + "argument must be of the form: \ + `expected = \"error message\"`" + ).note("Errors in this attribute were erroneously \ + allowed and will become a hard error in a \ + future release.").emit(); + ShouldPanic::Yes(None) + } else { + ShouldPanic::Yes(msg) + } + }, + } + } + None => ShouldPanic::No, + } +} + +fn has_test_signature(cx: &ExtCtxt, i: &ast::Item) -> bool { + let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic"); + let ref sd = cx.parse_sess.span_diagnostic; + if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node { + if header.unsafety == ast::Unsafety::Unsafe { + sd.span_err( + i.span, + "unsafe functions cannot be used for tests" + ); + return false + } + if header.asyncness.is_async() { + sd.span_err( + i.span, + "async functions cannot be used for tests" + ); + return false + } + + + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let has_output = match decl.output { + ast::FunctionRetTy::Default(..) => false, + ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false, + _ => true + }; + + if !decl.inputs.is_empty() { + sd.span_err(i.span, "functions used as tests can not have any arguments"); + return false; + } + + match (has_output, has_should_panic_attr) { + (true, true) => { + sd.span_err(i.span, "functions using `#[should_panic]` must return `()`"); + false + }, + (true, false) => if !generics.params.is_empty() { + sd.span_err(i.span, + "functions used as tests must have signature fn() -> ()"); + false + } else { + true + }, + (false, _) => true + } + } else { + sd.span_err(i.span, "only functions may be used as tests"); + false + } +} + +fn has_bench_signature(cx: &ExtCtxt, i: &ast::Item) -> bool { + let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node { + // NB: inadequate check, but we're running + // well before resolve, can't get too deep. + decl.inputs.len() == 1 + } else { + false + }; + + if !has_sig { + cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \ + signature `fn(&mut Bencher) -> impl Termination`"); + } + + has_sig +} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index d993c6244fc..bf3cb7c537b 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -41,6 +41,7 @@ #![feature(panic_unwind)] #![feature(staged_api)] #![feature(termination_trait_lib)] +#![feature(test)] extern crate getopts; #[cfg(any(unix, target_os = "cloudabi"))] @@ -302,7 +303,7 @@ pub fn test_main(args: &[String], tests: Vec, options: Options) { // a Vec is used in order to effect ownership-transfer // semantics into parallel test runners, which in turn requires a Vec<> // rather than a &[]. -pub fn test_main_static(tests: &[TestDescAndFn]) { +pub fn test_main_static(tests: &[&TestDescAndFn]) { let args = env::args().collect::>(); let owned_tests = tests .iter() diff --git a/src/libtest/stats.rs b/src/libtest/stats.rs index ddb5dcf2a1c..9a8749712c3 100644 --- a/src/libtest/stats.rs +++ b/src/libtest/stats.rs @@ -907,7 +907,8 @@ mod tests { #[cfg(test)] mod bench { - use Bencher; + extern crate test; + use self::test::Bencher; use stats::Stats; #[bench] diff --git a/src/test/incremental/issue-49595/issue_49595.rs b/src/test/incremental/issue-49595/issue_49595.rs index 134f114e6ac..7067e725072 100644 --- a/src/test/incremental/issue-49595/issue_49595.rs +++ b/src/test/incremental/issue-49595/issue_49595.rs @@ -15,12 +15,11 @@ #![feature(rustc_attrs)] #![crate_type = "rlib"] -#![rustc_partition_codegened(module="issue_49595-__test", cfg="cfail2")] +#![rustc_partition_codegened(module="issue_49595-tests", cfg="cfail2")] #![rustc_partition_codegened(module="issue_49595-lit_test", cfg="cfail3")] mod tests { - #[cfg_attr(not(cfail1), ignore)] - #[test] + #[cfg_attr(not(cfail1), test)] fn test() { } } diff --git a/src/test/ui/cfg-non-opt-expr.rs b/src/test/ui/cfg-non-opt-expr.rs index a4b24fa8b4b..bd0a5c66b3e 100644 --- a/src/test/ui/cfg-non-opt-expr.rs +++ b/src/test/ui/cfg-non-opt-expr.rs @@ -9,6 +9,7 @@ // except according to those terms. #![feature(stmt_expr_attributes)] +#![feature(custom_test_frameworks)] fn main() { let _ = #[cfg(unset)] (); @@ -17,6 +18,6 @@ fn main() { //~^ ERROR removing an expression is not supported in this position let _ = [1, 2, 3][#[cfg(unset)] 1]; //~^ ERROR removing an expression is not supported in this position - let _ = #[test] (); + let _ = #[test_case] (); //~^ ERROR removing an expression is not supported in this position } diff --git a/src/test/ui/cfg-non-opt-expr.stderr b/src/test/ui/cfg-non-opt-expr.stderr index 0511c575546..8c5d8900f8b 100644 --- a/src/test/ui/cfg-non-opt-expr.stderr +++ b/src/test/ui/cfg-non-opt-expr.stderr @@ -1,26 +1,26 @@ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:14:13 + --> $DIR/cfg-non-opt-expr.rs:15:13 | LL | let _ = #[cfg(unset)] (); | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:16:21 + --> $DIR/cfg-non-opt-expr.rs:17:21 | LL | let _ = 1 + 2 + #[cfg(unset)] 3; | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:18:23 + --> $DIR/cfg-non-opt-expr.rs:19:23 | LL | let _ = [1, 2, 3][#[cfg(unset)] 1]; | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:20:13 + --> $DIR/cfg-non-opt-expr.rs:21:13 | -LL | let _ = #[test] (); - | ^^^^^^^ +LL | let _ = #[test_case] (); + | ^^^^^^^^^^^^ error: aborting due to 4 previous errors diff --git a/src/test/ui/custom-test-frameworks-simple.rs b/src/test/ui/custom-test-frameworks-simple.rs new file mode 100644 index 00000000000..39a4dc569fa --- /dev/null +++ b/src/test/ui/custom-test-frameworks-simple.rs @@ -0,0 +1,32 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: --test +// run-pass + +#![feature(custom_test_frameworks)] +#![test_runner(crate::foo_runner)] + +#[cfg(test)] +fn foo_runner(ts: &[&Fn(usize)->()]) { + for (i, t) in ts.iter().enumerate() { + t(i); + } +} + +#[test_case] +fn test1(i: usize) { + println!("Hi #{}", i); +} + +#[test_case] +fn test2(i: usize) { + println!("Hey #{}", i); +} diff --git a/src/test/ui/custom_test_frameworks/auxiliary/dynamic_runner.rs b/src/test/ui/custom_test_frameworks/auxiliary/dynamic_runner.rs new file mode 100644 index 00000000000..c204e69eafc --- /dev/null +++ b/src/test/ui/custom_test_frameworks/auxiliary/dynamic_runner.rs @@ -0,0 +1,45 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::process::exit; + +pub trait Testable { + // Name of the test + fn name(&self) -> String; + + // Tests pass by default + fn run(&self) -> bool { + true + } + + // A test can generate subtests + fn subtests(&self) -> Vec> { + vec![] + } +} + +fn run_test(t: &dyn Testable) -> bool { + let success = t.subtests().into_iter().all(|sub_t| run_test(&*sub_t)) && t.run(); + println!("{}...{}", t.name(), if success { "SUCCESS" } else { "FAIL" }); + success +} + +pub fn runner(tests: &[&dyn Testable]) { + let mut failed = false; + for t in tests { + if !run_test(*t) { + failed = true; + } + } + + if failed { + exit(1); + } +} diff --git a/src/test/ui/custom_test_frameworks/auxiliary/example_runner.rs b/src/test/ui/custom_test_frameworks/auxiliary/example_runner.rs new file mode 100644 index 00000000000..7b6b5e02955 --- /dev/null +++ b/src/test/ui/custom_test_frameworks/auxiliary/example_runner.rs @@ -0,0 +1,20 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub trait Testable { + fn name(&self) -> String; + fn run(&self) -> Option; // None will be success, Some is the error message +} + +pub fn runner(tests: &[&dyn Testable]) { + for t in tests { + print!("{}........{}", t.name(), t.run().unwrap_or_else(|| "SUCCESS".to_string())); + } +} diff --git a/src/test/ui/custom_test_frameworks/dynamic.rs b/src/test/ui/custom_test_frameworks/dynamic.rs new file mode 100644 index 00000000000..f82571b948a --- /dev/null +++ b/src/test/ui/custom_test_frameworks/dynamic.rs @@ -0,0 +1,45 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// run-pass +// aux-build:dynamic_runner.rs +// compile-flags:--test +#![feature(custom_test_frameworks)] +#![test_runner(dynamic_runner::runner)] + +extern crate dynamic_runner; + +pub struct AllFoo(&'static str); +struct IsFoo(String); + +impl dynamic_runner::Testable for AllFoo { + fn name(&self) -> String { + String::from(self.0) + } + + fn subtests(&self) -> Vec> { + self.0.split(" ").map(|word| + Box::new(IsFoo(word.into())) as Box + ).collect() + } +} + +impl dynamic_runner::Testable for IsFoo { + fn name(&self) -> String { + self.0.clone() + } + + fn run(&self) -> bool { + self.0 == "foo" + } +} + +#[test_case] +const TEST_2: AllFoo = AllFoo("foo foo"); diff --git a/src/test/ui/custom_test_frameworks/full.rs b/src/test/ui/custom_test_frameworks/full.rs new file mode 100644 index 00000000000..9fcf76ec33e --- /dev/null +++ b/src/test/ui/custom_test_frameworks/full.rs @@ -0,0 +1,38 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// run-pass +// aux-build:example_runner.rs +// compile-flags:--test + +#![feature(custom_test_frameworks)] +#![test_runner(example_runner::runner)] +extern crate example_runner; + +pub struct IsFoo(&'static str); + +impl example_runner::Testable for IsFoo { + fn name(&self) -> String { + self.0.to_string() + } + + fn run(&self) -> Option { + if self.0 != "foo" { + return Some(format!("{} != foo", self.0)); + } + None + } +} + +#[test_case] +const TEST_1: IsFoo = IsFoo("hello"); + +#[test_case] +const TEST_2: IsFoo = IsFoo("foo"); diff --git a/src/test/ui/custom_test_frameworks/mismatch.rs b/src/test/ui/custom_test_frameworks/mismatch.rs new file mode 100644 index 00000000000..28753f1649a --- /dev/null +++ b/src/test/ui/custom_test_frameworks/mismatch.rs @@ -0,0 +1,19 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:example_runner.rs +// compile-flags:--test +#![feature(custom_test_frameworks)] +#![test_runner(example_runner::runner)] + +extern crate example_runner; + +#[test] +fn wrong_kind(){} diff --git a/src/test/ui/custom_test_frameworks/mismatch.stderr b/src/test/ui/custom_test_frameworks/mismatch.stderr new file mode 100644 index 00000000000..8e2afaedfff --- /dev/null +++ b/src/test/ui/custom_test_frameworks/mismatch.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `test::TestDescAndFn: example_runner::Testable` is not satisfied + --> $DIR/mismatch.rs:19:1 + | +LL | fn wrong_kind(){} + | ^^^^^^^^^^^^^^^^^ the trait `example_runner::Testable` is not implemented for `test::TestDescAndFn` + | + = note: required for the cast to the object type `dyn example_runner::Testable` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/feature-gate-custom_test_frameworks.rs b/src/test/ui/feature-gate-custom_test_frameworks.rs new file mode 100644 index 00000000000..e8d1524996f --- /dev/null +++ b/src/test/ui/feature-gate-custom_test_frameworks.rs @@ -0,0 +1,13 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature + +fn main() {} diff --git a/src/test/ui/feature-gate-custom_test_frameworks.stderr b/src/test/ui/feature-gate-custom_test_frameworks.stderr new file mode 100644 index 00000000000..cd04f32697b --- /dev/null +++ b/src/test/ui/feature-gate-custom_test_frameworks.stderr @@ -0,0 +1,11 @@ +error[E0658]: Custom Test Frameworks is an unstable feature (see issue #50297) + --> $DIR/feature-gate-custom_test_frameworks.rs:11:1 + | +LL | #![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: add #![feature(custom_test_frameworks)] to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/inaccessible-test-modules.stderr b/src/test/ui/inaccessible-test-modules.stderr index ce8eaf59027..5b964c1a14b 100644 --- a/src/test/ui/inaccessible-test-modules.stderr +++ b/src/test/ui/inaccessible-test-modules.stderr @@ -2,7 +2,7 @@ error[E0432]: unresolved import `__test` --> $DIR/inaccessible-test-modules.rs:15:5 | LL | use __test as x; //~ ERROR unresolved import `__test` - | ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `__test`? + | ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `test`? error[E0432]: unresolved import `__test_reexports` --> $DIR/inaccessible-test-modules.rs:16:5 diff --git a/src/test/ui/issues/issue-11692-2.rs b/src/test/ui/issues/issue-11692-2.rs index acac2d151fe..d1f5712afb6 100644 --- a/src/test/ui/issues/issue-11692-2.rs +++ b/src/test/ui/issues/issue-11692-2.rs @@ -10,5 +10,5 @@ fn main() { concat!(test!()); - //~^ ERROR cannot find macro `test!` in this scope + //~^ error: `test` can only be used in attributes } diff --git a/src/test/ui/issues/issue-11692-2.stderr b/src/test/ui/issues/issue-11692-2.stderr index 51d6041e922..6c21287bed3 100644 --- a/src/test/ui/issues/issue-11692-2.stderr +++ b/src/test/ui/issues/issue-11692-2.stderr @@ -1,4 +1,4 @@ -error: cannot find macro `test!` in this scope +error: `test` can only be used in attributes --> $DIR/issue-11692-2.rs:12:13 | LL | concat!(test!()); diff --git a/src/test/ui/issues/issue-12997-2.stderr b/src/test/ui/issues/issue-12997-2.stderr index 3030ee4779b..853a2a0f1b4 100644 --- a/src/test/ui/issues/issue-12997-2.stderr +++ b/src/test/ui/issues/issue-12997-2.stderr @@ -5,7 +5,7 @@ LL | fn bar(x: isize) { } | ^^^^^^^^^^^^^^^^^^^^ expected isize, found mutable reference | = note: expected type `isize` - found type `&mut __test::test::Bencher` + found type `&mut test::Bencher` error: aborting due to previous error diff --git a/src/test/ui/lint/test-inner-fn.rs b/src/test/ui/lint/test-inner-fn.rs index 4304c96197f..a7727c69e4c 100644 --- a/src/test/ui/lint/test-inner-fn.rs +++ b/src/test/ui/lint/test-inner-fn.rs @@ -8,11 +8,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// compile-flags: --test -D unnameable_test_functions +// compile-flags: --test -D unnameable_test_items #[test] fn foo() { - #[test] //~ ERROR cannot test inner function [unnameable_test_functions] + #[test] //~ ERROR cannot test inner items [unnameable_test_items] fn bar() {} bar(); } @@ -20,7 +20,7 @@ fn foo() { mod x { #[test] fn foo() { - #[test] //~ ERROR cannot test inner function [unnameable_test_functions] + #[test] //~ ERROR cannot test inner items [unnameable_test_items] fn bar() {} bar(); } diff --git a/src/test/ui/lint/test-inner-fn.stderr b/src/test/ui/lint/test-inner-fn.stderr index 37f0c161036..182fb31a9aa 100644 --- a/src/test/ui/lint/test-inner-fn.stderr +++ b/src/test/ui/lint/test-inner-fn.stderr @@ -1,15 +1,15 @@ -error: cannot test inner function +error: cannot test inner items --> $DIR/test-inner-fn.rs:15:5 | -LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions] +LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items] | ^^^^^^^ | - = note: requested on the command line with `-D unnameable-test-functions` + = note: requested on the command line with `-D unnameable-test-items` -error: cannot test inner function +error: cannot test inner items --> $DIR/test-inner-fn.rs:23:9 | -LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions] +LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items] | ^^^^^^^ error: aborting due to 2 previous errors diff --git a/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr b/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr index 0972a0994fc..0e95c053ce4 100644 --- a/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr +++ b/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr @@ -7,7 +7,7 @@ LL | | } | |_^ `main` can only return types that implement `std::process::Termination` | = help: the trait `std::process::Termination` is not implemented for `std::result::Result` - = note: required by `__test::test::assert_test_result` + = note: required by `test::assert_test_result` error: aborting due to previous error diff --git a/src/test/ui/test-on-macro.rs b/src/test/ui/test-on-macro.rs new file mode 100644 index 00000000000..a153e634434 --- /dev/null +++ b/src/test/ui/test-on-macro.rs @@ -0,0 +1,23 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-pass +// compile-flags:--test + +#![deny(warnings)] + +macro_rules! foo { + () => (fn foo(){}) +} + +#[test] +foo!(); + +fn main(){} diff --git a/src/test/ui/test-on-macro.stderr b/src/test/ui/test-on-macro.stderr new file mode 100644 index 00000000000..a45bb25255e --- /dev/null +++ b/src/test/ui/test-on-macro.stderr @@ -0,0 +1,6 @@ +warning: #[test] attribute should not be used on macros. Use #[cfg(test)] instead. + --> $DIR/test-on-macro.rs:21:1 + | +LL | foo!(); + | ^^^^^^^ + From 08ea5b7c78a864da0b1a348c3c3425e8611cef76 Mon Sep 17 00:00:00 2001 From: John Renner Date: Thu, 30 Aug 2018 13:39:32 -0700 Subject: [PATCH 11/16] Fix #[test] shadowing in macro_prelude --- src/librustc_resolve/lib.rs | 2 ++ src/librustc_resolve/macros.rs | 23 +++++++++++++++++-- src/libsyntax/ext/base.rs | 3 +++ src/libsyntax/feature_gate.rs | 2 -- src/libsyntax_ext/lib.rs | 14 +++++++++-- .../ui/test-shadowing/auxiliary/test_macro.rs | 14 +++++++++++ .../test-shadowing/test-cant-be-shadowed.rs | 18 +++++++++++++++ 7 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/test/ui/test-shadowing/auxiliary/test_macro.rs create mode 100644 src/test/ui/test-shadowing/test-cant-be-shadowed.rs diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 0f6a9742309..65fe01ff96a 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -1412,6 +1412,7 @@ pub struct Resolver<'a, 'b: 'a> { crate_loader: &'a mut CrateLoader<'b>, macro_names: FxHashSet, macro_prelude: FxHashMap>, + unshadowable_attrs: FxHashMap>, pub all_macros: FxHashMap, macro_map: FxHashMap>, macro_defs: FxHashMap, @@ -1729,6 +1730,7 @@ impl<'a, 'crateloader: 'a> Resolver<'a, 'crateloader> { crate_loader, macro_names: FxHashSet(), macro_prelude: FxHashMap(), + unshadowable_attrs: FxHashMap(), all_macros: FxHashMap(), macro_map: FxHashMap(), invocations, diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index 0d3e615b446..4cc8d348667 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -207,6 +207,23 @@ impl<'a, 'crateloader: 'a> base::Resolver for Resolver<'a, 'crateloader> { self.macro_prelude.insert(ident.name, binding); } + fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc) { + let def_id = DefId { + krate: BUILTIN_MACROS_CRATE, + index: DefIndex::from_array_index(self.macro_map.len(), + DefIndexAddressSpace::Low), + }; + let kind = ext.kind(); + self.macro_map.insert(def_id, ext); + let binding = self.arenas.alloc_name_binding(NameBinding { + kind: NameBindingKind::Def(Def::Macro(def_id, kind), false), + span: DUMMY_SP, + vis: ty::Visibility::Invisible, + expansion: Mark::root(), + }); + self.unshadowable_attrs.insert(ident.name, binding); + } + fn resolve_imports(&mut self) { ImportResolver { resolver: self }.resolve_imports() } @@ -462,8 +479,10 @@ impl<'a, 'cl> Resolver<'a, 'cl> { return def; } - if kind == MacroKind::Attr && *&path[0].as_str() == "test" { - return Ok(self.macro_prelude.get(&path[0].name).unwrap().def()) + if kind == MacroKind::Attr && path.len() == 1 { + if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) { + return Ok(ext.def()); + } } let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false); diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index e8a68b6d767..0e059bc4a6c 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -721,6 +721,7 @@ pub trait Resolver { fn visit_ast_fragment_with_placeholders(&mut self, mark: Mark, fragment: &AstFragment, derives: &[Mark]); fn add_builtin(&mut self, ident: ast::Ident, ext: Lrc); + fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc); fn resolve_imports(&mut self); // Resolves attribute and derive legacy macros from `#![plugin(..)]`. @@ -729,6 +730,7 @@ pub trait Resolver { fn resolve_macro_invocation(&mut self, invoc: &Invocation, scope: Mark, force: bool) -> Result>, Determinacy>; + fn resolve_macro_path(&mut self, path: &ast::Path, kind: MacroKind, scope: Mark, derives_in_scope: &[ast::Path], force: bool) -> Result, Determinacy>; @@ -759,6 +761,7 @@ impl Resolver for DummyResolver { fn visit_ast_fragment_with_placeholders(&mut self, _invoc: Mark, _fragment: &AstFragment, _derives: &[Mark]) {} fn add_builtin(&mut self, _ident: ast::Ident, _ext: Lrc) {} + fn add_unshadowable_attr(&mut self, _ident: ast::Ident, _ext: Lrc) {} fn resolve_imports(&mut self) {} fn find_legacy_attr_invoc(&mut self, _attrs: &mut Vec, _allow_derive: bool) diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 35cb5aa5475..c94c7874a05 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -769,8 +769,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("cfg_attr", Normal, Ungated), ("main", Normal, Ungated), ("start", Normal, Ungated), - ("test", Normal, Ungated), - ("bench", Normal, Ungated), ("repr", Normal, Ungated), ("path", Normal, Ungated), ("abi", Normal, Ungated), diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs index bbbf338c4f3..a9990cdeabf 100644 --- a/src/libsyntax_ext/lib.rs +++ b/src/libsyntax_ext/lib.rs @@ -71,6 +71,18 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver, enable_quotes: bool) { deriving::register_builtin_derives(resolver); + { + let mut register_unshadowable = |name, ext| { + resolver.add_unshadowable_attr(ast::Ident::with_empty_ctxt(name), Lrc::new(ext)); + }; + + register_unshadowable(Symbol::intern("test"), + MultiModifier(Box::new(test::expand_test))); + + register_unshadowable(Symbol::intern("bench"), + MultiModifier(Box::new(test::expand_bench))); + } + let mut register = |name, ext| { resolver.add_builtin(ast::Ident::with_empty_ctxt(name), Lrc::new(ext)); }; @@ -133,8 +145,6 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver, assert: assert::expand_assert, } - register(Symbol::intern("test"), MultiModifier(Box::new(test::expand_test))); - register(Symbol::intern("bench"), MultiModifier(Box::new(test::expand_bench))); // format_args uses `unstable` things internally. register(Symbol::intern("format_args"), diff --git a/src/test/ui/test-shadowing/auxiliary/test_macro.rs b/src/test/ui/test-shadowing/auxiliary/test_macro.rs new file mode 100644 index 00000000000..ba7ed73b822 --- /dev/null +++ b/src/test/ui/test-shadowing/auxiliary/test_macro.rs @@ -0,0 +1,14 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_export] +macro_rules! test { + () => {}; +} \ No newline at end of file diff --git a/src/test/ui/test-shadowing/test-cant-be-shadowed.rs b/src/test/ui/test-shadowing/test-cant-be-shadowed.rs new file mode 100644 index 00000000000..4ae8b7ffe8b --- /dev/null +++ b/src/test/ui/test-shadowing/test-cant-be-shadowed.rs @@ -0,0 +1,18 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-pass +// aux-build:test_macro.rs +// compile-flags:--test + +#[macro_use] extern crate test_macro; + +#[test] +fn foo(){} \ No newline at end of file From e5ed10571690b2ee4fc64319967973b2e50b517f Mon Sep 17 00:00:00 2001 From: John Renner Date: Thu, 30 Aug 2018 13:40:33 -0700 Subject: [PATCH 12/16] Document #[test_case] and #![test_runner] --- .../custom-test-frameworks.md | 33 +++++++++++++++++++ src/test/ui/issues/issue-11692-2.rs | 2 +- src/test/ui/issues/issue-11692-2.stderr | 2 +- .../ui/test-shadowing/auxiliary/test_macro.rs | 2 +- .../test-shadowing/test-cant-be-shadowed.rs | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/custom-test-frameworks.md diff --git a/src/doc/unstable-book/src/language-features/custom-test-frameworks.md b/src/doc/unstable-book/src/language-features/custom-test-frameworks.md new file mode 100644 index 00000000000..3990b6ad2f0 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/custom-test-frameworks.md @@ -0,0 +1,33 @@ +# `custom_test_frameworks` + +The tracking issue for this feature is: [#50297] + +[#50297]: https://github.com/rust-lang/rust/issues/50297 + +------------------------ + +The `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`. +Any function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`) +and be passed to the test runner determined by the `#![test_runner]` crate attribute. + +```rust +#![feature(custom_test_frameworks)] +#![test_runner(my_runner)] + +fn my_runner(tests: &[&i32]) { + for t in tests { + if **t == 0 { + println!("PASSED"); + } else { + println!("FAILED"); + } + } +} + +#[test_case] +const WILL_PASS: i32 = 0; + +#[test_case] +const WILL_FAIL: i32 = 4; +``` + diff --git a/src/test/ui/issues/issue-11692-2.rs b/src/test/ui/issues/issue-11692-2.rs index d1f5712afb6..424cbd981c8 100644 --- a/src/test/ui/issues/issue-11692-2.rs +++ b/src/test/ui/issues/issue-11692-2.rs @@ -10,5 +10,5 @@ fn main() { concat!(test!()); - //~^ error: `test` can only be used in attributes + //~^ error: cannot find macro `test!` in this scope } diff --git a/src/test/ui/issues/issue-11692-2.stderr b/src/test/ui/issues/issue-11692-2.stderr index 6c21287bed3..51d6041e922 100644 --- a/src/test/ui/issues/issue-11692-2.stderr +++ b/src/test/ui/issues/issue-11692-2.stderr @@ -1,4 +1,4 @@ -error: `test` can only be used in attributes +error: cannot find macro `test!` in this scope --> $DIR/issue-11692-2.rs:12:13 | LL | concat!(test!()); diff --git a/src/test/ui/test-shadowing/auxiliary/test_macro.rs b/src/test/ui/test-shadowing/auxiliary/test_macro.rs index ba7ed73b822..2e9d2dcc410 100644 --- a/src/test/ui/test-shadowing/auxiliary/test_macro.rs +++ b/src/test/ui/test-shadowing/auxiliary/test_macro.rs @@ -11,4 +11,4 @@ #[macro_export] macro_rules! test { () => {}; -} \ No newline at end of file +} diff --git a/src/test/ui/test-shadowing/test-cant-be-shadowed.rs b/src/test/ui/test-shadowing/test-cant-be-shadowed.rs index 4ae8b7ffe8b..4b1a437818f 100644 --- a/src/test/ui/test-shadowing/test-cant-be-shadowed.rs +++ b/src/test/ui/test-shadowing/test-cant-be-shadowed.rs @@ -15,4 +15,4 @@ #[macro_use] extern crate test_macro; #[test] -fn foo(){} \ No newline at end of file +fn foo(){} From 0593dc7e3c9783f1c0bbbc8f017f9e914114e057 Mon Sep 17 00:00:00 2001 From: John Renner Date: Sun, 2 Sep 2018 09:03:24 -0700 Subject: [PATCH 13/16] Move #[test_case] to a syntax extension --- src/librustc_driver/driver.rs | 1 - src/librustc_lint/builtin.rs | 2 +- src/librustc_resolve/macros.rs | 2 +- src/libsyntax/config.rs | 15 +--- src/libsyntax/ext/expand.rs | 50 ++----------- src/libsyntax/feature_gate.rs | 14 ++-- src/libsyntax/parse/parser.rs | 1 - src/libsyntax/test.rs | 2 +- src/libsyntax_ext/lib.rs | 2 + src/libsyntax_ext/test.rs | 10 ++- src/libsyntax_ext/test_case.rs | 75 +++++++++++++++++++ src/test/ui/cfg-non-opt-expr.rs | 2 - src/test/ui/cfg-non-opt-expr.stderr | 8 +- .../ui/feature-gate-custom_test_frameworks.rs | 2 +- ...feature-gate-custom_test_frameworks.stderr | 4 +- 15 files changed, 108 insertions(+), 82 deletions(-) create mode 100644 src/libsyntax_ext/test_case.rs diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index c6344cb9210..d27b0856c15 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -828,7 +828,6 @@ where let (mut krate, features) = syntax::config::features( krate, &sess.parse_sess, - sess.opts.test, sess.edition(), ); // these need to be set "early" so that expansion sees `quote` if enabled. diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 40662bafa1b..8ebb181247c 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -1872,7 +1872,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems { return; } - if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") { + if let Some(attr) = attr::find_by_name(&it.attrs, "rustc_test_marker") { cx.struct_span_lint( UNNAMEABLE_TEST_ITEMS, attr.span, diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index 4cc8d348667..f403e09b7f7 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -479,7 +479,7 @@ impl<'a, 'cl> Resolver<'a, 'cl> { return def; } - if kind == MacroKind::Attr && path.len() == 1 { + if kind == MacroKind::Attr { if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) { return Ok(ext.def()); } diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs index 900d830b4c0..5233267e3a9 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -21,18 +21,16 @@ use ptr::P; /// A folder that strips out items that do not belong in the current configuration. pub struct StripUnconfigured<'a> { - pub should_test: bool, pub sess: &'a ParseSess, pub features: Option<&'a Features>, } // `cfg_attr`-process the crate's attributes and compute the crate's features. -pub fn features(mut krate: ast::Crate, sess: &ParseSess, should_test: bool, edition: Edition) +pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition) -> (ast::Crate, Features) { let features; { let mut strip_unconfigured = StripUnconfigured { - should_test, sess, features: None, }; @@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> { // Determine if a node with the given attributes should be included in this configuration. pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool { attrs.iter().all(|attr| { - // When not compiling with --test we should not compile the #[test] functions - if !self.should_test && is_test(attr) { - return false; - } - let mis = if !is_cfg(attr) { return true; } else if let Some(mis) = attr.meta_item_list() { @@ -249,7 +242,7 @@ impl<'a> StripUnconfigured<'a> { // // NB: This is intentionally not part of the fold_expr() function // in order for fold_opt_expr() to be able to avoid this check - if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) { + if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) { let msg = "removing an expression is not supported in this position"; self.sess.span_diagnostic.span_err(attr.span, msg); } @@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> { fn is_cfg(attr: &ast::Attribute) -> bool { attr.check_name("cfg") } - -pub fn is_test(att: &ast::Attribute) -> bool { - att.check_name("test_case") -} diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 956e086b01b..3bb19121ee3 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -450,14 +450,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { let (fragment_with_placeholders, invocations) = { let mut collector = InvocationCollector { cfg: StripUnconfigured { - should_test: self.cx.ecfg.should_test, sess: self.cx.parse_sess, features: self.cx.ecfg.features, }, cx: self.cx, invocations: Vec::new(), monotonic: self.monotonic, - tests_nameable: true, }; (fragment.fold_with(&mut collector), collector.invocations) }; @@ -475,7 +473,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fn fully_configure(&mut self, item: Annotatable) -> Annotatable { let mut cfg = StripUnconfigured { - should_test: self.cx.ecfg.should_test, sess: self.cx.parse_sess, features: self.cx.ecfg.features, }; @@ -1047,11 +1044,6 @@ struct InvocationCollector<'a, 'b: 'a> { cfg: StripUnconfigured<'a>, invocations: Vec, monotonic: bool, - - /// Test functions need to be nameable. Tests inside functions or in other - /// unnameable locations need to be ignored. `tests_nameable` tracks whether - /// any test functions found in the current context would be nameable. - tests_nameable: bool, } impl<'a, 'b> InvocationCollector<'a, 'b> { @@ -1069,20 +1061,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { placeholder(fragment_kind, NodeId::placeholder_from_mark(mark)) } - /// Folds the item allowing tests to be expanded because they are still nameable. - /// This should probably only be called with module items - fn fold_nameable(&mut self, item: P) -> OneVector> { - fold::noop_fold_item(item, self) - } - - /// Folds the item but doesn't allow tests to occur within it - fn fold_unnameable(&mut self, item: P) -> OneVector> { - let was_nameable = mem::replace(&mut self.tests_nameable, false); - let items = fold::noop_fold_item(item, self); - self.tests_nameable = was_nameable; - items - } - fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment { self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span }) } @@ -1297,7 +1275,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { fn fold_item(&mut self, item: P) -> OneVector> { let item = configure!(self, item); - let (attr, traits, mut item) = self.classify_item(item); + let (attr, traits, item) = self.classify_item(item); if attr.is_some() || !traits.is_empty() { let item = Annotatable::Item(item); return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items(); @@ -1319,7 +1297,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { } ast::ItemKind::Mod(ast::Mod { inner, .. }) => { if item.ident == keywords::Invalid.ident() { - return self.fold_nameable(item); + return noop_fold_item(item, self); } let orig_directory_ownership = self.cx.current_expansion.directory_ownership; @@ -1359,32 +1337,13 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { let orig_module = mem::replace(&mut self.cx.current_expansion.module, Rc::new(module)); - let result = self.fold_nameable(item); + let result = noop_fold_item(item, self); self.cx.current_expansion.module = orig_module; self.cx.current_expansion.directory_ownership = orig_directory_ownership; result } - // Ensure that test items can be exported by the harness generator. - // #[test] fn foo() {} - // becomes: - // #[test] pub fn foo_gensym(){} - ast::ItemKind::Const(..) - | ast::ItemKind::Static(..) - | ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => { - if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") { - // Publicize the item under gensymed name to avoid pollution - // This means #[test_case] items can't be referenced by user code - item = item.map(|mut item| { - item.vis = respan(item.vis.span, ast::VisibilityKind::Public); - item.ident = item.ident.gensym(); - item - }); - } - - self.fold_unnameable(item) - } - _ => self.fold_unnameable(item), + _ => noop_fold_item(item, self), } } @@ -1609,6 +1568,7 @@ impl<'feat> ExpansionConfig<'feat> { feature_tests! { fn enable_quotes = quote, fn enable_asm = asm, + fn enable_custom_test_frameworks = custom_test_frameworks, fn enable_global_asm = global_asm, fn enable_log_syntax = log_syntax, fn enable_concat_idents = concat_idents, diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index c94c7874a05..e3ea3563d85 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -777,10 +777,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("no_link", Normal, Ungated), ("derive", Normal, Ungated), ("should_panic", Normal, Ungated), - ("test_case", Normal, Gated(Stability::Unstable, - "custom_test_frameworks", - "Custom test frameworks are experimental", - cfg_fn!(custom_test_frameworks))), ("ignore", Normal, Ungated), ("no_implicit_prelude", Normal, Ungated), ("reexport_test_harness_main", Normal, Ungated), @@ -965,6 +961,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG attribute is just used for rustc unit \ tests and will never be stable", cfg_fn!(rustc_attrs))), + ("rustc_test_marker", Normal, Gated(Stability::Unstable, + "rustc_attrs", + "the `#[rustc_test_marker]` attribute \ + is used internally to track tests", + cfg_fn!(rustc_attrs))), // RFC #2094 ("nll", Whitelisted, Gated(Stability::Unstable, @@ -1164,7 +1165,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("type_length_limit", CrateLevel, Ungated), ("test_runner", CrateLevel, Gated(Stability::Unstable, "custom_test_frameworks", - "Custom Test Frameworks is an unstable feature", + EXPLAIN_CUSTOM_TEST_FRAMEWORKS, cfg_fn!(custom_test_frameworks))), ]; @@ -1382,6 +1383,9 @@ pub const EXPLAIN_ASM: &'static str = pub const EXPLAIN_GLOBAL_ASM: &'static str = "`global_asm!` is not stable enough for use and is subject to change"; +pub const EXPLAIN_CUSTOM_TEST_FRAMEWORKS: &'static str = + "custom test frameworks are an unstable feature"; + pub const EXPLAIN_LOG_SYNTAX: &'static str = "`log_syntax!` is not stable enough for use and is subject to change"; diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 3862877c3d9..5f80af77f49 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -6272,7 +6272,6 @@ impl<'a> Parser<'a> { let (in_cfg, outer_attrs) = { let mut strip_unconfigured = ::config::StripUnconfigured { sess: self.sess, - should_test: false, // irrelevant features: None, // don't perform gated feature checking }; let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned()); diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 91c63227d30..ab67736c389 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -433,7 +433,7 @@ fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec{ } fn is_test_case(i: &ast::Item) -> bool { - attr::contains_name(&i.attrs, "test_case") + attr::contains_name(&i.attrs, "rustc_test_marker") } fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option { diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs index a9990cdeabf..e16f3b1ccb3 100644 --- a/src/libsyntax_ext/lib.rs +++ b/src/libsyntax_ext/lib.rs @@ -54,6 +54,7 @@ mod global_asm; mod log_syntax; mod trace_macros; mod test; +mod test_case; pub mod proc_macro_registrar; @@ -145,6 +146,7 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver, assert: assert::expand_assert, } + register(Symbol::intern("test_case"), MultiModifier(Box::new(test_case::expand))); // format_args uses `unstable` things internally. register(Symbol::intern("format_args"), diff --git a/src/libsyntax_ext/test.rs b/src/libsyntax_ext/test.rs index d9d0f3d0a32..be3485cfa7c 100644 --- a/src/libsyntax_ext/test.rs +++ b/src/libsyntax_ext/test.rs @@ -135,8 +135,14 @@ pub fn expand_test_or_bench( }; let mut test_const = cx.item(sp, item.ident.gensym(), - // #[test_case] - vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))], + vec![ + // #[cfg(test)] + cx.attribute(attr_sp, cx.meta_list(attr_sp, Symbol::intern("cfg"), vec![ + cx.meta_list_item_word(attr_sp, Symbol::intern("test")) + ])), + // #[rustc_test_marker] + cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker"))) + ], // const $ident: test::TestDescAndFn = ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), // test::TestDescAndFn { diff --git a/src/libsyntax_ext/test_case.rs b/src/libsyntax_ext/test_case.rs new file mode 100644 index 00000000000..0128db7dd78 --- /dev/null +++ b/src/libsyntax_ext/test_case.rs @@ -0,0 +1,75 @@ + +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// #[test_case] is used by custom test authors to mark tests +// When building for test, it needs to make the item public and gensym the name +// Otherwise, we'll omit the item. This behavior means that any item annotated +// with #[test_case] is never addressable. +// +// We mark item with an inert attribute "rustc_test_marker" which the test generation +// logic will pick up on. + +use syntax::ext::base::*; +use syntax::ext::build::AstBuilder; +use syntax::ext::hygiene::{self, Mark, SyntaxContext}; +use syntax::ast; +use syntax::source_map::respan; +use syntax::symbol::Symbol; +use syntax_pos::{DUMMY_SP, Span}; +use syntax::source_map::{ExpnInfo, MacroAttribute}; +use syntax::feature_gate; + +pub fn expand( + ecx: &mut ExtCtxt, + attr_sp: Span, + _meta_item: &ast::MetaItem, + anno_item: Annotatable +) -> Vec { + if !ecx.ecfg.enable_custom_test_frameworks() { + feature_gate::emit_feature_err(&ecx.parse_sess, + "custom_test_frameworks", + attr_sp, + feature_gate::GateIssue::Language, + feature_gate::EXPLAIN_CUSTOM_TEST_FRAMEWORKS); + + return vec![anno_item]; + } + + if !ecx.ecfg.should_test { return vec![]; } + + let sp = { + let mark = Mark::fresh(Mark::root()); + mark.set_expn_info(ExpnInfo { + call_site: DUMMY_SP, + def_site: None, + format: MacroAttribute(Symbol::intern("test_case")), + allow_internal_unstable: true, + allow_internal_unsafe: false, + local_inner_macros: false, + edition: hygiene::default_edition(), + }); + attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)) + }; + + let mut item = anno_item.expect_item(); + + item = item.map(|mut item| { + item.vis = respan(item.vis.span, ast::VisibilityKind::Public); + item.ident = item.ident.gensym(); + item.attrs.push( + ecx.attribute(sp, + ecx.meta_word(sp, Symbol::intern("rustc_test_marker"))) + ); + item + }); + + return vec![Annotatable::Item(item)] +} diff --git a/src/test/ui/cfg-non-opt-expr.rs b/src/test/ui/cfg-non-opt-expr.rs index bd0a5c66b3e..55eca7f45a5 100644 --- a/src/test/ui/cfg-non-opt-expr.rs +++ b/src/test/ui/cfg-non-opt-expr.rs @@ -18,6 +18,4 @@ fn main() { //~^ ERROR removing an expression is not supported in this position let _ = [1, 2, 3][#[cfg(unset)] 1]; //~^ ERROR removing an expression is not supported in this position - let _ = #[test_case] (); - //~^ ERROR removing an expression is not supported in this position } diff --git a/src/test/ui/cfg-non-opt-expr.stderr b/src/test/ui/cfg-non-opt-expr.stderr index 8c5d8900f8b..1892cee113e 100644 --- a/src/test/ui/cfg-non-opt-expr.stderr +++ b/src/test/ui/cfg-non-opt-expr.stderr @@ -16,11 +16,5 @@ error: removing an expression is not supported in this position LL | let _ = [1, 2, 3][#[cfg(unset)] 1]; | ^^^^^^^^^^^^^ -error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:21:13 - | -LL | let _ = #[test_case] (); - | ^^^^^^^^^^^^ - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors diff --git a/src/test/ui/feature-gate-custom_test_frameworks.rs b/src/test/ui/feature-gate-custom_test_frameworks.rs index e8d1524996f..7c9f7dd0402 100644 --- a/src/test/ui/feature-gate-custom_test_frameworks.rs +++ b/src/test/ui/feature-gate-custom_test_frameworks.rs @@ -8,6 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature +#![test_runner(main)] //~ ERROR custom test frameworks are an unstable feature fn main() {} diff --git a/src/test/ui/feature-gate-custom_test_frameworks.stderr b/src/test/ui/feature-gate-custom_test_frameworks.stderr index cd04f32697b..bfcbab50067 100644 --- a/src/test/ui/feature-gate-custom_test_frameworks.stderr +++ b/src/test/ui/feature-gate-custom_test_frameworks.stderr @@ -1,7 +1,7 @@ -error[E0658]: Custom Test Frameworks is an unstable feature (see issue #50297) +error[E0658]: custom test frameworks are an unstable feature (see issue #50297) --> $DIR/feature-gate-custom_test_frameworks.rs:11:1 | -LL | #![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature +LL | #![test_runner(main)] //~ ERROR custom test frameworks are an unstable feature | ^^^^^^^^^^^^^^^^^^^^^ | = help: add #![feature(custom_test_frameworks)] to the crate attributes to enable From fc47a92336c02ee551c72e86ae4d0396895f9535 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Wed, 5 Sep 2018 13:52:58 +0200 Subject: [PATCH 14/16] ThinLTO: Don't keep files open after mmaping them (because it's not needed). --- src/librustc_codegen_llvm/back/lto.rs | 6 +++--- src/librustc_codegen_llvm/back/write.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustc_codegen_llvm/back/lto.rs b/src/librustc_codegen_llvm/back/lto.rs index c1dda02264e..25bc662c408 100644 --- a/src/librustc_codegen_llvm/back/lto.rs +++ b/src/librustc_codegen_llvm/back/lto.rs @@ -29,7 +29,7 @@ use {ModuleCodegen, ModuleLlvm, ModuleKind}; use libc; use std::ffi::{CStr, CString}; -use std::fs::{self, File}; +use std::fs; use std::ptr; use std::slice; use std::sync::Arc; @@ -619,7 +619,7 @@ fn run_pass_manager(cgcx: &CodegenContext, pub enum SerializedModule { Local(ModuleBuffer), FromRlib(Vec), - FromUncompressedFile(memmap::Mmap, File), + FromUncompressedFile(memmap::Mmap), } impl SerializedModule { @@ -627,7 +627,7 @@ impl SerializedModule { match *self { SerializedModule::Local(ref m) => m.data(), SerializedModule::FromRlib(ref m) => m, - SerializedModule::FromUncompressedFile(ref m, _) => m, + SerializedModule::FromUncompressedFile(ref m) => m, } } } diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs index 1c0f89193b2..f76a38c1a2b 100644 --- a/src/librustc_codegen_llvm/back/write.rs +++ b/src/librustc_codegen_llvm/back/write.rs @@ -2485,7 +2485,7 @@ pub(crate) fn submit_pre_lto_module_to_llvm(tcx: TyCtxt, // Schedule the module to be loaded drop(tcx.tx_to_llvm_workers.lock().send(Box::new(Message::AddImportOnlyModule { - module_data: SerializedModule::FromUncompressedFile(mmap, file), + module_data: SerializedModule::FromUncompressedFile(mmap), work_product: module.source, }))); } From bce09b6e34d97a4d71771c6283f25f6199e8e5c7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Sep 2018 12:57:27 -0700 Subject: [PATCH 15/16] rustbuild: Tweak LLVM distribution layout This commit tweaks the layout of a few components that we distribute to hopefully fix across all platforms the recent issues with LLD being unable to find the LLVM shared object. In #53245 we switched to building LLVM as a dynamic library, which means that LLVM tools by default link to LLVM dynamically rather than statically. This in turn means that the tools, at runtime, need to find the LLVM shared library. LLVM's shared library is currently distributed as part of the rustc component. This library is located, however, at `$sysroot/lib`. The LLVM tools we ship are in two locations: * LLD is shipped at `$sysroot/lib/rustlib/$host/bin/rust-lld` * Other LLVM tools are shipped at `$sysroot/bin` Each LLVM tool has an embedded rpath directive indicating where it will search for dynamic libraries. This currently points to `../lib` and is presumably inserted by LLVM's build system. Unfortunately, though, this directive is only correct for the LLVM tools at `$sysroot/bin`, not LLD! This commit is targeted at fixing this situation by making two changes: * LLVM tools other than LLD are moved in the distribution to `$sysroot/lib/rustlib/$host/bin`. This moves them next to LLD and should position them for... * The LLVM shared object is moved to `$sysroot/lib/rustlib/$host/lib` Together this means that all tools should natively be able to find the shared object and the shared object should be installed all the time for the various tools. Overall this should... Closes #53813 --- src/bootstrap/dist.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 92c0a088b5e..c3282275f6f 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -1913,7 +1913,7 @@ fn maybe_install_llvm_dylib(builder: &Builder, llvm_dylib_path.display(), e); }); - let dst_libdir = image.join("lib"); + let dst_libdir = image.join("lib/rustlib").join(&*target).join("lib"); t!(fs::create_dir_all(&dst_libdir)); builder.install(&llvm_dylib_path, &dst_libdir, 0o644); @@ -1967,7 +1967,9 @@ impl Step for LlvmTools { let src_bindir = builder .llvm_out(target) .join("bin"); - let dst_bindir = image.join("bin"); + let dst_bindir = image.join("lib/rustlib") + .join(&*target) + .join("bin"); t!(fs::create_dir_all(&dst_bindir)); for tool in LLVM_TOOLS { let exe = src_bindir.join(exe(tool, &target)); From 7bb30ff0d923f27afd7ab98e338cc835c7b4cf52 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Wed, 5 Sep 2018 10:37:05 -0600 Subject: [PATCH 16/16] Pick up State.h include path change --- src/tools/lldb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/lldb b/src/tools/lldb index 3be47d264e9..7728fa22beb 160000 --- a/src/tools/lldb +++ b/src/tools/lldb @@ -1 +1 @@ -Subproject commit 3be47d264e91844544d56078a18182b8a7418086 +Subproject commit 7728fa22bebea288abfea3b70cf795c60b93df3a