Skip to content

Commit bf870c6

Browse files
authored
Rollup merge of rust-lang#153801 - ywxt:parallel-test, r=petrochenkov
Add the option to run UI tests with the parallel frontend This PR adds two arguments for tests: 1. `--parallel-frontend-threads`: specify `-Zthread` to compile test case (currently UI tests only) 2. `--iteration-count`: the number of times to run each test Also, due to the non-deterministic diagnostic orders and cycle errors, this PR adds the directive `//@ ignore-parallel-frontend` to ignore tests with cycle error when the parallel-frontend is enabled (by `--parallel-frontend-threads`) and enables `//@ compare-output-by-lines` by default. Context: [#t-compiler/parallel-rustc > Add the parallel front-end test suite @ 💬](https://rust-lang.zulipchat.com/#narrow/channel/187679-t-compiler.2Fparallel-rustc/topic/Add.20the.20parallel.20front-end.20test.20suite/near/578781369) This PR should work with rust-lang#153797 together.
2 parents b711f95 + 6a0ce16 commit bf870c6

110 files changed

Lines changed: 289 additions & 194 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/doc/rustc-dev-guide/src/tests/compiletest.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,3 +836,20 @@ In CI, compare modes are only used in one Linux builder, and only with the follo
836836
Note that compare modes are separate to [revisions](#revisions).
837837
All revisions are tested when running `./x test tests/ui`, however compare-modes must be
838838
manually run individually via the `--compare-mode` flag.
839+
840+
## Parallel frontend
841+
842+
Compiletest can be run with the `--parallel-frontend-threads` flag to run the compiler in parallel mode.
843+
This can be used to check that the compiler produces the same output in parallel mode as in non-parallel mode, and to check for any issues that might arise in parallel mode.
844+
845+
To run the tests in parallel mode, you need to pass the `--parallel-frontend-threads` CLI flag:
846+
847+
```bash
848+
./x test tests/ui -- --parallel-frontend-threads=N --iteration-count=M
849+
```
850+
851+
Where `N` is the number of threads to use for the parallel frontend, and `M` is the number of times to run each test in parallel mode (to increase the chances of catching any non-determinism).
852+
853+
Also, when running with `--parallel-frontend-threads`, the `compare-output-by-lines` directive would be implied for all tests, since the output from the parallel frontend can be non-deterministic in terms of the order of lines.
854+
855+
The parallel frontend is available in UI tests only at the moment, and is not currently supported in other test suites.

src/doc/rustc-dev-guide/src/tests/directives.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Some examples of `X` in `ignore-X` or `only-X`:
148148
- When [remote testing] is used: `remote`
149149
- When particular debuggers are being tested: `cdb`, `gdb`, `lldb`
150150
- When particular debugger versions are matched: `ignore-gdb-version`
151+
- When the [parallel frontend] is enabled: `ignore-parallel-frontend`
151152
- Specific [compare modes]: `compare-mode-polonius`, `compare-mode-chalk`,
152153
`compare-mode-split-dwarf`, `compare-mode-split-dwarf-single`
153154
- The two different test modes used by coverage tests:
@@ -233,6 +234,7 @@ The following directives will check LLVM support:
233234
See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for ignoring debuggers.
234235

235236
[remote testing]: running.md#running-tests-on-a-remote-machine
237+
[parallel frontend]: compiletest.md#parallel-frontend
236238
[compare modes]: ui.md#compare-modes
237239
[`x86_64-gnu-debug`]: https://github.com/rust-lang/rust/blob/ab3dba92db355b8d97db915a2dca161a117e959c/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile#L32
238240
[`aarch64-gnu-debug`]: https://github.com/rust-lang/rust/blob/20c909ff9cdd88d33768a4ddb8952927a675b0ad/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile#L32

src/tools/compiletest/src/common.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,9 +721,17 @@ pub struct Config {
721721
///
722722
/// This is forwarded from bootstrap's `jobs` configuration.
723723
pub jobs: u32,
724+
725+
/// Number of parallel threads to use for the frontend when building test artifacts.
726+
pub parallel_frontend_threads: u32,
727+
/// Number of times to execute each test.
728+
pub iteration_count: u32,
724729
}
725730

726731
impl Config {
732+
pub const DEFAULT_PARALLEL_FRONTEND_THREADS: u32 = 1;
733+
pub const DEFAULT_ITERATION_COUNT: u32 = 1;
734+
727735
/// FIXME: this run scheme is... confusing.
728736
pub fn run_enabled(&self) -> bool {
729737
self.run.unwrap_or_else(|| {
@@ -834,6 +842,17 @@ impl Config {
834842
|| self.target_cfg().os == "emscripten";
835843
!unsupported_target
836844
}
845+
846+
/// Whether the parallel frontend is enabled,
847+
/// which is the case when `parallel_frontend_threads` is not set to `1`.
848+
///
849+
/// - `0` means auto-detect: use the number of available hardware threads on the host.
850+
/// But we treat it as the parallel frontend being enabled in this case.
851+
/// - `1` means single-threaded (parallel frontend disabled).
852+
/// - `>1` means an explicitly configured thread count.
853+
pub fn parallel_frontend_enabled(&self) -> bool {
854+
self.parallel_frontend_threads != 1
855+
}
837856
}
838857

839858
/// Known widths of `target_has_atomic`.

src/tools/compiletest/src/directives.rs

Lines changed: 93 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl EarlyProps {
6767
let mut props = EarlyProps::default();
6868

6969
iter_directives(
70-
config.mode,
70+
config,
7171
file_directives,
7272
// (dummy comment to force args into vertical layout)
7373
&mut |ln: &DirectiveLine<'_>| {
@@ -362,7 +362,7 @@ impl TestProps {
362362
let file_directives = FileDirectives::from_file_contents(testfile, &file_contents);
363363

364364
iter_directives(
365-
config.mode,
365+
config,
366366
&file_directives,
367367
// (dummy comment to force args into vertical layout)
368368
&mut |ln: &DirectiveLine<'_>| {
@@ -574,43 +574,51 @@ fn check_directive<'a>(
574574
}
575575

576576
fn iter_directives(
577-
mode: TestMode,
577+
config: &Config,
578578
file_directives: &FileDirectives<'_>,
579579
it: &mut dyn FnMut(&DirectiveLine<'_>),
580580
) {
581581
let testfile = file_directives.path;
582582

583-
// Coverage tests in coverage-run mode always have these extra directives, without needing to
584-
// specify them manually in every test file.
585-
//
586-
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
587-
if mode == TestMode::CoverageRun {
588-
let extra_directives: &[&str] = &[
589-
"//@ needs-profiler-runtime",
590-
// FIXME(pietroalbini): this test currently does not work on cross-compiled targets
591-
// because remote-test is not capable of sending back the *.profraw files generated by
592-
// the LLVM instrumentation.
593-
"//@ ignore-cross-compile",
594-
];
595-
// Process the extra implied directives, with a dummy line number of 0.
596-
for directive_str in extra_directives {
597-
let directive_line = line_directive(testfile, LineNumber::ZERO, directive_str)
598-
.unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
599-
it(&directive_line);
583+
let extra_directives = match config.mode {
584+
TestMode::CoverageRun => {
585+
// Coverage tests in coverage-run mode always have these extra directives, without needing to
586+
// specify them manually in every test file.
587+
//
588+
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
589+
vec![
590+
"//@ needs-profiler-runtime",
591+
// FIXME(pietroalbini): this test currently does not work on cross-compiled targets
592+
// because remote-test is not capable of sending back the *.profraw files generated by
593+
// the LLVM instrumentation.
594+
"//@ ignore-cross-compile",
595+
]
596+
}
597+
TestMode::Codegen if !file_directives.has_explicit_no_std_core_attribute => {
598+
// Note: affects all codegen test suites under test mode `codegen`, e.g. `codegen-llvm`.
599+
//
600+
// Codegen tests automatically receive implied `//@ needs-target-std`, unless
601+
// `#![no_std]`/`#![no_core]` attribute was explicitly seen. The rationale is basically to avoid
602+
// having to manually maintain a bunch of `//@ needs-target-std` directives esp. for targets
603+
// tested/built out-of-tree.
604+
vec!["//@ needs-target-std"]
605+
}
606+
TestMode::Ui if config.parallel_frontend_enabled() => {
607+
// UI tests in parallel-frontend mode always have this extra directive, without needing to
608+
// specify it manually in every test file.
609+
vec!["//@ compare-output-by-lines"]
600610
}
601-
}
602611

603-
// Note: affects all codegen test suites under test mode `codegen`, e.g. `codegen-llvm`.
604-
//
605-
// Codegen tests automatically receive implied `//@ needs-target-std`, unless
606-
// `#![no_std]`/`#![no_core]` attribute was explicitly seen. The rationale is basically to avoid
607-
// having to manually maintain a bunch of `//@ needs-target-std` directives esp. for targets
608-
// tested/built out-of-tree.
609-
if mode == TestMode::Codegen && !file_directives.has_explicit_no_std_core_attribute {
610-
let implied_needs_target_std_line =
611-
line_directive(testfile, LineNumber::ZERO, "//@ needs-target-std")
612-
.expect("valid `needs-target-std` directive line");
613-
it(&implied_needs_target_std_line);
612+
_ => {
613+
// No extra directives for other test modes.
614+
vec![]
615+
}
616+
};
617+
618+
for directive_str in extra_directives {
619+
let directive_line = line_directive(testfile, LineNumber::ZERO, directive_str)
620+
.unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
621+
it(&directive_line);
614622
}
615623

616624
for directive_line in &file_directives.lines {
@@ -951,55 +959,52 @@ pub(crate) fn make_test_description(
951959
let mut should_fail = false;
952960

953961
// Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives.
954-
iter_directives(
955-
config.mode,
956-
file_directives,
957-
&mut |ln @ &DirectiveLine { line_number, .. }| {
958-
if !ln.applies_to_test_revision(test_revision) {
959-
return;
960-
}
962+
iter_directives(config, file_directives, &mut |ln @ &DirectiveLine { line_number, .. }| {
963+
if !ln.applies_to_test_revision(test_revision) {
964+
return;
965+
}
966+
967+
// Parse `aux-*` directives, for use by up-to-date checks.
968+
parse_and_update_aux(config, ln, aux_props);
961969

962-
// Parse `aux-*` directives, for use by up-to-date checks.
963-
parse_and_update_aux(config, ln, aux_props);
964-
965-
macro_rules! decision {
966-
($e:expr) => {
967-
match $e {
968-
IgnoreDecision::Ignore { reason } => {
969-
ignore = true;
970-
ignore_message = Some(reason.into());
971-
}
972-
IgnoreDecision::Error { message } => {
973-
error!("{path}:{line_number}: {message}");
974-
*poisoned = true;
975-
return;
976-
}
977-
IgnoreDecision::Continue => {}
970+
macro_rules! decision {
971+
($e:expr) => {
972+
match $e {
973+
IgnoreDecision::Ignore { reason } => {
974+
ignore = true;
975+
ignore_message = Some(reason.into());
978976
}
979-
};
980-
}
977+
IgnoreDecision::Error { message } => {
978+
error!("{path}:{line_number}: {message}");
979+
*poisoned = true;
980+
return;
981+
}
982+
IgnoreDecision::Continue => {}
983+
}
984+
};
985+
}
981986

982-
decision!(cfg::handle_ignore(&cache.cfg_conditions, ln));
983-
decision!(cfg::handle_only(&cache.cfg_conditions, ln));
984-
decision!(needs::handle_needs(&cache.needs, config, ln));
985-
decision!(ignore_llvm(config, ln));
986-
decision!(ignore_backends(config, ln));
987-
decision!(needs_backends(config, ln));
988-
decision!(ignore_cdb(config, ln));
989-
decision!(ignore_gdb(config, ln));
990-
decision!(ignore_lldb(config, ln));
991-
992-
if config.target == "wasm32-unknown-unknown"
993-
&& config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
994-
{
995-
decision!(IgnoreDecision::Ignore {
996-
reason: "ignored on WASM as the run results cannot be checked there".into(),
997-
});
998-
}
987+
decision!(cfg::handle_ignore(&cache.cfg_conditions, ln));
988+
decision!(cfg::handle_only(&cache.cfg_conditions, ln));
989+
decision!(needs::handle_needs(&cache.needs, config, ln));
990+
decision!(ignore_llvm(config, ln));
991+
decision!(ignore_backends(config, ln));
992+
decision!(needs_backends(config, ln));
993+
decision!(ignore_cdb(config, ln));
994+
decision!(ignore_gdb(config, ln));
995+
decision!(ignore_lldb(config, ln));
996+
decision!(ignore_parallel_frontend(config, ln));
997+
998+
if config.target == "wasm32-unknown-unknown"
999+
&& config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
1000+
{
1001+
decision!(IgnoreDecision::Ignore {
1002+
reason: "ignored on WASM as the run results cannot be checked there".into(),
1003+
});
1004+
}
9991005

1000-
should_fail |= config.parse_name_directive(ln, "should-fail");
1001-
},
1002-
);
1006+
should_fail |= config.parse_name_directive(ln, "should-fail");
1007+
});
10031008

10041009
// The `should-fail` annotation doesn't apply to pretty tests,
10051010
// since we run the pretty printer across all tests by default.
@@ -1270,6 +1275,17 @@ fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
12701275
IgnoreDecision::Continue
12711276
}
12721277

1278+
fn ignore_parallel_frontend(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1279+
if config.parallel_frontend_enabled()
1280+
&& config.parse_name_directive(line, "ignore-parallel-frontend")
1281+
{
1282+
return IgnoreDecision::Ignore {
1283+
reason: "ignored when the parallel frontend is enabled".into(),
1284+
};
1285+
}
1286+
IgnoreDecision::Continue
1287+
}
1288+
12731289
enum IgnoreDecision {
12741290
Ignore { reason: String },
12751291
Continue,

src/tools/compiletest/src/directives/cfg.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const EXTERNAL_IGNORES_LIST: &[&str] = &[
1111
"ignore-backends",
1212
"ignore-gdb-version",
1313
"ignore-llvm-version",
14+
"ignore-parallel-frontend",
1415
"ignore-pass",
1516
// tidy-alphabetical-end
1617
];

src/tools/compiletest/src/directives/directive_names.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
101101
"ignore-nvptx64",
102102
"ignore-nvptx64-nvidia-cuda",
103103
"ignore-openbsd",
104+
"ignore-parallel-frontend",
104105
"ignore-pass",
105106
"ignore-powerpc",
106107
"ignore-powerpc64",

src/tools/compiletest/src/lib.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,14 @@ fn parse_config(args: Vec<String>) -> Config {
219219
"CODEGEN BACKEND [NAME | PATH]",
220220
)
221221
.optflag("", "bypass-ignore-backends", "ignore `//@ ignore-backends` directives")
222-
.reqopt("", "jobs", "number of parallel jobs bootstrap was configured with", "JOBS");
222+
.reqopt("", "jobs", "number of parallel jobs bootstrap was configured with", "JOBS")
223+
.optopt(
224+
"",
225+
"parallel-frontend-threads",
226+
"number of parallel threads to use for the frontend when building test artifacts",
227+
"THREADS_COUNT",
228+
)
229+
.optopt("", "iteration-count", "number of times to execute each test", "COUNT");
223230

224231
let (argv0, args_) = args.split_first().unwrap();
225232
if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
@@ -369,6 +376,20 @@ fn parse_config(args: Vec<String>) -> Config {
369376
None => panic!("`--jobs` is required"),
370377
};
371378

379+
let parallel_frontend_threads = match matches.opt_str("parallel-frontend-threads") {
380+
Some(threads) => {
381+
threads.parse::<u32>().expect("expected `--parallel-frontend-threads` to be an `u32`")
382+
}
383+
None => Config::DEFAULT_PARALLEL_FRONTEND_THREADS,
384+
};
385+
let iteration_count = match matches.opt_str("iteration-count") {
386+
Some(count) => {
387+
count.parse::<u32>().expect("expected `--iteration-count` to be a positive integer")
388+
}
389+
None => Config::DEFAULT_ITERATION_COUNT,
390+
};
391+
assert!(iteration_count > 0, "`--iteration-count` must be a positive integer");
392+
372393
Config {
373394
bless: matches.opt_present("bless"),
374395
fail_fast: matches.opt_present("fail-fast")
@@ -489,6 +510,9 @@ fn parse_config(args: Vec<String>) -> Config {
489510
bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
490511

491512
jobs,
513+
514+
parallel_frontend_threads,
515+
iteration_count,
492516
}
493517
}
494518

0 commit comments

Comments
 (0)