diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 8905591bdba5a..780b9e185b0ed 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -214,3 +214,14 @@ impl SingleAttributeParser for RustcTestMarkerParser { Some(AttributeKind::RustcTestMarker(value_str)) } } + +pub(crate) struct RustcTestEntrypointMarkerParser; + +impl NoArgsAttributeParser for RustcTestEntrypointMarkerParser { + const PATH: &[Symbol] = &[sym::rustc_test_entrypoint_marker]; + const ALLOWED_TARGETS: AllowedTargets<'_> = + AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::Closure)]); + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const STABILITY: AttributeStability = unstable!(rustc_attrs); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcTestEntrypointMarker; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 1595d2f80ddc7..e094447ec454b 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -328,6 +328,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 8d6b3adf36db8..cbab0b7e3010b 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -24,6 +24,9 @@ use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; /// /// We mark item with an inert attribute "rustc_test_marker" which the test generation /// logic will pick up on. +/// +/// The test function also gains a `#[rustc_test_entrypoint_marker]` attribute for tools to pick up +/// on. This behavior is *unstable*. pub(crate) fn expand_test_case( ecx: &mut ExtCtxt<'_>, attr_sp: Span, @@ -377,6 +380,12 @@ pub(crate) fn expand_test_or_bench( let test_extern = cx.item(sp, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, test_ident)); + let item = { + let mut item = item; + item.attrs.push(cx.attr_word(sym::rustc_test_entrypoint_marker, attr_sp)); + item + }; + debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); if is_stmt { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 6a9cfeff49339..88aa1b77cbaed 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -365,6 +365,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::rustc_paren_sugar, sym::rustc_inherit_overflow_checks, sym::rustc_reservation_impl, + sym::rustc_test_entrypoint_marker, sym::rustc_test_marker, sym::rustc_unsafe_specialization_marker, sym::rustc_specialization_trait, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 17d00863d99d5..29cfbe2558751 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1636,6 +1636,9 @@ pub enum AttributeKind { /// Represents `#[rustc_strict_coherence]`. RustcStrictCoherence(Span), + /// Represents `#[rustc_test_entrypoint_marker]` + RustcTestEntrypointMarker, + /// Represents `#[rustc_test_marker]` RustcTestMarker(Symbol), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index f5a6eeec07406..01eab3cc8cb52 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -189,6 +189,7 @@ impl AttributeKind { RustcSpecializationTrait => No, RustcStdInternalSymbol => No, RustcStrictCoherence(..) => Yes, + RustcTestEntrypointMarker => No, RustcTestMarker(..) => No, RustcThenThisWouldNeed(..) => No, RustcTrivialFieldReads => Yes, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 863e4d88872c9..aaee0b8342504 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -395,6 +395,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcSpecializationTrait => (), AttributeKind::RustcStdInternalSymbol => (), AttributeKind::RustcStrictCoherence(..) => (), + AttributeKind::RustcTestEntrypointMarker => (), AttributeKind::RustcTestMarker(..) => (), AttributeKind::RustcThenThisWouldNeed(..) => (), AttributeKind::RustcTrivialFieldReads => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9ab27cd1a4918..f26a3207f9a0e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1856,6 +1856,7 @@ symbols! { rustc_specialization_trait, rustc_std_internal_symbol, rustc_strict_coherence, + rustc_test_entrypoint_marker, rustc_test_marker, rustc_then_this_would_need, rustc_trivial_field_reads, diff --git a/tests/pretty/tests-are-sorted.pp b/tests/pretty/tests-are-sorted.pp index 43f9838e68ce9..f49c79f31a5ec 100644 --- a/tests/pretty/tests-are-sorted.pp +++ b/tests/pretty/tests-are-sorted.pp @@ -30,6 +30,7 @@ testfn: test::StaticTestFn(#[coverage(off)] || test::assert_test_result(m_test())), }; +#[rustc_test_entrypoint_marker] fn m_test() {} extern crate test; @@ -55,6 +56,7 @@ test::assert_test_result(z_test())), }; #[ignore = "not yet implemented"] +#[rustc_test_entrypoint_marker] fn z_test() {} extern crate test; @@ -79,6 +81,7 @@ testfn: test::StaticTestFn(#[coverage(off)] || test::assert_test_result(a_test())), }; +#[rustc_test_entrypoint_marker] fn a_test() {} #[rustc_main] #[coverage(off)] diff --git a/tests/ui-fulldeps/test_entrypoint_attrs.rs b/tests/ui-fulldeps/test_entrypoint_attrs.rs new file mode 100644 index 0000000000000..dac7406337c57 --- /dev/null +++ b/tests/ui-fulldeps/test_entrypoint_attrs.rs @@ -0,0 +1,84 @@ +//@ run-pass +//@ ignore-cross-compile +//@ ignore-remote +//@ edition: 2024 +//@ ignore-stage1 +//! Uses a rustc driver to check that test entrypoints get a `#[rustc_test_entrypoint_marker]` +//! and can be found using that attribute in rustc drivers (the main use for this attribute). + +#![feature(rustc_private)] + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_hir; + +use interface::Compiler; +use rustc_driver::Compilation; +use rustc_interface::interface; +use rustc_middle::ty::TyCtxt; +use std::io::Write; + +const CRATE_NAME: &str = "input"; + +struct TestAttr { + expected_tests: usize, +} + +impl rustc_driver::Callbacks for TestAttr { + fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation { + let mut tests = Vec::new(); + for did in tcx.hir_crate_items(()).definitions() { + if find_attr!(tcx, did, RustcTestEntrypointMarker) { + tests.push(did); + } + } + + // the file contains one test, so we should find one entrypoint marker. + assert_eq!(tests.len(), self.expected_tests); + + Compilation::Stop + } +} + +fn count_tests(src: &str, expected_tests: usize) { + let path = "test_input.rs"; + let mut file = std::fs::File::create(path).unwrap(); + file.write_all(src.as_bytes()).unwrap(); + + let args = [ + "rustc".to_string(), + "--test".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + rustc_driver::catch_fatal_errors(|| -> interface::Result<()> { + rustc_driver::run_compiler(&args, &mut TestAttr { expected_tests }); + Ok(()) + }) + .unwrap() + .unwrap(); +} + +fn main() { + count_tests( + r#" + #[test] + fn meow() {{ }} + "#, + 1, + ); + count_tests( + r#" + #[test] + fn one() {{ }} + + #[test] + fn two() {{ }} + "#, + 2, + ); +}