Skip to content

Commit 18ed848

Browse files
committed
Add some tests for simulating behavior under rustup.
1 parent 8205389 commit 18ed848

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed

tests/testsuite/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ mod rustdoc;
119119
mod rustdoc_extern_html;
120120
mod rustdocflags;
121121
mod rustflags;
122+
mod rustup;
122123
mod search;
123124
mod shell_quoting;
124125
mod source_replacement;

tests/testsuite/rustup.rs

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
//! Tests for Cargo's behavior under Rustup.
2+
3+
use cargo_test_support::paths::{home, root, CargoPathExt};
4+
use cargo_test_support::{cargo_process, process, project};
5+
use std::env;
6+
use std::env::consts::EXE_EXTENSION;
7+
use std::ffi::OsString;
8+
use std::fs;
9+
use std::path::{Path, PathBuf};
10+
11+
/// Helper to generate an executable.
12+
fn make_exe(dest: &Path, name: &str, contents: &str, env: &[(&str, PathBuf)]) -> PathBuf {
13+
let rs_name = format!("{name}.rs");
14+
fs::write(
15+
root().join(&rs_name),
16+
&format!("fn main() {{ {contents} }}"),
17+
)
18+
.unwrap();
19+
let mut pb = process("rustc");
20+
env.iter().for_each(|(key, value)| {
21+
pb.env(key, value);
22+
});
23+
pb.arg("--edition=2021")
24+
.arg(root().join(&rs_name))
25+
.exec()
26+
.unwrap();
27+
let exe = Path::new(name).with_extension(EXE_EXTENSION);
28+
let output = dest.join(&exe);
29+
fs::rename(root().join(&exe), &output).unwrap();
30+
output
31+
}
32+
33+
fn prepend_path(path: &Path) -> OsString {
34+
let mut paths = vec![path.to_path_buf()];
35+
paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default()));
36+
env::join_paths(paths).unwrap()
37+
}
38+
39+
struct RustupEnvironment {
40+
/// Path for ~/.cargo/bin
41+
cargo_bin: PathBuf,
42+
/// Path for ~/.rustup
43+
rustup_home: PathBuf,
44+
/// Path to the cargo executable in the toolchain directory
45+
/// (~/.rustup/toolchain/test-toolchain/bin/cargo.exe).
46+
cargo_toolchain_exe: PathBuf,
47+
}
48+
49+
/// Creates an executable which prints a message and then runs the *real* rustc.
50+
fn real_rustc_wrapper(bin_dir: &Path, message: &str) -> PathBuf {
51+
let real_rustc = cargo_util::paths::resolve_executable("rustc".as_ref()).unwrap();
52+
// The toolchain rustc needs to call the real rustc. In order to do that,
53+
// it needs to restore or clear the RUSTUP environment variables so that
54+
// if rustup is installed, it will call the correct rustc.
55+
let rustup_toolchain_setup = match std::env::var_os("RUSTUP_TOOLCHAIN") {
56+
Some(t) => format!(
57+
".env(\"RUSTUP_TOOLCHAIN\", \"{}\")",
58+
t.into_string().unwrap()
59+
),
60+
None => format!(".env_remove(\"RUSTUP_TOOLCHAIN\")"),
61+
};
62+
let mut env = vec![("CARGO_RUSTUP_TEST_real_rustc", real_rustc)];
63+
let rustup_home_setup = match std::env::var_os("RUSTUP_HOME") {
64+
Some(h) => {
65+
env.push(("CARGO_RUSTUP_TEST_RUSTUP_HOME", h.into()));
66+
format!(".env(\"RUSTUP_HOME\", env!(\"CARGO_RUSTUP_TEST_RUSTUP_HOME\"))")
67+
}
68+
None => format!(".env_remove(\"RUSTUP_HOME\")"),
69+
};
70+
make_exe(
71+
bin_dir,
72+
"rustc",
73+
&format!(
74+
r#"
75+
eprintln!("{message}");
76+
let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_real_rustc"))
77+
.args(std::env::args_os().skip(1))
78+
{rustup_toolchain_setup}
79+
{rustup_home_setup}
80+
.status();
81+
std::process::exit(r.unwrap().code().unwrap_or(2));
82+
"#
83+
),
84+
&env,
85+
)
86+
}
87+
88+
/// Creates a simulation of a rustup environment with `~/.cargo/bin` and
89+
/// `~/.rustup` directories populated with some executables that simulate
90+
/// rustup.
91+
fn simulated_rustup_environment() -> RustupEnvironment {
92+
// Set up ~/.rustup/toolchains/test-toolchain/bin with a custom rustc and cargo.
93+
let rustup_home = home().join(".rustup");
94+
let toolchain_bin = rustup_home
95+
.join("toolchains")
96+
.join("test-toolchain")
97+
.join("bin");
98+
toolchain_bin.mkdir_p();
99+
let rustc_toolchain_exe = real_rustc_wrapper(&toolchain_bin, "real rustc running");
100+
let cargo_toolchain_exe = make_exe(
101+
&toolchain_bin,
102+
"cargo",
103+
r#"panic!("cargo toolchain should not be called");"#,
104+
&[],
105+
);
106+
107+
// Set up ~/.cargo/bin with a typical set of rustup proxies.
108+
let cargo_bin = home().join(".cargo").join("bin");
109+
cargo_bin.mkdir_p();
110+
111+
let rustc_proxy = make_exe(
112+
&cargo_bin,
113+
"rustc",
114+
&format!(
115+
r#"
116+
match std::env::args().next().unwrap().as_ref() {{
117+
"rustc" => {{}}
118+
arg => panic!("proxy only supports rustc, got {{arg:?}}"),
119+
}}
120+
eprintln!("rustc proxy running");
121+
let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_rustc_toolchain_exe"))
122+
.args(std::env::args_os().skip(1))
123+
.status();
124+
std::process::exit(r.unwrap().code().unwrap_or(2));
125+
"#
126+
),
127+
&[("CARGO_RUSTUP_TEST_rustc_toolchain_exe", rustc_toolchain_exe)],
128+
);
129+
fs::hard_link(
130+
&rustc_proxy,
131+
cargo_bin.join("cargo").with_extension(EXE_EXTENSION),
132+
)
133+
.unwrap();
134+
fs::hard_link(
135+
&rustc_proxy,
136+
cargo_bin.join("rustup").with_extension(EXE_EXTENSION),
137+
)
138+
.unwrap();
139+
140+
RustupEnvironment {
141+
cargo_bin,
142+
rustup_home,
143+
cargo_toolchain_exe,
144+
}
145+
}
146+
147+
#[cargo_test]
148+
fn typical_rustup() {
149+
// Test behavior under a typical rustup setup with a normal toolchain.
150+
let RustupEnvironment {
151+
cargo_bin,
152+
rustup_home,
153+
cargo_toolchain_exe,
154+
} = simulated_rustup_environment();
155+
156+
// Set up a project and run a normal cargo build.
157+
let p = project().file("src/lib.rs", "").build();
158+
// The path is modified so that cargo will call `rustc` from
159+
// `~/.cargo/bin/rustc to use our custom rustup proxies.
160+
let path = prepend_path(&cargo_bin);
161+
p.cargo("check")
162+
.env("RUSTUP_TOOLCHAIN", "test-toolchain")
163+
.env("RUSTUP_HOME", &rustup_home)
164+
.env("PATH", &path)
165+
.with_stderr(
166+
"\
167+
[CHECKING] foo v0.0.1 [..]
168+
rustc proxy running
169+
real rustc running
170+
[FINISHED] [..]
171+
",
172+
)
173+
.run();
174+
175+
// Do a similar test, but with a toolchain link that does not have cargo
176+
// (which normally would do a fallback to nightly/beta/stable).
177+
cargo_toolchain_exe.rm_rf();
178+
p.build_dir().rm_rf();
179+
180+
p.cargo("check")
181+
.env("RUSTUP_TOOLCHAIN", "test-toolchain")
182+
.env("RUSTUP_HOME", &rustup_home)
183+
.env("PATH", &path)
184+
.with_stderr(
185+
"\
186+
[CHECKING] foo v0.0.1 [..]
187+
rustc proxy running
188+
real rustc running
189+
[FINISHED] [..]
190+
",
191+
)
192+
.run();
193+
}
194+
195+
// This doesn't work on Windows because Cargo forces the PATH to contain the
196+
// sysroot_libdir, which is actually `bin`, preventing the test from
197+
// overriding the bin directory.
198+
#[cargo_test(ignore_windows = "PATH can't be overridden on Windows")]
199+
fn custom_calls_other_cargo() {
200+
// Test behavior when a custom subcommand tries to manipulate PATH to use
201+
// a different toolchain.
202+
let RustupEnvironment {
203+
cargo_bin,
204+
rustup_home,
205+
cargo_toolchain_exe: _,
206+
} = simulated_rustup_environment();
207+
208+
// Create a directory with a custom toolchain (outside of the rustup universe).
209+
let custom_bin = root().join("custom-bin");
210+
custom_bin.mkdir_p();
211+
// `cargo` points to the real cargo.
212+
let cargo_exe = cargo_test_support::cargo_exe();
213+
fs::hard_link(&cargo_exe, custom_bin.join(cargo_exe.file_name().unwrap())).unwrap();
214+
// `rustc` executes the real rustc.
215+
real_rustc_wrapper(&custom_bin, "custom toolchain rustc running");
216+
217+
// A project that cargo-custom will try to build.
218+
let p = project().file("src/lib.rs", "").build();
219+
220+
// Create a custom cargo subcommand.
221+
// This will modify PATH to a custom toolchain and call cargo from that.
222+
make_exe(
223+
&cargo_bin,
224+
"cargo-custom",
225+
r#"
226+
use std::env;
227+
use std::process::Command;
228+
229+
eprintln!("custom command running");
230+
231+
let mut paths = vec![std::path::PathBuf::from(env!("CARGO_RUSTUP_TEST_custom_bin"))];
232+
paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default()));
233+
let path = env::join_paths(paths).unwrap();
234+
235+
let status = Command::new("cargo")
236+
.arg("check")
237+
.current_dir(env!("CARGO_RUSTUP_TEST_project_dir"))
238+
.env("PATH", path)
239+
.status()
240+
.unwrap();
241+
assert!(status.success());
242+
"#,
243+
&[
244+
("CARGO_RUSTUP_TEST_custom_bin", custom_bin),
245+
("CARGO_RUSTUP_TEST_project_dir", p.root()),
246+
],
247+
);
248+
249+
cargo_process("custom")
250+
// Set these to simulate what would happen when running under rustup.
251+
// We want to make sure that cargo-custom does not try to use the
252+
// rustup proxies.
253+
.env("RUSTUP_TOOLCHAIN", "test-toolchain")
254+
.env("RUSTUP_HOME", &rustup_home)
255+
.with_stderr(
256+
"\
257+
custom command running
258+
[CHECKING] foo [..]
259+
custom toolchain rustc running
260+
[FINISHED] [..]
261+
",
262+
)
263+
.run();
264+
}

0 commit comments

Comments
 (0)