nextest_runner/
rustc_cli.rs1use crate::cargo_config::TargetTriple;
5use camino::Utf8PathBuf;
6use std::{borrow::Cow, path::PathBuf};
7use tracing::{debug, trace};
8
9#[derive(Clone, Debug)]
11pub struct RustcCli<'a> {
12 rustc_path: Utf8PathBuf,
13 args: Vec<Cow<'a, str>>,
14}
15
16impl<'a> RustcCli<'a> {
17 pub fn version_verbose() -> Self {
19 let mut cli = Self::default();
20 cli.add_arg("--version").add_arg("--verbose");
21 cli
22 }
23
24 pub fn print_host_libdir() -> Self {
26 let mut cli = Self::default();
27 cli.add_arg("--print").add_arg("target-libdir");
28 cli
29 }
30
31 pub fn print_target_libdir(triple: &'a TargetTriple) -> Self {
33 let mut cli = Self::default();
34 cli.add_arg("--print")
35 .add_arg("target-libdir")
36 .add_arg("--target")
37 .add_arg(triple.platform.triple_str());
38 cli
39 }
40
41 fn add_arg(&mut self, arg: impl Into<Cow<'a, str>>) -> &mut Self {
42 self.args.push(arg.into());
43 self
44 }
45
46 pub fn to_expression(&self) -> duct::Expression {
48 duct::cmd(self.rustc_path.as_str(), self.args.iter().map(|arg| &**arg))
49 }
50
51 pub fn read(&self) -> Option<Vec<u8>> {
54 let expression = self.to_expression();
55 trace!("Executing command: {:?}", expression);
56 let output = match expression
57 .stdout_capture()
58 .stderr_capture()
59 .unchecked()
60 .run()
61 {
62 Ok(output) => output,
63 Err(e) => {
64 debug!("Failed to spawn the child process: {}", e);
65 return None;
66 }
67 };
68 if !output.status.success() {
69 debug!("execution failed with {}", output.status);
70 debug!("stdout:");
71 debug!("{}", String::from_utf8_lossy(&output.stdout));
72 debug!("stderr:");
73 debug!("{}", String::from_utf8_lossy(&output.stderr));
74 return None;
75 }
76 Some(output.stdout)
77 }
78}
79
80impl Default for RustcCli<'_> {
81 fn default() -> Self {
82 Self {
83 rustc_path: rustc_path(),
84 args: vec![],
85 }
86 }
87}
88
89fn rustc_path() -> Utf8PathBuf {
90 match std::env::var_os("RUSTC") {
91 Some(rustc_path) => PathBuf::from(rustc_path)
92 .try_into()
93 .expect("RUSTC env var is not valid UTF-8"),
94 None => Utf8PathBuf::from("rustc"),
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use camino_tempfile::Utf8TempDir;
102 use std::env;
103
104 #[test]
105 fn test_should_run_rustc_version() {
106 let mut cli = RustcCli::default();
107 cli.add_arg("--version");
108 let output = cli.read().expect("rustc --version should run successfully");
109 let output = String::from_utf8(output).expect("the output should be valid utf-8");
110 assert!(
111 output.starts_with("rustc"),
112 "The output should start with rustc, but the actual output is: {output}"
113 );
114 }
115
116 #[test]
117 fn test_should_respect_rustc_env() {
118 unsafe { env::set_var("RUSTC", "cargo") };
121 let mut cli = RustcCli::default();
122 cli.add_arg("--version");
123 let output = cli.read().expect("cargo --version should run successfully");
124 let output = String::from_utf8(output).expect("the output should be valid utf-8");
125 assert!(
126 output.starts_with("cargo"),
127 "The output should start with cargo, but the actual output is: {output}"
128 );
129 }
130
131 #[test]
132 fn test_fail_to_spawn() {
133 let fake_dir = Utf8TempDir::new().expect("should create the temp dir successfully");
134 unsafe { env::set_var("RUSTC", fake_dir.path()) };
138 let mut cli = RustcCli::default();
139 cli.add_arg("--version");
140 let output = cli.read();
141 assert_eq!(output, None);
142 }
143
144 #[test]
145 fn test_execute_with_failure() {
146 let mut cli = RustcCli::default();
147 cli.add_arg("--print");
149 cli.add_arg("Y7uDG1HrrY");
150 let output = cli.read();
151 assert_eq!(output, None);
152 }
153}