Skip to content

Add compare-output-lines-by-subset flag to compiletest #110444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/tools/compiletest/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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";
}
Expand All @@ -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(),
Expand Down Expand Up @@ -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,
);
});
}

Expand Down
66 changes: 58 additions & 8 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3238,17 +3238,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
Expand Down Expand Up @@ -3332,7 +3350,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` \
Expand Down Expand Up @@ -3790,11 +3813,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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can get this with putting both sets of lines into HashSet and then calling https://doc.rust-lang.org/nightly/std/collections/hash_set/struct.HashSet.html#method.is_subset, while also avoiding both the - if I'm getting the math right - O(n^3) (n^2 for each set and another n for the copies as we remove lines, I think, in the worst case) retain loop.

Copy link
Member Author

@Veykril Veykril Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already n^2? For each expected line this iterates through the actual output once. That aside, using a HashSet doesn't work as it join over the vecs in line 3840 which is order dependent. Unless IndexSet is used which requires adding it as a dependency to compiletest. I suppose actual.lines() could be collected into a HashSet at least.

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);
Expand Down