Skip to content

Commit 53cfc10

Browse files
authored
Rollup merge of #131679 - Zalathar:ct-docs, r=jieyouxu
compiletest: Document various parts of compiletest's `lib.rs` This adds some much-needed comments to many of the items in compiletest's `lib.rs`, giving a better overview of how a compiletest invocation actually proceeds. To make review easier I have refrained from renames or functional changes, though I couldn't resist getting rid of one obviously-redundant `Arc::clone` in `make_test_closure`.
2 parents dbb0581 + cec5986 commit 53cfc10

File tree

1 file changed

+114
-13
lines changed
  • src/tools/compiletest/src

1 file changed

+114
-13
lines changed

Diff for: src/tools/compiletest/src/lib.rs

+114-13
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ use crate::common::{
4343
use crate::header::HeadersCache;
4444
use crate::util::logv;
4545

46+
/// Creates the `Config` instance for this invocation of compiletest.
47+
///
48+
/// The config mostly reflects command-line arguments, but there might also be
49+
/// some code here that inspects environment variables or even runs executables
50+
/// (e.g. when discovering debugger versions).
4651
pub fn parse_config(args: Vec<String>) -> Config {
4752
let mut opts = Options::new();
4853
opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
@@ -413,6 +418,7 @@ pub fn opt_str2(maybestr: Option<String>) -> String {
413418
}
414419
}
415420

421+
/// Called by `main` after the config has been parsed.
416422
pub fn run_tests(config: Arc<Config>) {
417423
// If we want to collect rustfix coverage information,
418424
// we first make sure that the coverage file does not exist.
@@ -454,6 +460,8 @@ pub fn run_tests(config: Arc<Config>) {
454460
configs.push(config.clone());
455461
};
456462

463+
// Discover all of the tests in the test suite directory, and build a libtest
464+
// structure for each test (or each revision of a multi-revision test).
457465
let mut tests = Vec::new();
458466
for c in configs {
459467
let mut found_paths = HashSet::new();
@@ -463,7 +471,12 @@ pub fn run_tests(config: Arc<Config>) {
463471

464472
tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
465473

474+
// Delegate to libtest to filter and run the big list of structures created
475+
// during test discovery. When libtest decides to run a test, it will invoke
476+
// the corresponding closure created by `make_test_closure`.
466477
let res = test::run_tests_console(&opts, tests);
478+
479+
// Check the outcome reported by libtest.
467480
match res {
468481
Ok(true) => {}
469482
Ok(false) => {
@@ -532,6 +545,11 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
532545
}
533546
}
534547

548+
/// Creates libtest structures for every test/revision in the test suite directory.
549+
///
550+
/// This always inspects _all_ test files in the suite (e.g. all 17k+ ui tests),
551+
/// regardless of whether any filters/tests were specified on the command-line,
552+
/// because filtering is handled later by libtest.
535553
pub fn make_tests(
536554
config: Arc<Config>,
537555
tests: &mut Vec<test::TestDescAndFn>,
@@ -610,10 +628,17 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
610628
stamp
611629
}
612630

631+
/// Returns a list of modified/untracked test files that should be run when
632+
/// the `--only-modified` flag is in use.
633+
///
634+
/// (Might be inaccurate in some cases.)
613635
fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
636+
// If `--only-modified` wasn't passed, the list of modified tests won't be
637+
// used for anything, so avoid some work and just return an empty list.
614638
if !config.only_modified {
615639
return Ok(vec![]);
616640
}
641+
617642
let files =
618643
get_git_modified_files(&config.git_config(), Some(dir), &vec!["rs", "stderr", "fixed"])?
619644
.unwrap_or(vec![]);
@@ -634,6 +659,8 @@ fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
634659
Ok(full_paths)
635660
}
636661

662+
/// Recursively scans a directory to find test files and create test structures
663+
/// that will be handed over to libtest.
637664
fn collect_tests_from_dir(
638665
config: Arc<Config>,
639666
cache: &HeadersCache,
@@ -650,6 +677,8 @@ fn collect_tests_from_dir(
650677
return Ok(());
651678
}
652679

680+
// For run-make tests, a "test file" is actually a directory that contains
681+
// an `rmake.rs` or `Makefile`"
653682
if config.mode == Mode::RunMake {
654683
if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() {
655684
return Err(io::Error::other(
@@ -663,6 +692,7 @@ fn collect_tests_from_dir(
663692
relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
664693
};
665694
tests.extend(make_test(config, cache, &paths, inputs, poisoned));
695+
// This directory is a test, so don't try to find other tests inside it.
666696
return Ok(());
667697
}
668698
}
@@ -677,22 +707,27 @@ fn collect_tests_from_dir(
677707
fs::create_dir_all(&build_dir).unwrap();
678708

679709
// Add each `.rs` file as a test, and recurse further on any
680-
// subdirectories we find, except for `aux` directories.
710+
// subdirectories we find, except for `auxiliary` directories.
681711
// FIXME: this walks full tests tree, even if we have something to ignore
682712
// use walkdir/ignore like in tidy?
683713
for file in fs::read_dir(dir)? {
684714
let file = file?;
685715
let file_path = file.path();
686716
let file_name = file.file_name();
717+
687718
if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) {
719+
// We found a test file, so create the corresponding libtest structures.
688720
debug!("found test file: {:?}", file_path.display());
721+
722+
// Record the stem of the test file, to check for overlaps later.
689723
let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
690724
found_paths.insert(rel_test_path);
725+
691726
let paths =
692727
TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
693-
694728
tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned))
695729
} else if file_path.is_dir() {
730+
// Recurse to find more tests in a subdirectory.
696731
let relative_file_path = relative_dir_path.join(file.file_name());
697732
if &file_name != "auxiliary" {
698733
debug!("found directory: {:?}", file_path.display());
@@ -728,13 +763,18 @@ pub fn is_test(file_name: &OsString) -> bool {
728763
!invalid_prefixes.iter().any(|p| file_name.starts_with(p))
729764
}
730765

766+
/// For a single test file, creates one or more test structures (one per revision)
767+
/// that can be handed over to libtest to run, possibly in parallel.
731768
fn make_test(
732769
config: Arc<Config>,
733770
cache: &HeadersCache,
734771
testpaths: &TestPaths,
735772
inputs: &Stamp,
736773
poisoned: &mut bool,
737774
) -> Vec<test::TestDescAndFn> {
775+
// For run-make tests, each "test file" is actually a _directory_ containing
776+
// an `rmake.rs` or `Makefile`. But for the purposes of directive parsing,
777+
// we want to look at that recipe file, not the directory itself.
738778
let test_path = if config.mode == Mode::RunMake {
739779
if testpaths.file.join("rmake.rs").exists() && testpaths.file.join("Makefile").exists() {
740780
panic!("run-make tests cannot have both `rmake.rs` and `Makefile`");
@@ -750,45 +790,66 @@ fn make_test(
750790
} else {
751791
PathBuf::from(&testpaths.file)
752792
};
793+
794+
// Scan the test file to discover its revisions, if any.
753795
let early_props = EarlyProps::from_file(&config, &test_path);
754796

755-
// Incremental tests are special, they inherently cannot be run in parallel.
756-
// `runtest::run` will be responsible for iterating over revisions.
797+
// Normally we create one libtest structure per revision, with two exceptions:
798+
// - If a test doesn't use revisions, create a dummy revision (None) so that
799+
// the test can still run.
800+
// - Incremental tests inherently can't run their revisions in parallel, so
801+
// we treat them like non-revisioned tests here. Incremental revisions are
802+
// handled internally by `runtest::run` instead.
757803
let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
758804
vec![None]
759805
} else {
760806
early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
761807
};
762808

809+
// For each revision (or the sole dummy revision), create and return a
810+
// `test::TestDescAndFn` that can be handed over to libtest.
763811
revisions
764812
.into_iter()
765813
.map(|revision| {
814+
// Create a test name and description to hand over to libtest.
766815
let src_file =
767816
std::fs::File::open(&test_path).expect("open test file to parse ignores");
768817
let test_name = crate::make_test_name(&config, testpaths, revision);
818+
// Create a libtest description for the test/revision.
819+
// This is where `ignore-*`/`only-*`/`needs-*` directives are handled,
820+
// because they need to set the libtest ignored flag.
769821
let mut desc = make_test_description(
770822
&config, cache, test_name, &test_path, src_file, revision, poisoned,
771823
);
772-
// Ignore tests that already run and are up to date with respect to inputs.
824+
825+
// If a test's inputs haven't changed since the last time it ran,
826+
// mark it as ignored so that libtest will skip it.
773827
if !config.force_rerun
774828
&& is_up_to_date(&config, testpaths, &early_props, revision, inputs)
775829
{
776830
desc.ignore = true;
777831
// Keep this in sync with the "up-to-date" message detected by bootstrap.
778832
desc.ignore_message = Some("up-to-date");
779833
}
780-
test::TestDescAndFn {
781-
desc,
782-
testfn: make_test_closure(config.clone(), testpaths, revision),
783-
}
834+
835+
// Create the callback that will run this test/revision when libtest calls it.
836+
let testfn = make_test_closure(config.clone(), testpaths, revision);
837+
838+
test::TestDescAndFn { desc, testfn }
784839
})
785840
.collect()
786841
}
787842

843+
/// The path of the `stamp` file that gets created or updated whenever a
844+
/// particular test completes successfully.
788845
fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
789846
output_base_dir(config, testpaths, revision).join("stamp")
790847
}
791848

849+
/// Returns a list of files that, if modified, would cause this test to no
850+
/// longer be up-to-date.
851+
///
852+
/// (Might be inaccurate in some cases.)
792853
fn files_related_to_test(
793854
config: &Config,
794855
testpaths: &TestPaths,
@@ -824,53 +885,71 @@ fn files_related_to_test(
824885
related
825886
}
826887

888+
/// Checks whether a particular test/revision is "up-to-date", meaning that no
889+
/// relevant files/settings have changed since the last time the test succeeded.
890+
///
891+
/// (This is not very reliable in some circumstances, so the `--force-rerun`
892+
/// flag can be used to ignore up-to-date checking and always re-run tests.)
827893
fn is_up_to_date(
828894
config: &Config,
829895
testpaths: &TestPaths,
830896
props: &EarlyProps,
831897
revision: Option<&str>,
832-
inputs: &Stamp,
898+
inputs: &Stamp, // Last-modified timestamp of the compiler, compiletest etc
833899
) -> bool {
834900
let stamp_name = stamp(config, testpaths, revision);
835-
// Check hash.
901+
// Check the config hash inside the stamp file.
836902
let contents = match fs::read_to_string(&stamp_name) {
837903
Ok(f) => f,
838904
Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
905+
// The test hasn't succeeded yet, so it is not up-to-date.
839906
Err(_) => return false,
840907
};
841908
let expected_hash = runtest::compute_stamp_hash(config);
842909
if contents != expected_hash {
910+
// Some part of compiletest configuration has changed since the test
911+
// last succeeded, so it is not up-to-date.
843912
return false;
844913
}
845914

846-
// Check timestamps.
915+
// Check the timestamp of the stamp file against the last modified time
916+
// of all files known to be relevant to the test.
847917
let mut inputs = inputs.clone();
848918
for path in files_related_to_test(config, testpaths, props, revision) {
849919
inputs.add_path(&path);
850920
}
851921

922+
// If no relevant files have been modified since the stamp file was last
923+
// written, the test is up-to-date.
852924
inputs < Stamp::from_path(&stamp_name)
853925
}
854926

927+
/// The maximum of a set of file-modified timestamps.
855928
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
856929
struct Stamp {
857930
time: SystemTime,
858931
}
859932

860933
impl Stamp {
934+
/// Creates a timestamp holding the last-modified time of the specified file.
861935
fn from_path(path: &Path) -> Self {
862936
let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
863937
stamp.add_path(path);
864938
stamp
865939
}
866940

941+
/// Updates this timestamp to the last-modified time of the specified file,
942+
/// if it is later than the currently-stored timestamp.
867943
fn add_path(&mut self, path: &Path) {
868944
let modified = fs::metadata(path)
869945
.and_then(|metadata| metadata.modified())
870946
.unwrap_or(SystemTime::UNIX_EPOCH);
871947
self.time = self.time.max(modified);
872948
}
873949

950+
/// Updates this timestamp to the most recent last-modified time of all files
951+
/// recursively contained in the given directory, if it is later than the
952+
/// currently-stored timestamp.
874953
fn add_dir(&mut self, path: &Path) {
875954
for entry in WalkDir::new(path) {
876955
let entry = entry.unwrap();
@@ -886,6 +965,7 @@ impl Stamp {
886965
}
887966
}
888967

968+
/// Creates a name for this test/revision that can be handed over to libtest.
889969
fn make_test_name(
890970
config: &Config,
891971
testpaths: &TestPaths,
@@ -914,20 +994,41 @@ fn make_test_name(
914994
))
915995
}
916996

997+
/// Creates a callback for this test/revision that libtest will call when it
998+
/// decides to actually run the underlying test.
917999
fn make_test_closure(
9181000
config: Arc<Config>,
9191001
testpaths: &TestPaths,
9201002
revision: Option<&str>,
9211003
) -> test::TestFn {
922-
let config = config.clone();
9231004
let testpaths = testpaths.clone();
9241005
let revision = revision.map(str::to_owned);
1006+
1007+
// This callback is the link between compiletest's test discovery code,
1008+
// and the parts of compiletest that know how to run an individual test.
9251009
test::DynTestFn(Box::new(move || {
9261010
runtest::run(config, &testpaths, revision.as_deref());
9271011
Ok(())
9281012
}))
9291013
}
9301014

1015+
/// Checks that test discovery didn't find any tests whose name stem is a prefix
1016+
/// of some other tests's name.
1017+
///
1018+
/// For example, suppose the test suite contains these two test files:
1019+
/// - `tests/rustdoc/primitive.rs`
1020+
/// - `tests/rustdoc/primitive/no_std.rs`
1021+
///
1022+
/// The test runner might put the output from those tests in these directories:
1023+
/// - `$build/test/rustdoc/primitive/`
1024+
/// - `$build/test/rustdoc/primitive/no_std/`
1025+
///
1026+
/// Because one output path is a subdirectory of the other, the two tests might
1027+
/// interfere with each other in unwanted ways, especially if the test runner
1028+
/// decides to delete test output directories to clean them between runs.
1029+
/// To avoid problems, we forbid test names from overlapping in this way.
1030+
///
1031+
/// See <https://github.com/rust-lang/rust/pull/109509> for more context.
9311032
fn check_overlapping_tests(found_paths: &HashSet<PathBuf>) {
9321033
let mut collisions = Vec::new();
9331034
for path in found_paths {

0 commit comments

Comments
 (0)