Skip to content

Commit 5fd13ff

Browse files
authored
Merge pull request #231 from phansch/bless
Add basic `bless` support
2 parents 139dfdb + c3096be commit 5fd13ff

File tree

7 files changed

+323
-38
lines changed

7 files changed

+323
-38
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ serde = "1.0"
2525
serde_json = "1.0"
2626
serde_derive = "1.0"
2727
rustfix = "0.5"
28-
tester = "0.8"
28+
tester = "0.9"
29+
lazy_static = "1.4"
2930

3031
[target."cfg(unix)".dependencies]
3132
libc = "0.2"

src/common.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ impl fmt::Display for Mode {
102102

103103
#[derive(Clone)]
104104
pub struct Config {
105+
/// `true` to overwrite stderr/stdout/fixed files instead of complaining about changes in output.
106+
pub bless: bool,
107+
105108
/// The library paths required for running the compiler
106109
pub compile_lib_path: PathBuf,
107110

@@ -145,8 +148,8 @@ pub struct Config {
145148
/// Run ignored tests
146149
pub run_ignored: bool,
147150

148-
/// Only run tests that match this filter
149-
pub filter: Option<String>,
151+
/// Only run tests that match these filters
152+
pub filters: Vec<String>,
150153

151154
/// Exactly match the filter, rather than a substring
152155
pub filter_exact: bool,
@@ -239,6 +242,25 @@ pub struct TestPaths {
239242
pub relative_dir: PathBuf, // e.g., foo/bar
240243
}
241244

245+
/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
246+
pub fn expected_output_path(
247+
testpaths: &TestPaths,
248+
revision: Option<&str>,
249+
kind: &str,
250+
) -> PathBuf {
251+
assert!(UI_EXTENSIONS.contains(&kind));
252+
let mut parts = Vec::new();
253+
254+
if let Some(x) = revision {
255+
parts.push(x);
256+
}
257+
parts.push(kind);
258+
259+
let extension = parts.join(".");
260+
testpaths.file.with_extension(extension)
261+
}
262+
263+
pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT, UI_FIXED];
242264
pub const UI_STDERR: &str = "stderr";
243265
pub const UI_STDOUT: &str = "stdout";
244266
pub const UI_FIXED: &str = "fixed";
@@ -335,6 +357,7 @@ impl Default for Config {
335357
let platform = rustc_session::config::host_triple().to_string();
336358

337359
Config {
360+
bless: false,
338361
compile_lib_path: PathBuf::from(""),
339362
run_lib_path: PathBuf::from(""),
340363
rustc_path: PathBuf::from("rustc"),
@@ -349,7 +372,7 @@ impl Default for Config {
349372
stage_id: "stage-id".to_owned(),
350373
mode: Mode::RunPass,
351374
run_ignored: false,
352-
filter: None,
375+
filters: vec![],
353376
filter_exact: false,
354377
logfile: None,
355378
runtool: None,

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub fn run_tests(config: &Config) {
111111

112112
pub fn test_opts(config: &Config) -> test::TestOpts {
113113
test::TestOpts {
114-
filter: config.filter.clone(),
114+
filters: config.filters.clone(),
115115
filter_exact: config.filter_exact,
116116
exclude_should_panic: false,
117117
force_run_in_process: false,

src/runtest.rs

+82-33
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// except according to those terms.
1010

1111
use common::{Config, TestPaths};
12-
use common::{UI_FIXED, UI_STDERR, UI_STDOUT};
12+
use common::{expected_output_path, UI_FIXED, UI_STDERR, UI_STDOUT};
1313
use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind};
1414
use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc, CodegenUnits};
1515
use common::{Incremental, RunMake, Ui, MirOpt};
@@ -20,7 +20,7 @@ use json;
2020
use regex::Regex;
2121
use rustfix::{apply_suggestions, get_suggestions_from_json, Filter};
2222
use header::TestProps;
23-
use util::logv;
23+
use crate::util::{logv, PathBufExt};
2424

2525
use std::collections::HashMap;
2626
use std::collections::HashSet;
@@ -2167,6 +2167,18 @@ actual:\n\
21672167
// compiler flags set in the test cases:
21682168
cmd.env_remove("RUSTFLAGS");
21692169

2170+
if self.config.bless {
2171+
cmd.env("RUSTC_BLESS_TEST", "--bless");
2172+
// Assume this option is active if the environment variable is "defined", with _any_ value.
2173+
// As an example, a `Makefile` can use this option by:
2174+
//
2175+
// ifdef RUSTC_BLESS_TEST
2176+
// cp "$(TMPDIR)"/actual_something.ext expected_something.ext
2177+
// else
2178+
// $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
2179+
// endif
2180+
}
2181+
21702182
if self.config.target.contains("msvc") {
21712183
// We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
21722184
// and that `lib.exe` lives next to it.
@@ -2323,16 +2335,17 @@ actual:\n\
23232335
}
23242336

23252337
if errors > 0 {
2326-
println!("To update references, run this command from build directory:");
2338+
println!("To update references, rerun the tests and pass the `--bless` flag");
23272339
let relative_path_to_file =
2328-
self.testpaths.relative_dir
2329-
.join(self.testpaths.file.file_name().unwrap());
2330-
println!("{}/update-references.sh '{}' '{}'",
2331-
self.config.src_base.display(),
2332-
self.config.build_base.display(),
2333-
relative_path_to_file.display());
2334-
self.fatal_proc_rec(&format!("{} errors occurred comparing output.", errors),
2335-
&proc_res);
2340+
self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
2341+
println!(
2342+
"To only update this specific test, also pass `--test-args {}`",
2343+
relative_path_to_file.display(),
2344+
);
2345+
self.fatal_proc_rec(
2346+
&format!("{} errors occurred comparing output.", errors),
2347+
&proc_res,
2348+
);
23362349
}
23372350

23382351
if self.props.run_pass {
@@ -2566,11 +2579,14 @@ actual:\n\
25662579
}
25672580

25682581
fn expected_output_path(&self, kind: &str) -> PathBuf {
2569-
let extension = match self.revision {
2570-
Some(r) => format!("{}.{}", r, kind),
2571-
None => kind.to_string(),
2572-
};
2573-
self.testpaths.file.with_extension(extension)
2582+
let mut path =
2583+
expected_output_path(&self.testpaths, self.revision, kind);
2584+
2585+
if !path.exists() {
2586+
path = expected_output_path(&self.testpaths, self.revision, kind);
2587+
}
2588+
2589+
path
25742590
}
25752591

25762592
fn load_expected_output(&self, path: &Path) -> String {
@@ -2594,35 +2610,68 @@ actual:\n\
25942610
})
25952611
}
25962612

2613+
fn delete_file(&self, file: &PathBuf) {
2614+
if !file.exists() {
2615+
// Deleting a nonexistant file would error.
2616+
return;
2617+
}
2618+
if let Err(e) = fs::remove_file(file) {
2619+
self.fatal(&format!("failed to delete `{}`: {}", file.display(), e,));
2620+
}
2621+
}
2622+
25972623
fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize {
25982624
if actual == expected {
25992625
return 0;
26002626
}
26012627

2602-
println!("normalized {}:\n{}\n", kind, actual);
2603-
println!("expected {}:\n{}\n", kind, expected);
2604-
println!("diff of {}:\n", kind);
2605-
2606-
for diff in diff::lines(expected, actual) {
2607-
match diff {
2608-
diff::Result::Left(l) => println!("-{}", l),
2609-
diff::Result::Both(l, _) => println!(" {}", l),
2610-
diff::Result::Right(r) => println!("+{}", r),
2628+
if !self.config.bless {
2629+
if expected.is_empty() {
2630+
println!("normalized {}:\n{}\n", kind, actual);
2631+
} else {
2632+
println!("diff of {}:\n", kind);
2633+
for diff in diff::lines(expected, actual) {
2634+
match diff {
2635+
diff::Result::Left(l) => println!("-{}", l),
2636+
diff::Result::Both(l, _) => println!(" {}", l),
2637+
diff::Result::Right(r) => println!("+{}", r),
2638+
}
2639+
}
26112640
}
26122641
}
26132642

2614-
let output_file = self.output_base_name().with_extension(kind);
2615-
match File::create(&output_file).and_then(|mut f| f.write_all(actual.as_bytes())) {
2616-
Ok(()) => { }
2617-
Err(e) => {
2618-
self.fatal(&format!("failed to write {} to `{}`: {}",
2619-
kind, output_file.display(), e))
2643+
let output_file = self
2644+
.output_base_name()
2645+
.with_extra_extension(self.revision.unwrap_or(""))
2646+
.with_extra_extension(kind);
2647+
2648+
let mut files = vec![output_file];
2649+
if self.config.bless {
2650+
files.push(expected_output_path(
2651+
self.testpaths,
2652+
self.revision,
2653+
kind,
2654+
));
2655+
}
2656+
2657+
for output_file in &files {
2658+
if actual.is_empty() {
2659+
self.delete_file(output_file);
2660+
} else if let Err(err) = fs::write(&output_file, &actual) {
2661+
self.fatal(&format!(
2662+
"failed to write {} to `{}`: {}",
2663+
kind,
2664+
output_file.display(),
2665+
err,
2666+
));
26202667
}
26212668
}
26222669

26232670
println!("\nThe actual {0} differed from the expected {0}.", kind);
2624-
println!("Actual {} saved to {}", kind, output_file.display());
2625-
1
2671+
for output_file in files {
2672+
println!("Actual {} saved to {}", kind, output_file.display());
2673+
}
2674+
if self.config.bless { 0 } else { 1 }
26262675
}
26272676
}
26282677

src/util.rs

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use std::env;
1212
use common::Config;
13+
use std::ffi::OsStr;
14+
use std::path::PathBuf;
1315

1416
/// Conversion table from triple OS name to Rust SYSNAME
1517
const OS_TABLE: &'static [(&'static str, &'static str)] = &[
@@ -108,3 +110,23 @@ pub fn logv(config: &Config, s: String) {
108110
println!("{}", s);
109111
}
110112
}
113+
114+
pub trait PathBufExt {
115+
/// Append an extension to the path, even if it already has one.
116+
fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf;
117+
}
118+
119+
impl PathBufExt for PathBuf {
120+
fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
121+
if extension.as_ref().is_empty() {
122+
self.clone()
123+
} else {
124+
let mut fname = self.file_name().unwrap().to_os_string();
125+
if !extension.as_ref().to_str().unwrap().starts_with('.') {
126+
fname.push(".");
127+
}
128+
fname.push(extension);
129+
self.with_file_name(fname)
130+
}
131+
}
132+
}

tests/bless.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Tests for the `bless` option
2+
3+
extern crate compiletest_rs as compiletest;
4+
5+
mod test_support;
6+
use test_support::{testsuite, TestsuiteBuilder, GLOBAL_ROOT};
7+
use compiletest::Config;
8+
9+
fn setup(mode: &str) -> (Config, TestsuiteBuilder) {
10+
let builder = testsuite(mode);
11+
let mut config = Config::default();
12+
let cfg_mode = mode.parse().expect("Invalid mode");
13+
config.mode = cfg_mode;
14+
config.src_base = builder.root.clone();
15+
config.build_base = GLOBAL_ROOT.join("build_base");
16+
17+
(config, builder)
18+
}
19+
20+
#[test]
21+
fn test_bless_new_file() {
22+
let (mut config, builder) = setup("ui");
23+
config.bless = true;
24+
25+
builder.mk_file(
26+
"foobar.rs",
27+
r#"
28+
#[warn(unused_variables)]
29+
fn main() {
30+
let abc = "foobar";
31+
}
32+
"#,
33+
);
34+
compiletest::run_tests(&config);
35+
36+
// Blessing should cause the stderr to be created directly
37+
assert!(builder.file_contents("foobar.stderr").contains("unused variable"));
38+
39+
// And a second run of the tests, with blessing disabled should work just fine
40+
config.bless = false;
41+
compiletest::run_tests(&config);
42+
}
43+
44+
#[test]
45+
fn test_bless_update_file() {
46+
let (mut config, builder) = setup("ui");
47+
config.bless = true;
48+
49+
builder.mk_file(
50+
"foobar2.rs",
51+
r#"
52+
#[warn(unused_variables)]
53+
fn main() {
54+
let abc = "foobar_update";
55+
}
56+
"#,
57+
);
58+
builder.mk_file(
59+
"foobar2.stderr",
60+
r#"
61+
warning: unused variable: `abc`
62+
--> $DIR/foobar2.rs:4:27
63+
|
64+
4 | let abc = "foobar";
65+
| ^^^ help: if this is intentional, prefix it with an underscore: `_abc`
66+
|
67+
note: the lint level is defined here
68+
--> $DIR/foobar2.rs:2:26
69+
|
70+
2 | #[warn(unused_variables)]
71+
| ^^^^^^^^^^^^^^^^
72+
73+
warning: 1 warning emitted
74+
"#,
75+
);
76+
compiletest::run_tests(&config);
77+
78+
// Blessing should cause the stderr to be created directly
79+
assert!(builder.file_contents("foobar2.stderr").contains("unused variable"));
80+
assert!(builder.file_contents("foobar2.stderr").contains("foobar_update"));
81+
82+
// And a second run of the tests, with blessing disabled should work just fine
83+
config.bless = false;
84+
compiletest::run_tests(&config);
85+
}

0 commit comments

Comments
 (0)