Rollup merge of #151294 - jieyouxu:infer-needs-target-std, r=Zalathar

compiletest: add implied `needs-target-std` for `codegen` mode tests unless annotated with `#![no_std]`/`#![no_core]`

A `codegen` mode test (such as `codegen-llvm` test suite) will now by default have an implied `//@ needs-target-std` directive, *unless* the test explicitly has an `#![no_std]`/`#![no_core]` attribute which disables this behavior.

- When a test has both `#![no_std]`/`#![no_core]` and `//@ needs-target-std`, the explicit `//@ needs-target-std` directive will cause the test to be ignored for targets that do not support std still.

This is to make it easier to test out-of-tree targets / custom targets (and targets not tested in r-l/r CI) without requiring target maintainers to do a bunch of manual `//@ needs-target-std` busywork.

Context: [#t-compiler/help > `compiletest` cannot find `core` library for target != host](https://rust-lang.zulipchat.com/#narrow/channel/182449-t-compiler.2Fhelp/topic/.60compiletest.60.20cannot.20find.20.60core.60.20library.20for.20target.20!.3D.20host/with/568652419)

## Implementation remarks

This is an alternative version of https://github.com/rust-lang/rust/pull/150672, with some differences:

- *This* PR applies this implied-`needs-target-std` behavior to all `codegen` test mode tests.
- *This* PR does the synthetic directive injection in the same place as implied-`codegen-run` directives. Both are of course hacks, but at least they're together next to each other.
This commit is contained in:
Stuart Cook 2026-01-26 14:36:22 +11:00 committed by GitHub
commit 504c7fe65d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 137 additions and 5 deletions

View file

@ -311,14 +311,20 @@ For example, `./x test tests/debuginfo -- --debugger gdb` will only test GDB com
### Codegen tests
The tests in [`tests/codegen-llvm`] test LLVM code generation. They compile the test
with the `--emit=llvm-ir` flag to emit LLVM IR. They then run the LLVM
The tests in [`tests/codegen-llvm`] test LLVM code generation. They compile the
test with the `--emit=llvm-ir` flag to emit LLVM IR. They then run the LLVM
[FileCheck] tool. The test is annotated with various `// CHECK` comments to
check the generated code. See the [FileCheck] documentation for a tutorial and
more information.
See also the [assembly tests](#assembly-tests) for a similar set of tests.
By default, codegen tests will have `//@ needs-target-std` *implied* (that the
target needs to support std), *unless* the `#![no_std]`/`#![no_core]` attribute
was specified in the test source. You can override this behavior and explicitly
write `//@ needs-target-std` to only run the test when target supports std, even
if the test is `#![no_std]`/`#![no_core]`.
If you need to work with `#![no_std]` cross-compiling tests, consult the
[`minicore` test auxiliary](./minicore.md) chapter.

View file

@ -200,6 +200,8 @@ The following directives will check rustc build settings and target settings:
on `wasm32-unknown-unknown` target because the target does not support the
`proc-macro` crate type.
- `needs-target-std` — ignores if target platform does not have std support.
- See also [`#![no_std]`/`#![no_core]` and implied `needs-target-std` for
codegen tests](./compiletest.md#codegen-tests).
- `ignore-backends` — ignores the listed backends, separated by whitespace characters.
Please note
that this directive can be overriden with the `--bypass-ignore-backends=[BACKEND]` command line

View file

@ -206,7 +206,7 @@ pub(crate) struct TestProps {
pub add_minicore: bool,
/// Add these flags to the build of `minicore`.
pub minicore_compile_flags: Vec<String>,
/// Whether line annotatins are required for the given error kind.
/// Whether line annotations are required for the given error kind.
pub dont_require_annotations: HashSet<ErrorKind>,
/// Whether pretty printers should be disabled in gdb.
pub disable_gdb_pretty_printers: bool,
@ -600,6 +600,19 @@ fn iter_directives(
}
}
// Note: affects all codegen test suites under test mode `codegen`, e.g. `codegen-llvm`.
//
// Codegen tests automatically receive implied `//@ needs-target-std`, unless
// `#![no_std]`/`#![no_core]` attribute was explicitly seen. The rationale is basically to avoid
// having to manually maintain a bunch of `//@ needs-target-std` directives esp. for targets
// tested/built out-of-tree.
if mode == TestMode::Codegen && !file_directives.has_explicit_no_std_core_attribute {
let implied_needs_target_std_line =
line_directive(testfile, LineNumber::ZERO, "//@ needs-target-std")
.expect("valid `needs-target-std` directive line");
it(&implied_needs_target_std_line);
}
for directive_line in &file_directives.lines {
it(directive_line);
}

View file

@ -6,20 +6,37 @@ use crate::directives::line::{DirectiveLine, line_directive};
pub(crate) struct FileDirectives<'a> {
pub(crate) path: &'a Utf8Path,
pub(crate) lines: Vec<DirectiveLine<'a>>,
/// Whether the test source file contains an explicit `#![no_std]`/`#![no_core]` attribute.
pub(crate) has_explicit_no_std_core_attribute: bool,
}
impl<'a> FileDirectives<'a> {
pub(crate) fn from_file_contents(path: &'a Utf8Path, file_contents: &'a str) -> Self {
let mut lines = vec![];
let mut has_explicit_no_std_core_attribute = false;
for (line_number, ln) in LineNumber::enumerate().zip(file_contents.lines()) {
let ln = ln.trim();
// Perform a naive check for lines starting with `#![no_std]`/`#![no_core]`, which
// suppresses the implied `//@ needs-target-std` in codegen tests. This ignores
// occurrences in ordinary comments.
//
// This check is imperfect in some edge cases, but we can generally trust our own test
// suite to not hit those edge cases (e.g. `#![no_std]`/`#![no_core]` in multi-line
// comments or string literals). Tests can write `//@ needs-target-std` manually if
// needed.
if ln.starts_with("#![no_std]") || ln.starts_with("#![no_core]") {
has_explicit_no_std_core_attribute = true;
continue;
}
if let Some(directive_line) = line_directive(path, line_number, ln) {
lines.push(directive_line);
}
}
Self { path, lines }
Self { path, lines, has_explicit_no_std_core_attribute }
}
}

View file

@ -105,6 +105,7 @@ fn test_parse_normalize_rule() {
#[derive(Default)]
struct ConfigBuilder {
mode: Option<String>,
suite: Option<String>,
channel: Option<String>,
edition: Option<Edition>,
host: Option<String>,
@ -126,6 +127,11 @@ impl ConfigBuilder {
self
}
fn suite(&mut self, s: &str) -> &mut Self {
self.suite = Some(s.to_owned());
self
}
fn channel(&mut self, s: &str) -> &mut Self {
self.channel = Some(s.to_owned());
self
@ -196,7 +202,8 @@ impl ConfigBuilder {
"compiletest",
"--mode",
self.mode.as_deref().unwrap_or("ui"),
"--suite=ui",
"--suite",
self.suite.as_deref().unwrap_or("ui"),
"--compile-lib-path=",
"--run-lib-path=",
"--python=",
@ -1019,6 +1026,93 @@ fn test_needs_target_std() {
assert!(!check_ignore(&config, "//@ needs-target-std"));
}
#[test]
fn implied_needs_target_std() {
let config = cfg().mode("codegen").suite("codegen-llvm").target("x86_64-unknown-none").build();
// Implied `needs-target-std` due to no `#![no_std]`/`#![no_core]`.
assert!(check_ignore(&config, ""));
assert!(check_ignore(&config, "//@ needs-target-std"));
assert!(!check_ignore(&config, "#![no_std]"));
assert!(!check_ignore(&config, "#![no_core]"));
// Make sure that `//@ needs-target-std` takes precedence.
assert!(check_ignore(
&config,
r#"
//@ needs-target-std
#![no_std]
"#
));
assert!(check_ignore(
&config,
r#"
//@ needs-target-std
#![no_core]
"#
));
let config =
cfg().mode("codegen").suite("codegen-llvm").target("x86_64-unknown-linux-gnu").build();
assert!(!check_ignore(&config, ""));
assert!(!check_ignore(&config, "//@ needs-target-std"));
assert!(!check_ignore(&config, "#![no_std]"));
assert!(!check_ignore(&config, "#![no_core]"));
assert!(!check_ignore(
&config,
r#"
//@ needs-target-std
#![no_std]
"#
));
assert!(!check_ignore(
&config,
r#"
//@ needs-target-std
#![no_core]
"#
));
let config = cfg().mode("ui").suite("ui").target("x86_64-unknown-none").build();
// The implied `//@ needs-target-std` is only applicable for mode=codegen tests.
assert!(!check_ignore(&config, ""));
assert!(check_ignore(&config, "//@ needs-target-std"));
assert!(!check_ignore(&config, "#![no_std]"));
assert!(!check_ignore(&config, "#![no_core]"));
assert!(check_ignore(
&config,
r#"
//@ needs-target-std
#![no_std]
"#
));
assert!(check_ignore(
&config,
r#"
//@ needs-target-std
#![no_core]
"#
));
let config = cfg().mode("ui").suite("ui").target("x86_64-unknown-linux-gnu").build();
assert!(!check_ignore(&config, ""));
assert!(!check_ignore(&config, "//@ needs-target-std"));
assert!(!check_ignore(&config, "#![no_std]"));
assert!(!check_ignore(&config, "#![no_core]"));
assert!(!check_ignore(
&config,
r#"
//@ needs-target-std
#![no_std]
"#
));
assert!(!check_ignore(
&config,
r#"
//@ needs-target-std
#![no_core]
"#
));
}
fn parse_edition_range(line: &str) -> Option<EditionRange> {
let config = cfg().build();