diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index aaa70bf19b2fb..ccce62bd5b0cf 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -102,6 +102,9 @@ pub struct TestProps { pub dont_check_compiler_stdout: bool, // For UI tests, allows compiler to generate arbitrary output to stderr pub dont_check_compiler_stderr: bool, + // When checking the output of stdout or stderr check + // that the lines of expected output are a subset of the actual output. + pub compare_output_lines_by_subset: bool, // Don't force a --crate-type=dylib flag on the command line // // Set this for example if you have an auxiliary test file that contains @@ -209,6 +212,7 @@ mod directives { pub const KNOWN_BUG: &'static str = "known-bug"; pub const MIR_UNIT_TEST: &'static str = "unit-test"; pub const REMAP_SRC_BASE: &'static str = "remap-src-base"; + pub const COMPARE_OUTPUT_LINES_BY_SUBSET: &'static str = "compare-output-lines-by-subset"; // This isn't a real directive, just one that is probably mistyped often pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags"; } @@ -233,6 +237,7 @@ impl TestProps { check_run_results: false, dont_check_compiler_stdout: false, dont_check_compiler_stderr: false, + compare_output_lines_by_subset: false, no_prefer_dynamic: false, pretty_expanded: false, pretty_mode: "normal".to_string(), @@ -467,6 +472,11 @@ impl TestProps { s.trim().to_string() }); config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base); + config.set_name_directive( + ln, + COMPARE_OUTPUT_LINES_BY_SUBSET, + &mut self.compare_output_lines_by_subset, + ); }); } diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 0c17ae798086d..f6597c729387e 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -3244,17 +3244,35 @@ impl<'test> TestCx<'test> { match output_kind { TestOutput::Compile => { if !self.props.dont_check_compiler_stdout { - errors += - self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout); + errors += self.compare_output( + stdout_kind, + &normalized_stdout, + &expected_stdout, + self.props.compare_output_lines_by_subset, + ); } if !self.props.dont_check_compiler_stderr { - errors += - self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr); + errors += self.compare_output( + stderr_kind, + &normalized_stderr, + &expected_stderr, + self.props.compare_output_lines_by_subset, + ); } } TestOutput::Run => { - errors += self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout); - errors += self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr); + errors += self.compare_output( + stdout_kind, + &normalized_stdout, + &expected_stdout, + self.props.compare_output_lines_by_subset, + ); + errors += self.compare_output( + stderr_kind, + &normalized_stderr, + &expected_stderr, + self.props.compare_output_lines_by_subset, + ); } } errors @@ -3338,7 +3356,12 @@ impl<'test> TestCx<'test> { ) }); - errors += self.compare_output("fixed", &fixed_code, &expected_fixed); + errors += self.compare_output( + "fixed", + &fixed_code, + &expected_fixed, + self.props.compare_output_lines_by_subset, + ); } else if !expected_fixed.is_empty() { panic!( "the `// run-rustfix` directive wasn't found but a `*.fixed` \ @@ -3796,11 +3819,38 @@ impl<'test> TestCx<'test> { } } - fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize { + fn compare_output( + &self, + kind: &str, + actual: &str, + expected: &str, + compare_output_by_lines: bool, + ) -> usize { if actual == expected { return 0; } + let tmp; + let (expected, actual): (&str, &str) = if compare_output_by_lines { + let actual_lines: HashSet<_> = actual.lines().collect(); + let expected_lines: Vec<_> = expected.lines().collect(); + let mut used = expected_lines.clone(); + used.retain(|line| actual_lines.contains(line)); + // check if `expected` contains a subset of the lines of `actual` + if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) { + return 0; + } + if expected_lines.is_empty() { + // if we have no lines to check, force a full overwite + ("", actual) + } else { + tmp = (expected_lines.join("\n"), used.join("\n")); + (&tmp.0, &tmp.1) + } + } else { + (expected, actual) + }; + if !self.config.bless { if expected.is_empty() { println!("normalized {}:\n{}\n", kind, actual);