Skip to content

Commit b10a1b0

Browse files
committed
Update README, add more comments, cleanup test-cargo-miri
1 parent 75053be commit b10a1b0

File tree

6 files changed

+60
-47
lines changed

6 files changed

+60
-47
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ different Miri binaries, and as such worth documenting:
282282
that the compiled `rlib`s are compatible with Miri.
283283
When set while running `cargo-miri`, it indicates that we are part of a sysroot
284284
build (for which some crates need special treatment).
285+
* `MIRI_CALLED_FROM_RUSTDOC` when set to any value tells `cargo-miri` that it is
286+
running as a child process of `rustdoc`, which invokes it twice for each doc-test
287+
and requires special treatment, most notably a check-only build before validation.
288+
This is set by `cargo-miri` itself when running as a `rustdoc`-wrapper.
285289
* `MIRI_CWD` when set to any value tells the Miri driver to change to the given
286290
directory after loading all the source files, but before commencing
287291
interpretation. This is useful if the interpreted program wants a different

cargo-miri/bin.rs

+43-32
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ fn phase_cargo_rustc(args: env::Args) {
615615
info.store(&out_filename("", ".exe"));
616616

617617
// Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
618+
// just creating the JSON file is not enough: we need to detect syntax errors,
618619
// so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
619620
if std::env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() {
620621
let mut cmd = miri();
@@ -626,8 +627,12 @@ fn phase_cargo_rustc(args: env::Args) {
626627
cmd.arg("--sysroot").arg(sysroot);
627628
}
628629

629-
// don't go into "code generation" (i.e. validation)
630-
if info.args.iter().position(|arg| arg.starts_with("--emit=")).is_none() {
630+
// ensure --emit argument for a check-only build is present
631+
if let Some(i) = info.args.iter().position(|arg| arg.starts_with("--emit=")) {
632+
// We need to make sure we're not producing a binary that overwrites the JSON file.
633+
// rustdoc should only ever pass an --emit=metadata argument for tests marked as `no_run`:
634+
assert_eq!(info.args[i], "--emit=metadata");
635+
} else {
631636
cmd.arg("--emit=dep-info,metadata");
632637
}
633638

@@ -703,6 +708,18 @@ fn phase_cargo_rustc(args: env::Args) {
703708
}
704709
}
705710

711+
fn try_forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
712+
cmd.arg("--extern"); // always forward flag, but adjust filename:
713+
let path = args.next().expect("`--extern` should be followed by a filename");
714+
if let Some(lib) = path.strip_suffix(".rlib") {
715+
// If this is an rlib, make it an rmeta.
716+
cmd.arg(format!("{}.rmeta", lib));
717+
} else {
718+
// Some other extern file (e.g. a `.so`). Forward unchanged.
719+
cmd.arg(path);
720+
}
721+
}
722+
706723
fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
707724
let verbose = std::env::var_os("MIRI_VERBOSE").is_some();
708725

@@ -740,16 +757,7 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
740757
let json_flag = "--json";
741758
while let Some(arg) = args.next() {
742759
if arg == extern_flag {
743-
cmd.arg(extern_flag); // always forward flag, but adjust filename
744-
// `--extern` is always passed as a separate argument by cargo.
745-
let next_arg = args.next().expect("`--extern` should be followed by a filename");
746-
if let Some(next_lib) = next_arg.strip_suffix(".rlib") {
747-
// If this is an rlib, make it an rmeta.
748-
cmd.arg(format!("{}.rmeta", next_lib));
749-
} else {
750-
// Some other extern file (e.g., a `.so`). Forward unchanged.
751-
cmd.arg(next_arg);
752-
}
760+
try_forward_patched_extern_arg(&mut args, &mut cmd);
753761
} else if arg.starts_with(error_format_flag) {
754762
let suffix = &arg[error_format_flag.len()..];
755763
assert!(suffix.starts_with('='));
@@ -806,38 +814,41 @@ fn phase_cargo_rustdoc(fst_arg: &str, mut args: env::Args) {
806814
// just default to a straight-forward invocation for now:
807815
let mut cmd = Command::new(OsString::from("rustdoc"));
808816

817+
// Because of the way the main function is structured, we have to take the first argument spearately
818+
// from the rest; to simplify the following argument patching loop, we'll just skip that one.
819+
// This is fine for now, because cargo will never pass an --extern argument in the first position,
820+
// but we should defensively assert that this will work.
809821
let extern_flag = "--extern";
810822
assert!(fst_arg != extern_flag);
811823
cmd.arg(fst_arg);
812824

813825
// Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
814826
while let Some(arg) = args.next() {
815827
if arg == extern_flag {
816-
cmd.arg(extern_flag); // always forward flag, but adjust filename
817-
// `--extern` is always passed as a separate argument by cargo.
818-
let next_arg = args.next().expect("`--extern` should be followed by a filename");
819-
if let Some(next_lib) = next_arg.strip_suffix(".rlib") {
820-
// If this is an rlib, make it an rmeta.
821-
cmd.arg(format!("{}.rmeta", next_lib));
822-
} else {
823-
// Some other extern file (e.g., a `.so`). Forward unchanged.
824-
cmd.arg(next_arg);
825-
}
828+
try_forward_patched_extern_arg(&mut args, &mut cmd);
826829
} else {
827830
cmd.arg(arg);
828831
}
829832
}
830833

834+
// For each doc-test, rustdoc starts two child processes: first the test is compiled,
835+
// then the produced executable is invoked. We want to reroute both of these to cargo-miri,
836+
// such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
837+
//
838+
// rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
839+
// it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
840+
// Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
841+
// to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
842+
cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
843+
844+
// The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
845+
// which are disabled by default. We first need to enable them explicitly:
831846
cmd.arg("-Z").arg("unstable-options");
832847

833848
let cargo_miri_path = std::env::current_exe().expect("current executable path invalid");
834-
cmd.arg("--test-builder").arg(&cargo_miri_path);
835-
cmd.arg("--runtool").arg(&cargo_miri_path);
849+
cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
850+
cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
836851

837-
// rustdoc passes generated code to rustc via stdin, rather than a temporary file,
838-
// so we need to let the coming invocations know to expect that
839-
cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
840-
841852
if verbose {
842853
eprintln!("[cargo-miri rustdoc] {:?}", cmd);
843854
}
@@ -861,10 +872,10 @@ fn main() {
861872
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
862873
// by the arguments alone, and we can't take from the args iterator in this case.
863874
// phase_cargo_rustdoc sets this environment variable to let us disambiguate here
864-
let invoked_as_rustc_from_rustdoc = env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some();
865-
if invoked_as_rustc_from_rustdoc {
875+
let invoked_by_rustdoc = env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some();
876+
if invoked_by_rustdoc {
866877
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
867-
// The runner is invoked as `$runtool ($runtool-arg)* output_file;
878+
// The runner is invoked as `$runtool ($runtool-arg)* output_file`;
868879
// since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
869880
// the test-builder unconditionally, we can just check the number of remaining arguments:
870881
if args.len() == 1 {
@@ -873,7 +884,7 @@ fn main() {
873884
if binary.exists() {
874885
phase_cargo_runner(binary, args);
875886
} else {
876-
show_error(format!("`cargo-miri` called with non-existing path argument `{}`; please invoke this binary through `cargo miri`", arg));
887+
show_error(format!("`cargo-miri` called with non-existing path argument `{}` in rustdoc mode; please invoke this binary through `cargo miri`", arg));
877888
}
878889
} else {
879890
phase_cargo_rustc(args);

test-cargo-miri/run-test.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def cargo_miri(cmd):
2121
args += ["--target", os.environ['MIRI_TEST_TARGET']]
2222
return args
2323

24-
def scrub_timing_info(str):
25-
return re.sub("finished in \d+\.\d\ds", "", str)
24+
def normalize_stdout(str):
25+
return re.sub("finished in \d+\.\d\ds", "finished in $TIME", str)
2626

2727
def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}):
2828
print("Testing {}...".format(name))
@@ -39,7 +39,7 @@ def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}):
3939
(stdout, stderr) = p.communicate(input=stdin)
4040
stdout = stdout.decode("UTF-8")
4141
stderr = stderr.decode("UTF-8")
42-
if p.returncode == 0 and scrub_timing_info(stdout) == scrub_timing_info(open(stdout_ref).read()) and stderr == open(stderr_ref).read():
42+
if p.returncode == 0 and normalize_stdout(stdout) == open(stdout_ref).read() and stderr == open(stderr_ref).read():
4343
# All good!
4444
return
4545
# Show output
@@ -72,40 +72,38 @@ def test_cargo_miri_run():
7272
)
7373

7474
def test_cargo_miri_test():
75-
# rustdoc is not run on foreign targets
76-
is_foreign = 'MIRI_TEST_TARGET' in os.environ
77-
rustdoc_ref = "test.stderr-empty.ref" if is_foreign else "test.stderr-rustdoc.ref"
75+
empty_ref = "test.stderr-empty.ref"
7876

7977
test("`cargo miri test`",
8078
cargo_miri("test"),
81-
"test.default.stdout.ref", rustdoc_ref,
79+
"test.default.stdout.ref", empty_ref,
8280
env={'MIRIFLAGS': "-Zmiri-seed=feed"},
8381
)
8482
test("`cargo miri test` (no isolation)",
8583
cargo_miri("test"),
86-
"test.default.stdout.ref", rustdoc_ref,
84+
"test.default.stdout.ref", empty_ref,
8785
env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
8886
)
8987
test("`cargo miri test` (raw-ptr tracking)",
9088
cargo_miri("test"),
91-
"test.default.stdout.ref", rustdoc_ref,
89+
"test.default.stdout.ref", empty_ref,
9290
env={'MIRIFLAGS': "-Zmiri-track-raw-pointers"},
9391
)
9492
test("`cargo miri test` (with filter)",
9593
cargo_miri("test") + ["--", "--format=pretty", "le1"],
96-
"test.filter.stdout.ref", rustdoc_ref,
94+
"test.filter.stdout.ref", empty_ref,
9795
)
9896
test("`cargo miri test` (test target)",
9997
cargo_miri("test") + ["--test", "test", "--", "--format=pretty"],
100-
"test.test-target.stdout.ref", "test.stderr-empty.ref",
98+
"test.test-target.stdout.ref", empty_ref,
10199
)
102100
test("`cargo miri test` (bin target)",
103101
cargo_miri("test") + ["--bin", "cargo-miri-test", "--", "--format=pretty"],
104-
"test.bin-target.stdout.ref", "test.stderr-empty.ref",
102+
"test.bin-target.stdout.ref", empty_ref,
105103
)
106104
test("`cargo miri test` (subcrate, no isolation)",
107105
cargo_miri("test") + ["-p", "subcrate"],
108-
"test.subcrate.stdout.ref", "test.stderr-empty.ref",
106+
"test.subcrate.stdout.ref", empty_ref,
109107
env={'MIRIFLAGS': "-Zmiri-disable-isolation"},
110108
)
111109

test-cargo-miri/test.default.stdout.ref

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
1212
running 1 test
1313
test src/lib.rs - make_true (line 2) ... ok
1414

15-
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
15+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
1616

test-cargo-miri/test.filter.stdout.ref

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 6 filtered out
1212

1313
running 0 tests
1414

15-
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
15+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in $TIME
1616

test-cargo-miri/test.stderr-rustdoc.ref

Whitespace-only changes.

0 commit comments

Comments
 (0)