diff --git a/.gitmodules b/.gitmodules index 3836f4d0638..038237aa179 100644 --- a/.gitmodules +++ b/.gitmodules @@ -59,8 +59,9 @@ [submodule "src/tools/lldb"] path = src/tools/lldb url = https://github.com/rust-lang-nursery/lldb.git - 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.git - branch = release_70 + branch = rust-release-80-v1 + \ No newline at end of file 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/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/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)); 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/liballoc/tests/str.rs b/src/liballoc/tests/str.rs index 6275c7bb112..a5fa7f0c4d9 100644 --- a/src/liballoc/tests/str.rs +++ b/src/liballoc/tests/str.rs @@ -727,33 +727,33 @@ fn test_is_char_boundary() { } #[test] -fn test_trim_left_matches() { +fn test_trim_start_matches() { let v: &[char] = &[]; - assert_eq!(" *** foo *** ".trim_left_matches(v), " *** foo *** "); + assert_eq!(" *** foo *** ".trim_start_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!(" *** foo *** ".trim_start_matches(chars), "foo *** "); + assert_eq!(" *** *** ".trim_start_matches(chars), ""); + assert_eq!("foo *** ".trim_start_matches(chars), "foo *** "); - assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11"); + assert_eq!("11foo1bar11".trim_start_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"); + assert_eq!("12foo1bar12".trim_start_matches(chars), "foo1bar12"); + assert_eq!("123foo1bar123".trim_start_matches(|c: char| c.is_numeric()), "foo1bar123"); } #[test] -fn test_trim_right_matches() { +fn test_trim_end_matches() { let v: &[char] = &[]; - assert_eq!(" *** foo *** ".trim_right_matches(v), " *** foo *** "); + assert_eq!(" *** foo *** ".trim_end_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!(" *** foo *** ".trim_end_matches(chars), " *** foo"); + assert_eq!(" *** *** ".trim_end_matches(chars), ""); + assert_eq!(" *** foo".trim_end_matches(chars), " *** foo"); - assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar"); + assert_eq!("11foo1bar11".trim_end_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"); + assert_eq!("12foo1bar12".trim_end_matches(chars), "12foo1bar"); + assert_eq!("123foo1bar123".trim_end_matches(|c: char| c.is_numeric()), "123foo1bar"); } #[test] @@ -772,23 +772,23 @@ fn test_trim_matches() { } #[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 "); +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_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"); +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] @@ -1518,12 +1518,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 4dcb65dc3f8..2c941c28d4b 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -3563,6 +3563,76 @@ 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()); + /// ``` + #[stable(feature = "trim_direction", since = "1.30.0")] + 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()); + /// ``` + #[stable(feature = "trim_direction", since = "1.30.0")] + 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 @@ -3595,8 +3665,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. @@ -3631,8 +3702,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 @@ -3697,14 +3769,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 { + #[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); if let Some((a, _)) = matcher.next_reject() { @@ -3734,6 +3806,85 @@ impl str { /// 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"); + /// ``` + #[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> + { + 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. + /// + /// 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_right_matches('1'), "11foo1bar"); /// assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar"); /// @@ -3747,18 +3898,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. 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..6b257ed4c3e 100644 --- a/src/librustc_codegen_llvm/back/write.rs +++ b/src/librustc_codegen_llvm/back/write.rs @@ -176,13 +176,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; @@ -2485,7 +2494,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, }))); } diff --git a/src/librustc_codegen_llvm/llvm_util.rs b/src/librustc_codegen_llvm/llvm_util.rs index c23f4387df1..9e8ff204703 100644 --- a/src/librustc_codegen_llvm/llvm_util.rs +++ b/src/librustc_codegen_llvm/llvm_util.rs @@ -177,6 +177,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_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 822590450e2..8ebb181247c 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, "rustc_test_marker") { + 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/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 879b4ea3fe5..f403e09b7f7 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,6 +479,12 @@ impl<'a, 'cl> Resolver<'a, 'cl> { return def; } + if kind == MacroKind::Attr { + 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); let result = if let Some((legacy_binding, _)) = legacy_resolution { Ok(legacy_binding.def()) 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 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..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_or_bench(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_or_bench(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_or_bench(attr: &ast::Attribute) -> bool { - attr.check_name("test") || attr.check_name("bench") -} 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/ext/expand.rs b/src/libsyntax/ext/expand.rs index 6e38f820586..3bb19121ee3 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; @@ -452,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) }; @@ -477,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, }; @@ -1049,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> { @@ -1071,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 }) } @@ -1299,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(); @@ -1321,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; @@ -1361,58 +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 functions are accessible from the test harness. - // #[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(); - // Publicize the item under gensymed name to avoid pollution - 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), + _ => noop_fold_item(item, self), } } @@ -1637,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 14781dd8e24..e3ea3563d85 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! ( @@ -765,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), @@ -959,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, @@ -1156,6 +1163,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", + EXPLAIN_CUSTOM_TEST_FRAMEWORKS, + cfg_fn!(custom_test_frameworks))), ]; // cfg(...)'s that are feature gated @@ -1372,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 49ab0c2256e..ab67736c389 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, "rustc_test_marker") +} + +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..e16f3b1ccb3 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,8 @@ mod format_foreign; mod global_asm; mod log_syntax; mod trace_macros; +mod test; +mod test_case; pub mod proc_macro_registrar; @@ -59,7 +63,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; @@ -68,6 +72,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)); }; @@ -130,6 +146,8 @@ 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"), NormalTT { diff --git a/src/libsyntax_ext/test.rs b/src/libsyntax_ext/test.rs new file mode 100644 index 00000000000..be3485cfa7c --- /dev/null +++ b/src/libsyntax_ext/test.rs @@ -0,0 +1,334 @@ +// 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(), + 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 { + 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/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/libtest/formatters/json.rs b/src/libtest/formatters/json.rs index f7e0788a884..3c803ad8233 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 )) } @@ -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, 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/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 } diff --git a/src/test/ui/cfg-non-opt-expr.rs b/src/test/ui/cfg-non-opt-expr.rs index a4b24fa8b4b..55eca7f45a5 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,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] (); - //~^ 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..1892cee113e 100644 --- a/src/test/ui/cfg-non-opt-expr.stderr +++ b/src/test/ui/cfg-non-opt-expr.stderr @@ -1,26 +1,20 @@ 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 - | -LL | let _ = #[test] (); - | ^^^^^^^ - -error: aborting due to 4 previous errors +error: aborting due to 3 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..7c9f7dd0402 --- /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 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 new file mode 100644 index 00000000000..bfcbab50067 --- /dev/null +++ b/src/test/ui/feature-gate-custom_test_frameworks.stderr @@ -0,0 +1,11 @@ +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 are 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..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 cannot find macro `test!` in this scope + //~^ error: cannot find macro `test!` in this scope } 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!(); + | ^^^^^^^ + 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..2e9d2dcc410 --- /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 { + () => {}; +} 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..4b1a437818f --- /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(){} 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..7728fa22beb 160000 --- a/src/tools/lldb +++ b/src/tools/lldb @@ -1 +1 @@ -Subproject commit 3dbe998969d457c5cef245f61b48bdaed0f5c059 +Subproject commit 7728fa22bebea288abfea3b70cf795c60b93df3a