Skip to content

Commit 89cc96d

Browse files
committed
Break out {Cargo,}Miri into their own miri_tests module
1 parent 7b15551 commit 89cc96d

File tree

2 files changed

+240
-227
lines changed

2 files changed

+240
-227
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use std::path::PathBuf;
2+
3+
use super::test_helpers::prepare_cargo_test;
4+
use crate::core::build_steps::compile;
5+
use crate::core::build_steps::tool::{self, SourceType};
6+
use crate::core::builder::{self, Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
7+
use crate::core::config::TargetSelection;
8+
use crate::utils::build_stamp::{self};
9+
use crate::utils::exec::BootstrapCommand;
10+
use crate::utils::helpers;
11+
use crate::{DocTests, Mode};
12+
13+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14+
pub struct Miri {
15+
target: TargetSelection,
16+
}
17+
18+
impl Miri {
19+
/// Run `cargo miri setup` for the given target, return where the Miri sysroot was put.
20+
pub fn build_miri_sysroot(
21+
builder: &Builder<'_>,
22+
compiler: Compiler,
23+
target: TargetSelection,
24+
) -> PathBuf {
25+
let miri_sysroot = builder.out.join(compiler.host).join("miri-sysroot");
26+
let mut cargo = builder::Cargo::new(
27+
builder,
28+
compiler,
29+
Mode::Std,
30+
SourceType::Submodule,
31+
target,
32+
Kind::MiriSetup,
33+
);
34+
35+
// Tell `cargo miri setup` where to find the sources.
36+
cargo.env("MIRI_LIB_SRC", builder.src.join("library"));
37+
// Tell it where to put the sysroot.
38+
cargo.env("MIRI_SYSROOT", &miri_sysroot);
39+
40+
let mut cargo = BootstrapCommand::from(cargo);
41+
let _guard =
42+
builder.msg(Kind::Build, compiler.stage, "miri sysroot", compiler.host, target);
43+
cargo.run(builder);
44+
45+
// # Determine where Miri put its sysroot.
46+
// To this end, we run `cargo miri setup --print-sysroot` and capture the output.
47+
// (We do this separately from the above so that when the setup actually
48+
// happens we get some output.)
49+
// We re-use the `cargo` from above.
50+
cargo.arg("--print-sysroot");
51+
52+
builder.verbose(|| println!("running: {cargo:?}"));
53+
let stdout = cargo.run_capture_stdout(builder).stdout();
54+
// Output is "<sysroot>\n".
55+
let sysroot = stdout.trim_end();
56+
builder.verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
57+
PathBuf::from(sysroot)
58+
}
59+
}
60+
61+
impl Step for Miri {
62+
type Output = ();
63+
const ONLY_HOSTS: bool = false;
64+
65+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
66+
run.path("src/tools/miri")
67+
}
68+
69+
fn make_run(run: RunConfig<'_>) {
70+
run.builder.ensure(Miri { target: run.target });
71+
}
72+
73+
/// Runs `cargo test` for miri.
74+
fn run(self, builder: &Builder<'_>) {
75+
let host = builder.build.build;
76+
let target = self.target;
77+
let stage = builder.top_stage;
78+
if stage == 0 {
79+
eprintln!("miri cannot be tested at stage 0");
80+
std::process::exit(1);
81+
}
82+
83+
// This compiler runs on the host, we'll just use it for the target.
84+
let target_compiler = builder.compiler(stage, host);
85+
// Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise
86+
// we'd have stageN/bin/rustc and stageN/bin/rustdoc be effectively different stage
87+
// compilers, which isn't what we want. Rustdoc should be linked in the same way as the
88+
// rustc compiler it's paired with, so it must be built with the previous stage compiler.
89+
let host_compiler = builder.compiler(stage - 1, host);
90+
91+
// Build our tools.
92+
let miri = builder.ensure(tool::Miri { compiler: host_compiler, target: host });
93+
// the ui tests also assume cargo-miri has been built
94+
builder.ensure(tool::CargoMiri { compiler: host_compiler, target: host });
95+
96+
// We also need sysroots, for Miri and for the host (the latter for build scripts).
97+
// This is for the tests so everything is done with the target compiler.
98+
let miri_sysroot = Miri::build_miri_sysroot(builder, target_compiler, target);
99+
builder.ensure(compile::Std::new(target_compiler, host));
100+
let host_sysroot = builder.sysroot(target_compiler);
101+
102+
// Miri has its own "target dir" for ui test dependencies. Make sure it gets cleared when
103+
// the sysroot gets rebuilt, to avoid "found possibly newer version of crate `std`" errors.
104+
if !builder.config.dry_run() {
105+
let ui_test_dep_dir = builder.stage_out(host_compiler, Mode::ToolStd).join("miri_ui");
106+
// The mtime of `miri_sysroot` changes when the sysroot gets rebuilt (also see
107+
// <https://github.com/RalfJung/rustc-build-sysroot/commit/10ebcf60b80fe2c3dc765af0ff19fdc0da4b7466>).
108+
// We can hence use that directly as a signal to clear the ui test dir.
109+
build_stamp::clear_if_dirty(builder, &ui_test_dep_dir, &miri_sysroot);
110+
}
111+
112+
// Run `cargo test`.
113+
// This is with the Miri crate, so it uses the host compiler.
114+
let mut cargo = tool::prepare_tool_cargo(
115+
builder,
116+
host_compiler,
117+
Mode::ToolRustc,
118+
host,
119+
Kind::Test,
120+
"src/tools/miri",
121+
SourceType::InTree,
122+
&[],
123+
);
124+
125+
cargo.add_rustc_lib_path(builder);
126+
127+
// We can NOT use `run_cargo_test` since Miri's integration tests do not use the usual test
128+
// harness and therefore do not understand the flags added by `add_flags_and_try_run_test`.
129+
let mut cargo = prepare_cargo_test(cargo, &[], &[], "miri", host, builder);
130+
131+
// miri tests need to know about the stage sysroot
132+
cargo.env("MIRI_SYSROOT", &miri_sysroot);
133+
cargo.env("MIRI_HOST_SYSROOT", &host_sysroot);
134+
cargo.env("MIRI", &miri);
135+
136+
// Set the target.
137+
cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg());
138+
139+
{
140+
let _guard = builder.msg_sysroot_tool(Kind::Test, stage, "miri", host, target);
141+
let _time = helpers::timeit(builder);
142+
cargo.run(builder);
143+
}
144+
145+
// Run it again for mir-opt-level 4 to catch some miscompilations.
146+
if builder.config.test_args().is_empty() {
147+
cargo.env("MIRIFLAGS", "-O -Zmir-opt-level=4 -Cdebug-assertions=yes");
148+
// Optimizations can change backtraces
149+
cargo.env("MIRI_SKIP_UI_CHECKS", "1");
150+
// `MIRI_SKIP_UI_CHECKS` and `RUSTC_BLESS` are incompatible
151+
cargo.env_remove("RUSTC_BLESS");
152+
// Optimizations can change error locations and remove UB so don't run `fail` tests.
153+
cargo.args(["tests/pass", "tests/panic"]);
154+
155+
{
156+
let _guard = builder.msg_sysroot_tool(
157+
Kind::Test,
158+
stage,
159+
"miri (mir-opt-level 4)",
160+
host,
161+
target,
162+
);
163+
let _time = helpers::timeit(builder);
164+
cargo.run(builder);
165+
}
166+
}
167+
}
168+
}
169+
170+
/// Runs `cargo miri test` to demonstrate that `src/tools/miri/cargo-miri`
171+
/// works and that libtest works under miri.
172+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
173+
pub struct CargoMiri {
174+
target: TargetSelection,
175+
}
176+
177+
impl Step for CargoMiri {
178+
type Output = ();
179+
const ONLY_HOSTS: bool = false;
180+
181+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
182+
run.path("src/tools/miri/cargo-miri")
183+
}
184+
185+
fn make_run(run: RunConfig<'_>) {
186+
run.builder.ensure(CargoMiri { target: run.target });
187+
}
188+
189+
/// Tests `cargo miri test`.
190+
fn run(self, builder: &Builder<'_>) {
191+
let host = builder.build.build;
192+
let target = self.target;
193+
let stage = builder.top_stage;
194+
if stage == 0 {
195+
eprintln!("cargo-miri cannot be tested at stage 0");
196+
std::process::exit(1);
197+
}
198+
199+
// This compiler runs on the host, we'll just use it for the target.
200+
let compiler = builder.compiler(stage, host);
201+
202+
// Run `cargo miri test`.
203+
// This is just a smoke test (Miri's own CI invokes this in a bunch of different ways and ensures
204+
// that we get the desired output), but that is sufficient to make sure that the libtest harness
205+
// itself executes properly under Miri, and that all the logic in `cargo-miri` does not explode.
206+
let mut cargo = tool::prepare_tool_cargo(
207+
builder,
208+
compiler,
209+
Mode::ToolStd, // it's unclear what to use here, we're not building anything just doing a smoke test!
210+
target,
211+
Kind::MiriTest,
212+
"src/tools/miri/test-cargo-miri",
213+
SourceType::Submodule,
214+
&[],
215+
);
216+
217+
// We're not using `prepare_cargo_test` so we have to do this ourselves.
218+
// (We're not using that as the test-cargo-miri crate is not known to bootstrap.)
219+
match builder.doc_tests {
220+
DocTests::Yes => {}
221+
DocTests::No => {
222+
cargo.args(["--lib", "--bins", "--examples", "--tests", "--benches"]);
223+
}
224+
DocTests::Only => {
225+
cargo.arg("--doc");
226+
}
227+
}
228+
229+
// Finally, pass test-args and run everything.
230+
cargo.arg("--").args(builder.config.test_args());
231+
let mut cargo = BootstrapCommand::from(cargo);
232+
{
233+
let _guard = builder.msg_sysroot_tool(Kind::Test, stage, "cargo-miri", host, target);
234+
let _time = helpers::timeit(builder);
235+
cargo.run(builder);
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)