Skip to main content

supercov_engine/
rust_cargo_configuration.rs

1//! Exact pre-execution resolution for Cargo target runners.
2//!
3//! Cargo remains the authority for building and ordering test artifacts, but
4//! Supercov must compose an already-configured target runner inside its
5//! authenticated Cargo-runner boundary without changing Cargo or libtest
6//! semantics. This module resolves the user's original Cargo configuration
7//! before the repository is copied into the isolated workspace. Unsupported
8//! configuration surfaces fail before user code executes.
9
10use std::{
11    collections::{BTreeSet, HashMap},
12    ffi::OsString,
13    fs,
14    path::{Component, Path, PathBuf},
15    process::Command,
16    str::FromStr,
17};
18
19use cargo_config2::cargo_home_with_cwd;
20use cargo_platform::{Cfg, CfgExpr};
21use serde::{Deserialize, Serialize};
22
23use crate::{
24    rust_cargo_config_model::{
25        CargoConfigDefinition, CargoConfigKind, CargoConfigValue, load_cargo_configuration,
26    },
27    rust_test_runner::CargoTestInvocation,
28};
29
30#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
31#[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)]
32pub enum RustCargoRunnerProgram {
33    SearchPath { value: String },
34    Absolute { value: PathBuf },
35    WorkspaceRelative { value: PathBuf },
36}
37
38impl RustCargoRunnerProgram {
39    pub fn resolve(&self, workspace: &Path) -> PathBuf {
40        match self {
41            Self::SearchPath { value } => PathBuf::from(value),
42            Self::Absolute { value } => value.clone(),
43            Self::WorkspaceRelative { value } => workspace.join(value),
44        }
45    }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
49#[serde(rename_all = "camelCase", deny_unknown_fields)]
50pub struct RustCargoUnderlyingRunner {
51    pub program: RustCargoRunnerProgram,
52    pub arguments: Vec<String>,
53}
54
55impl RustCargoUnderlyingRunner {
56    pub fn resolve(&self, workspace: &Path) -> RustCargoResolvedRunner {
57        RustCargoResolvedRunner {
58            program: self.program.resolve(workspace),
59            arguments: self.arguments.clone(),
60        }
61    }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
65#[serde(rename_all = "camelCase", deny_unknown_fields)]
66pub struct RustCargoResolvedRunner {
67    pub program: PathBuf,
68    pub arguments: Vec<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
72#[serde(rename_all = "camelCase", deny_unknown_fields)]
73pub struct RustCargoTargetRunnerPlan {
74    pub target: String,
75    pub underlying_runner: Option<RustCargoUnderlyingRunner>,
76}
77
78impl RustCargoTargetRunnerPlan {
79    pub fn resolve(&self, workspace: &Path) -> RustCargoResolvedTargetRunner {
80        RustCargoResolvedTargetRunner {
81            target: self.target.clone(),
82            underlying_runner: self
83                .underlying_runner
84                .as_ref()
85                .map(|runner| runner.resolve(workspace)),
86        }
87    }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
91#[serde(rename_all = "camelCase", deny_unknown_fields)]
92pub struct RustCargoResolvedTargetRunner {
93    pub target: String,
94    pub underlying_runner: Option<RustCargoResolvedRunner>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase", deny_unknown_fields)]
99pub struct RustCargoRunnerPlan {
100    pub compiler: RustCargoCompilerCommandPlan,
101    pub targets: Vec<RustCargoTargetRunnerPlan>,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
105#[serde(rename_all = "camelCase", deny_unknown_fields)]
106pub struct RustCargoCompilerCommandPlan {
107    pub rustc: RustCargoRunnerProgram,
108    pub rustc_wrapper: Option<RustCargoRunnerProgram>,
109    pub rustc_workspace_wrapper: Option<RustCargoRunnerProgram>,
110}
111
112impl RustCargoCompilerCommandPlan {
113    fn resolved_programs(&self, workspace: &Path) -> Vec<PathBuf> {
114        self.rustc_wrapper
115            .iter()
116            .chain(self.rustc_workspace_wrapper.iter())
117            .chain(std::iter::once(&self.rustc))
118            .map(|program| program.resolve(workspace))
119            .collect()
120    }
121
122    fn command(&self, workspace: &Path) -> Command {
123        let programs = self.resolved_programs(workspace);
124        let mut command = Command::new(&programs[0]);
125        command.args(&programs[1..]);
126        command
127    }
128}
129
130#[derive(Debug, Clone)]
131struct CargoModelInputs {
132    cargo_home: Option<PathBuf>,
133    environment: HashMap<String, OsString>,
134    host_override: Option<String>,
135}
136
137impl CargoModelInputs {
138    fn ambient(root: &Path) -> Self {
139        Self {
140            cargo_home: cargo_home_with_cwd(root),
141            environment: std::env::vars_os()
142                .filter_map(|(key, value)| key.into_string().ok().map(|key| (key, value)))
143                .collect(),
144            host_override: None,
145        }
146    }
147
148    fn environment(&self, key: &str) -> Option<&OsString> {
149        self.environment.get(key)
150    }
151}
152
153#[derive(Debug)]
154pub enum RustCargoConfigurationError {
155    Io { path: PathBuf, reason: String },
156    Invalid(String),
157    Unsupported(String),
158}
159
160impl std::fmt::Display for RustCargoConfigurationError {
161    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        match self {
163            Self::Io { path, reason } => write!(formatter, "{}: {reason}", path.display()),
164            Self::Invalid(reason) => write!(formatter, "invalid Cargo configuration: {reason}"),
165            Self::Unsupported(reason) => formatter.write_str(reason),
166        }
167    }
168}
169
170impl std::error::Error for RustCargoConfigurationError {}
171
172fn io_error(path: &Path, error: impl std::fmt::Display) -> RustCargoConfigurationError {
173    RustCargoConfigurationError::Io {
174        path: path.to_owned(),
175        reason: error.to_string(),
176    }
177}
178
179fn command_targets(
180    invocation: &CargoTestInvocation,
181) -> Result<Vec<String>, RustCargoConfigurationError> {
182    let command_position = invocation.command_position().ok_or_else(|| {
183        RustCargoConfigurationError::Invalid("Cargo test runner subcommand is missing".into())
184    })?;
185    toolchain_selector(invocation, command_position)?;
186    if invocation.arguments[..command_position]
187        .iter()
188        .any(|argument| argument == "-Z" || argument.starts_with("-Z"))
189    {
190        return Err(RustCargoConfigurationError::Unsupported(
191            "Cargo runner composition does not yet resolve pre-subcommand -Z configuration semantics exactly"
192                .into(),
193        ));
194    }
195    let mut targets = Vec::new();
196    let mut index = 0;
197    while index < invocation.arguments.len() {
198        let argument = &invocation.arguments[index];
199        if argument == "--config" {
200            if invocation.arguments.get(index + 1).is_none() {
201                return Err(RustCargoConfigurationError::Invalid(
202                    "--config has no value".into(),
203                ));
204            }
205            index += 2;
206            continue;
207        }
208        if let Some(value) = argument.strip_prefix("--config=") {
209            if value.is_empty() {
210                return Err(RustCargoConfigurationError::Invalid(
211                    "--config has no value".into(),
212                ));
213            }
214            index += 1;
215            continue;
216        }
217        if argument == "--target" {
218            let target = invocation.arguments.get(index + 1).ok_or_else(|| {
219                RustCargoConfigurationError::Invalid("--target has no value".into())
220            })?;
221            targets.push(target.clone());
222            index += 2;
223            continue;
224        }
225        if let Some(target) = argument.strip_prefix("--target=") {
226            if target.is_empty() {
227                return Err(RustCargoConfigurationError::Invalid(
228                    "--target has no value".into(),
229                ));
230            }
231            targets.push(target.to_owned());
232        }
233        index += 1;
234    }
235    Ok(targets)
236}
237
238fn command_config_arguments(
239    invocation: &CargoTestInvocation,
240) -> Result<Vec<String>, RustCargoConfigurationError> {
241    let mut values = Vec::new();
242    let mut index = 0;
243    while index < invocation.arguments.len() {
244        let argument = &invocation.arguments[index];
245        if argument == "--config" {
246            let value = invocation.arguments.get(index + 1).ok_or_else(|| {
247                RustCargoConfigurationError::Invalid("--config has no value".into())
248            })?;
249            values.push(value.clone());
250            index += 2;
251            continue;
252        }
253        if let Some(value) = argument.strip_prefix("--config=") {
254            if value.is_empty() {
255                return Err(RustCargoConfigurationError::Invalid(
256                    "--config has no value".into(),
257                ));
258            }
259            values.push(value.to_owned());
260        }
261        index += 1;
262    }
263    Ok(values)
264}
265
266fn toolchain_selector(
267    invocation: &CargoTestInvocation,
268    test_position: usize,
269) -> Result<Option<&str>, RustCargoConfigurationError> {
270    let prefix = &invocation.arguments[..test_position];
271    let selectors = prefix
272        .iter()
273        .enumerate()
274        .filter(|(_, argument)| argument.starts_with('+'))
275        .collect::<Vec<_>>();
276    match selectors.as_slice() {
277        [] => Ok(None),
278        [(0, selector)] if selector.len() > 1 => Ok(Some(&selector[1..])),
279        [(0, _)] => Err(RustCargoConfigurationError::Invalid(
280            "the rustup toolchain selector is empty".into(),
281        )),
282        _ => Err(RustCargoConfigurationError::Invalid(
283            "the rustup +toolchain selector must be the first and only selector before the Cargo subcommand"
284                .into(),
285        )),
286    }
287}
288
289fn command_stdout(
290    program: &Path,
291    arguments: &[&str],
292    operation: &str,
293) -> Result<String, RustCargoConfigurationError> {
294    let output = Command::new(program)
295        .args(arguments)
296        .output()
297        .map_err(|error| io_error(program, error))?;
298    if !output.status.success() {
299        return Err(RustCargoConfigurationError::Invalid(format!(
300            "{} failed while {operation} with status {}: {}",
301            program.display(),
302            output
303                .status
304                .code()
305                .map_or_else(|| "signal".into(), |value| value.to_string()),
306            String::from_utf8_lossy(&output.stderr).trim()
307        )));
308    }
309    if !output.stderr.is_empty() {
310        return Err(RustCargoConfigurationError::Invalid(format!(
311            "{} wrote unexpected stderr while {operation}: {}",
312            program.display(),
313            String::from_utf8_lossy(&output.stderr).trim()
314        )));
315    }
316    String::from_utf8(output.stdout).map_err(|_| {
317        RustCargoConfigurationError::Invalid(format!(
318            "{} produced non-UTF-8 output while {operation}",
319            program.display()
320        ))
321    })
322}
323
324fn rustup_program(cargo: &Path) -> PathBuf {
325    cargo
326        .parent()
327        .filter(|parent| !parent.as_os_str().is_empty())
328        .map_or_else(
329            || PathBuf::from(format!("rustup{}", std::env::consts::EXE_SUFFIX)),
330            |parent| parent.join(format!("rustup{}", std::env::consts::EXE_SUFFIX)),
331        )
332}
333
334fn selected_cargo_program(
335    invocation: &CargoTestInvocation,
336) -> Result<OsString, RustCargoConfigurationError> {
337    let command_position = invocation.command_position().ok_or_else(|| {
338        RustCargoConfigurationError::Invalid("Cargo test runner subcommand is missing".into())
339    })?;
340    let Some(selector) = toolchain_selector(invocation, command_position)? else {
341        return Ok(invocation.program.clone().into());
342    };
343    let cargo_proxy = which::which(&invocation.program).map_err(|error| {
344        RustCargoConfigurationError::Invalid(format!(
345            "could not resolve the Cargo proxy {}: {error}",
346            invocation.program
347        ))
348    })?;
349    let rustup = rustup_program(&cargo_proxy);
350    let selected = command_stdout(
351        &rustup,
352        &["which", "--toolchain", selector, "cargo"],
353        "resolving the explicit rustup toolchain's Cargo",
354    )?;
355    let selected = PathBuf::from(selected.trim());
356    if !selected.is_absolute() {
357        return Err(RustCargoConfigurationError::Invalid(format!(
358            "rustup returned a non-absolute Cargo path for +{selector}: {}",
359            selected.display()
360        )));
361    }
362    let selected = fs::canonicalize(&selected).map_err(|error| io_error(&selected, error))?;
363    let metadata = fs::symlink_metadata(&selected).map_err(|error| io_error(&selected, error))?;
364    if !metadata.file_type().is_file() {
365        return Err(RustCargoConfigurationError::Invalid(format!(
366            "rustup returned a non-regular Cargo path for +{selector}: {}",
367            selected.display()
368        )));
369    }
370    let proxy_version = command_stdout(
371        &cargo_proxy,
372        &[&format!("+{selector}"), "-Vv"],
373        "verifying the explicit rustup Cargo selection",
374    )?;
375    let selected_version = command_stdout(
376        &selected,
377        &["-Vv"],
378        "verifying the resolved Cargo executable",
379    )?;
380    if proxy_version != selected_version {
381        return Err(RustCargoConfigurationError::Invalid(format!(
382            "the +{selector} Cargo proxy and rustup's selected Cargo executable disagree"
383        )));
384    }
385    Ok(selected.into_os_string())
386}
387
388fn model_target_config<'a>(
389    model: &'a CargoConfigValue,
390    target: &str,
391) -> Option<&'a CargoConfigValue> {
392    let target_table = model.at(&["target"])?.table()?;
393    if let Some(value) = target_table.get(target) {
394        return Some(value);
395    }
396    let mut value = model.at(&["target"])?;
397    for component in target.split('.') {
398        value = value.table()?.get(component)?;
399    }
400    Some(value)
401}
402
403fn model_exact_runner<'a>(
404    model: &'a CargoConfigValue,
405    target: &str,
406) -> Option<&'a CargoConfigValue> {
407    model_target_config(model, target)?.at(&["runner"])
408}
409
410#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
411enum ModelTargetKind {
412    Tuple,
413    Json,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
417struct ModelTarget {
418    kind: ModelTargetKind,
419    name: String,
420    cargo_argument: String,
421}
422
423fn model_targets(
424    root: &Path,
425    model: &CargoConfigValue,
426    command_targets: Vec<String>,
427    host: &str,
428    inputs: &CargoModelInputs,
429) -> Result<Vec<ModelTarget>, RustCargoConfigurationError> {
430    let convert = |target: &str| {
431        let target = target.trim();
432        if target.is_empty() {
433            return Err(RustCargoConfigurationError::Invalid(
434                "Cargo target was empty".into(),
435            ));
436        }
437        let target = if target == "host-tuple" { host } else { target };
438        if target.ends_with(".json") {
439            let requested = Path::new(target);
440            let requested = if requested.is_absolute() {
441                requested.to_owned()
442            } else {
443                root.join(requested)
444            };
445            let path = fs::canonicalize(&requested).map_err(|error| io_error(&requested, error))?;
446            let name = path
447                .file_stem()
448                .and_then(|stem| stem.to_str())
449                .filter(|stem| !stem.is_empty())
450                .ok_or_else(|| {
451                    RustCargoConfigurationError::Invalid(format!(
452                        "Cargo target specification has no UTF-8 file stem: {}",
453                        path.display()
454                    ))
455                })?;
456            let cargo_argument = path.to_str().ok_or_else(|| {
457                RustCargoConfigurationError::Invalid(format!(
458                    "Cargo target specification path is not UTF-8: {}",
459                    path.display()
460                ))
461            })?;
462            return Ok(ModelTarget {
463                kind: ModelTargetKind::Json,
464                name: name.to_owned(),
465                cargo_argument: cargo_argument.to_owned(),
466            });
467        }
468        Ok(ModelTarget {
469            kind: ModelTargetKind::Tuple,
470            name: target.to_owned(),
471            cargo_argument: target.to_owned(),
472        })
473    };
474    let targets = if !command_targets.is_empty() {
475        command_targets
476    } else if let Some(target) = inputs.environment("CARGO_BUILD_TARGET") {
477        let target = target.clone().into_string().map_err(|_| {
478            RustCargoConfigurationError::Invalid("CARGO_BUILD_TARGET is not UTF-8".into())
479        })?;
480        vec![target]
481    } else if let Some(targets) = model.at(&["build", "target"]) {
482        targets
483            .string_list()
484            .ok_or_else(|| {
485                RustCargoConfigurationError::Invalid(
486                    "build.target must be a string or string array".into(),
487                )
488            })?
489            .into_iter()
490            .map(str::to_owned)
491            .collect()
492    } else {
493        vec![host.to_owned()]
494    };
495    let targets = targets
496        .iter()
497        .map(|target| convert(target))
498        .collect::<Result<BTreeSet<_>, _>>()
499        .map(BTreeSet::into_iter)
500        .map(Iterator::collect::<Vec<_>>)?;
501    let mut names = HashMap::new();
502    for target in &targets {
503        if let Some(first) = names.insert(&target.name, &target.cargo_argument) {
504            return Err(RustCargoConfigurationError::Invalid(format!(
505                "Cargo targets {first} and {} have the same configuration identity {:?}",
506                target.cargo_argument, target.name
507            )));
508        }
509    }
510    Ok(targets)
511}
512
513fn environment_runner(
514    target: &str,
515    inputs: &CargoModelInputs,
516) -> Result<Option<CargoConfigValue>, RustCargoConfigurationError> {
517    let mut key = target.replace(['-', '.'], "_");
518    key.make_ascii_uppercase();
519    let key = format!("CARGO_TARGET_{key}_RUNNER");
520    let Some(value) = inputs.environment(&key) else {
521        return Ok(None);
522    };
523    let value = value
524        .clone()
525        .into_string()
526        .map_err(|_| RustCargoConfigurationError::Invalid(format!("{key} is not UTF-8")))?;
527    Ok(Some(CargoConfigValue {
528        kind: CargoConfigKind::String(value),
529        definition: CargoConfigDefinition::Environment(key),
530    }))
531}
532
533fn environment_tool(
534    root: &Path,
535    key: &str,
536    inputs: &CargoModelInputs,
537    empty_disables: bool,
538    non_utf8_is_absent: bool,
539) -> Result<Option<Option<RustCargoRunnerProgram>>, RustCargoConfigurationError> {
540    let Some(value) = inputs.environment(key) else {
541        return Ok(None);
542    };
543    let value = match value.clone().into_string() {
544        Ok(value) => value,
545        Err(_) if non_utf8_is_absent => return Ok(None),
546        Err(_) => {
547            return Err(RustCargoConfigurationError::Invalid(format!(
548                "{key} is not UTF-8"
549            )));
550        }
551    };
552    if empty_disables && value.is_empty() {
553        return Ok(Some(None));
554    }
555    let path = if value.contains('/') || value.contains('\\') {
556        root.join(value)
557    } else {
558        PathBuf::from(value)
559    };
560    Ok(Some(Some(runner_program(root, path)?)))
561}
562
563fn model_tool(
564    root: &Path,
565    value: &CargoConfigValue,
566    empty_disables: bool,
567) -> Result<Option<RustCargoRunnerProgram>, RustCargoConfigurationError> {
568    let path = value
569        .program_path(root)
570        .map_err(|error| RustCargoConfigurationError::Invalid(error.to_string()))?;
571    if empty_disables && path.as_os_str().is_empty() {
572        return Ok(None);
573    }
574    runner_program(root, path).map(Some)
575}
576
577fn selected_compiler_tool(
578    root: &Path,
579    model: &CargoConfigValue,
580    inputs: &CargoModelInputs,
581    model_key: &str,
582    direct_environment: &str,
583    cargo_environment: &str,
584    empty_disables: bool,
585) -> Result<Option<RustCargoRunnerProgram>, RustCargoConfigurationError> {
586    let configured = model.at(&["build", model_key]);
587    if let Some(selected) =
588        environment_tool(root, direct_environment, inputs, empty_disables, true)?
589    {
590        return Ok(selected);
591    }
592    if configured.is_some_and(|value| {
593        matches!(
594            value.definition,
595            CargoConfigDefinition::CliFile(_) | CargoConfigDefinition::CliValue
596        )
597    }) {
598        return model_tool(root, configured.expect("checked above"), empty_disables);
599    }
600    if let Some(selected) =
601        environment_tool(root, cargo_environment, inputs, empty_disables, false)?
602    {
603        return Ok(selected);
604    }
605    configured
606        .map(|value| model_tool(root, value, empty_disables))
607        .transpose()
608        .map(Option::flatten)
609}
610
611fn compiler_command_plan(
612    root: &Path,
613    model: &CargoConfigValue,
614    default_rustc: RustCargoRunnerProgram,
615    inputs: &CargoModelInputs,
616) -> Result<RustCargoCompilerCommandPlan, RustCargoConfigurationError> {
617    let rustc = selected_compiler_tool(
618        root,
619        model,
620        inputs,
621        "rustc",
622        "RUSTC",
623        "CARGO_BUILD_RUSTC",
624        false,
625    )?
626    .unwrap_or(default_rustc);
627    let rustc_wrapper = selected_compiler_tool(
628        root,
629        model,
630        inputs,
631        "rustc-wrapper",
632        "RUSTC_WRAPPER",
633        "CARGO_BUILD_RUSTC_WRAPPER",
634        true,
635    )?;
636    let rustc_workspace_wrapper = selected_compiler_tool(
637        root,
638        model,
639        inputs,
640        "rustc-workspace-wrapper",
641        "RUSTC_WORKSPACE_WRAPPER",
642        "CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER",
643        true,
644    )?;
645    Ok(RustCargoCompilerCommandPlan {
646        rustc,
647        rustc_wrapper,
648        rustc_workspace_wrapper,
649    })
650}
651
652fn compiler_host(
653    root: &Path,
654    compiler: &RustCargoCompilerCommandPlan,
655) -> Result<String, RustCargoConfigurationError> {
656    let output = compiler
657        .command(root)
658        .arg("-vV")
659        .output()
660        .map_err(|error| io_error(&compiler.rustc.resolve(root), error))?;
661    if !output.status.success() {
662        return Err(RustCargoConfigurationError::Invalid(format!(
663            "Cargo's configured compiler command failed while resolving its host with status {}: {}",
664            output
665                .status
666                .code()
667                .map_or_else(|| "signal".into(), |value| value.to_string()),
668            String::from_utf8_lossy(&output.stderr).trim()
669        )));
670    }
671    let stdout = String::from_utf8(output.stdout).map_err(|_| {
672        RustCargoConfigurationError::Invalid(
673            "Cargo's configured compiler command produced non-UTF-8 verbose version output".into(),
674        )
675    })?;
676    stdout
677        .lines()
678        .find_map(|line| line.strip_prefix("host: "))
679        .filter(|host| !host.is_empty())
680        .map(str::to_owned)
681        .ok_or_else(|| {
682            RustCargoConfigurationError::Invalid(
683                "Cargo's configured compiler command did not report a host in rustc -vV output"
684                    .into(),
685            )
686        })
687}
688
689fn parsed_model_runner(
690    root: &Path,
691    runner: &CargoConfigValue,
692) -> Result<RustCargoUnderlyingRunner, RustCargoConfigurationError> {
693    let (program, arguments) = runner
694        .program_and_arguments(root)
695        .map_err(|error| RustCargoConfigurationError::Invalid(error.to_string()))?;
696    Ok(RustCargoUnderlyingRunner {
697        program: runner_program(root, program)?,
698        arguments,
699    })
700}
701
702fn target_cfg(
703    configuration_root: &Path,
704    execution_root: &Path,
705    compiler: &RustCargoCompilerCommandPlan,
706    target: &ModelTarget,
707) -> Result<Vec<Cfg>, RustCargoConfigurationError> {
708    let target_argument = match target.kind {
709        ModelTargetKind::Tuple => PathBuf::from(&target.cargo_argument),
710        ModelTargetKind::Json => {
711            let source = Path::new(&target.cargo_argument);
712            source.strip_prefix(configuration_root).map_or_else(
713                |_| source.to_owned(),
714                |relative| execution_root.join(relative),
715            )
716        }
717    };
718    let output = compiler
719        .command(execution_root)
720        .args(["--print", "cfg", "--target"])
721        .arg(&target_argument)
722        .output()
723        .map_err(|error| io_error(&compiler.rustc.resolve(execution_root), error))?;
724    if !output.status.success() {
725        return Err(RustCargoConfigurationError::Invalid(format!(
726            "Cargo's configured compiler command failed while resolving cfg values for target {} with status {}: {}",
727            target.cargo_argument,
728            output
729                .status
730                .code()
731                .map_or_else(|| "signal".into(), |value| value.to_string()),
732            String::from_utf8_lossy(&output.stderr).trim()
733        )));
734    }
735    let stdout = String::from_utf8(output.stdout).map_err(|_| {
736        RustCargoConfigurationError::Invalid(format!(
737            "Cargo's configured compiler command produced non-UTF-8 cfg output for target {}",
738            target.cargo_argument
739        ))
740    })?;
741    stdout
742        .lines()
743        .filter(|line| !line.trim().is_empty())
744        .map(|line| {
745            Cfg::from_str(line.trim()).map_err(|error| {
746                RustCargoConfigurationError::Invalid(format!(
747                    "rustc emitted an invalid cfg value {line:?}: {error}"
748                ))
749            })
750        })
751        .collect()
752}
753
754fn cfg_runner<'a>(
755    model: &'a CargoConfigValue,
756    cfg: &[Cfg],
757) -> Result<Option<&'a CargoConfigValue>, RustCargoConfigurationError> {
758    let Some(targets) = model.at(&["target"]).and_then(CargoConfigValue::table) else {
759        return Ok(None);
760    };
761    let mut matches = Vec::new();
762    for (key, target) in targets {
763        let Some(expression) = key
764            .strip_prefix("cfg(")
765            .and_then(|key| key.strip_suffix(')'))
766        else {
767            continue;
768        };
769        let expression = CfgExpr::from_str(expression).map_err(|error| {
770            RustCargoConfigurationError::Invalid(format!(
771                "invalid Cargo target cfg key {key:?}: {error}"
772            ))
773        })?;
774        if expression.matches(cfg)
775            && let Some(runner) = target.at(&["runner"])
776        {
777            matches.push((key, runner));
778        }
779    }
780    match matches.as_slice() {
781        [] => Ok(None),
782        [(_, runner)] => Ok(Some(*runner)),
783        [(first, _), (second, _), ..] => Err(RustCargoConfigurationError::Invalid(format!(
784            "several matching instances of target.'cfg(..)'.runner: {first} and {second}"
785        ))),
786    }
787}
788
789fn model_runner(
790    configuration_root: &Path,
791    execution_root: &Path,
792    model: &CargoConfigValue,
793    compiler: &RustCargoCompilerCommandPlan,
794    target: &ModelTarget,
795    inputs: &CargoModelInputs,
796) -> Result<Option<RustCargoUnderlyingRunner>, RustCargoConfigurationError> {
797    let exact = model_exact_runner(model, &target.name);
798    let environment = environment_runner(&target.name, inputs)?;
799    let selected = match exact {
800        Some(value)
801            if matches!(
802                value.definition,
803                CargoConfigDefinition::CliFile(_) | CargoConfigDefinition::CliValue
804            ) =>
805        {
806            Some(value)
807        }
808        _ => environment.as_ref().or(exact),
809    };
810    if let Some(runner) = selected {
811        return parsed_model_runner(configuration_root, runner).map(Some);
812    }
813    let cfg = target_cfg(configuration_root, execution_root, compiler, target)?;
814    cfg_runner(model, &cfg)?
815        .map(|runner| parsed_model_runner(configuration_root, runner))
816        .transpose()
817}
818
819fn runner_program(
820    root: &Path,
821    path: PathBuf,
822) -> Result<RustCargoRunnerProgram, RustCargoConfigurationError> {
823    if path.is_absolute() {
824        if let Ok(relative) = path.strip_prefix(root) {
825            if relative.as_os_str().is_empty()
826                || relative
827                    .components()
828                    .any(|component| !matches!(component, Component::Normal(_)))
829            {
830                return Err(RustCargoConfigurationError::Invalid(format!(
831                    "runner path is not a regular workspace-relative path: {}",
832                    path.display()
833                )));
834            }
835            return Ok(RustCargoRunnerProgram::WorkspaceRelative {
836                value: relative.to_owned(),
837            });
838        }
839        return Ok(RustCargoRunnerProgram::Absolute { value: path });
840    }
841    if path.components().count() == 1 {
842        let value = path.into_os_string().into_string().map_err(|_| {
843            RustCargoConfigurationError::Invalid("runner search path is not UTF-8".into())
844        })?;
845        return Ok(RustCargoRunnerProgram::SearchPath { value });
846    }
847    Err(RustCargoConfigurationError::Invalid(format!(
848        "Cargo returned an unresolved relative runner path: {}",
849        path.display()
850    )))
851}
852
853fn resolve_with_inputs(
854    root: &Path,
855    execution_root: &Path,
856    invocation: &CargoTestInvocation,
857    model_inputs: CargoModelInputs,
858) -> Result<RustCargoRunnerPlan, RustCargoConfigurationError> {
859    let root = fs::canonicalize(root).map_err(|error| io_error(root, error))?;
860    let execution_root =
861        fs::canonicalize(execution_root).map_err(|error| io_error(execution_root, error))?;
862    let command_targets = command_targets(invocation)?;
863    let command_config = command_config_arguments(invocation)?;
864    let command_position = invocation.command_position().ok_or_else(|| {
865        RustCargoConfigurationError::Invalid("Cargo test runner subcommand is missing".into())
866    })?;
867    let explicit_toolchain = toolchain_selector(invocation, command_position)?.is_some();
868    let selected_cargo = selected_cargo_program(invocation)?;
869    let model = load_cargo_configuration(&root, model_inputs.cargo_home.clone(), &command_config)
870        .map_err(|error| RustCargoConfigurationError::Invalid(error.to_string()))?;
871    let selected_cargo_path = PathBuf::from(&selected_cargo);
872    let default_rustc = if explicit_toolchain {
873        selected_cargo_path
874            .parent()
875            .filter(|parent| !parent.as_os_str().is_empty())
876            .map(|parent| parent.join(format!("rustc{}", std::env::consts::EXE_SUFFIX)))
877            .filter(|candidate| candidate.is_file())
878            .map(|candidate| runner_program(&root, candidate))
879            .transpose()?
880            .unwrap_or_else(|| RustCargoRunnerProgram::SearchPath {
881                value: format!("rustc{}", std::env::consts::EXE_SUFFIX),
882            })
883    } else {
884        RustCargoRunnerProgram::SearchPath {
885            value: format!("rustc{}", std::env::consts::EXE_SUFFIX),
886        }
887    };
888    let compiler = compiler_command_plan(&root, &model, default_rustc, &model_inputs)?;
889    let host = model_inputs
890        .host_override
891        .clone()
892        .map(Ok)
893        .unwrap_or_else(|| compiler_host(&execution_root, &compiler))?;
894    let targets = model_targets(&root, &model, command_targets, &host, &model_inputs)?;
895    let targets = targets
896        .iter()
897        .map(|target| {
898            Ok(RustCargoTargetRunnerPlan {
899                target: target.name.clone(),
900                underlying_runner: model_runner(
901                    &root,
902                    &execution_root,
903                    &model,
904                    &compiler,
905                    target,
906                    &model_inputs,
907                )?,
908            })
909        })
910        .collect::<Result<Vec<_>, RustCargoConfigurationError>>()?;
911    Ok(RustCargoRunnerPlan { compiler, targets })
912}
913
914pub(crate) fn resolve_cargo_runner_plan(
915    root: &Path,
916    execution_root: &Path,
917    invocation: &CargoTestInvocation,
918) -> Result<RustCargoRunnerPlan, RustCargoConfigurationError> {
919    resolve_with_inputs(
920        root,
921        execution_root,
922        invocation,
923        CargoModelInputs::ambient(root),
924    )
925}
926
927#[cfg(test)]
928mod tests {
929    use std::{
930        sync::atomic::{AtomicU64, Ordering},
931        time::{SystemTime, UNIX_EPOCH},
932    };
933
934    use super::*;
935
936    static FIXTURE_ID: AtomicU64 = AtomicU64::new(0);
937
938    fn fixture() -> PathBuf {
939        let root = std::env::temp_dir().join(format!(
940            "supercov-cargo-configuration-{}-{}-{}",
941            std::process::id(),
942            SystemTime::now()
943                .duration_since(UNIX_EPOCH)
944                .unwrap()
945                .as_nanos(),
946            FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
947        ));
948        fs::create_dir_all(root.join(".cargo/bin with spaces")).unwrap();
949        root
950    }
951
952    fn invocation(arguments: &[&str]) -> CargoTestInvocation {
953        CargoTestInvocation {
954            program: "cargo".into(),
955            kind: crate::rust_test_runner::RustCargoCommandKind::CargoTest,
956            arguments: arguments.iter().map(|value| (*value).into()).collect(),
957            runner_arguments: Vec::new(),
958        }
959    }
960
961    fn model_inputs() -> CargoModelInputs {
962        model_inputs_with([])
963    }
964
965    fn model_inputs_with<const N: usize>(
966        additional: [(OsString, OsString); N],
967    ) -> CargoModelInputs {
968        CargoModelInputs {
969            cargo_home: None,
970            environment: additional
971                .into_iter()
972                .filter_map(|(key, value)| key.into_string().ok().map(|key| (key, value)))
973                .collect(),
974            host_override: Some("aarch64-apple-darwin".into()),
975        }
976    }
977
978    #[test]
979    fn compiler_tools_follow_cargo_cli_and_environment_precedence() {
980        let root = fixture();
981        fs::write(
982            root.join(".cargo/config.toml"),
983            concat!(
984                "[build]\n",
985                "rustc=\"./file-rustc\"\n",
986                "rustc-wrapper=\"./file-wrapper\"\n",
987                "rustc-workspace-wrapper=\"./file-workspace-wrapper\"\n",
988            ),
989        )
990        .unwrap();
991        let cli = [
992            "build.rustc=\"./cli-rustc\"".into(),
993            "build.rustc-wrapper=\"./cli-wrapper\"".into(),
994            "build.rustc-workspace-wrapper=\"./cli-workspace-wrapper\"".into(),
995        ];
996        let model = load_cargo_configuration(&root, None, &cli).unwrap();
997        let plan = compiler_command_plan(
998            &root,
999            &model,
1000            RustCargoRunnerProgram::SearchPath {
1001                value: "rustc".into(),
1002            },
1003            &model_inputs_with([
1004                (OsString::from("RUSTC"), OsString::from("./direct-rustc")),
1005                (
1006                    OsString::from("CARGO_BUILD_RUSTC_WRAPPER"),
1007                    OsString::from("./cargo-wrapper"),
1008                ),
1009                (OsString::from("RUSTC_WORKSPACE_WRAPPER"), OsString::new()),
1010            ]),
1011        )
1012        .unwrap();
1013        assert_eq!(
1014            plan.rustc,
1015            RustCargoRunnerProgram::WorkspaceRelative {
1016                value: "direct-rustc".into()
1017            }
1018        );
1019        assert_eq!(
1020            plan.rustc_wrapper,
1021            Some(RustCargoRunnerProgram::WorkspaceRelative {
1022                value: "cli-wrapper".into()
1023            })
1024        );
1025        assert_eq!(plan.rustc_workspace_wrapper, None);
1026        fs::remove_dir_all(root).unwrap();
1027    }
1028
1029    #[cfg(unix)]
1030    #[test]
1031    fn direct_non_utf8_tool_environment_falls_back_but_config_environment_is_invalid() {
1032        use std::os::unix::ffi::OsStringExt;
1033
1034        let root = fixture();
1035        fs::write(root.join(".cargo/config.toml"), "").unwrap();
1036        let model =
1037            load_cargo_configuration(&root, None, &["build.rustc=\"./cli-rustc\"".into()]).unwrap();
1038        let plan = compiler_command_plan(
1039            &root,
1040            &model,
1041            RustCargoRunnerProgram::SearchPath {
1042                value: "rustc".into(),
1043            },
1044            &model_inputs_with([(OsString::from("RUSTC"), OsString::from_vec(vec![0xff]))]),
1045        )
1046        .unwrap();
1047        assert_eq!(
1048            plan.rustc,
1049            RustCargoRunnerProgram::WorkspaceRelative {
1050                value: "cli-rustc".into()
1051            }
1052        );
1053
1054        let empty_model = load_cargo_configuration(&root, None, &[]).unwrap();
1055        let error = compiler_command_plan(
1056            &root,
1057            &empty_model,
1058            RustCargoRunnerProgram::SearchPath {
1059                value: "rustc".into(),
1060            },
1061            &model_inputs_with([(
1062                OsString::from("CARGO_BUILD_RUSTC"),
1063                OsString::from_vec(vec![0xff]),
1064            )]),
1065        )
1066        .unwrap_err()
1067        .to_string();
1068        assert!(error.contains("CARGO_BUILD_RUSTC is not UTF-8"), "{error}");
1069        fs::remove_dir_all(root).unwrap();
1070    }
1071
1072    #[test]
1073    fn every_compiler_wrapper_layer_is_preserved_in_the_compiler_plan() {
1074        let root = fixture();
1075        fs::write(
1076            root.join(".cargo/config.toml"),
1077            "[build]\nrustc-wrapper=\"wrapper\"\n",
1078        )
1079        .unwrap();
1080        let model = load_cargo_configuration(&root, None, &[]).unwrap();
1081        let plan = compiler_command_plan(
1082            &root,
1083            &model,
1084            RustCargoRunnerProgram::SearchPath {
1085                value: "rustc".into(),
1086            },
1087            &model_inputs(),
1088        )
1089        .unwrap();
1090        assert_eq!(
1091            plan.rustc_wrapper,
1092            Some(RustCargoRunnerProgram::SearchPath {
1093                value: "wrapper".into()
1094            })
1095        );
1096
1097        fs::write(root.join(".cargo/config.toml"), "").unwrap();
1098        let model = load_cargo_configuration(
1099            &root,
1100            None,
1101            &["build.rustc-workspace-wrapper=\"workspace-wrapper\"".into()],
1102        )
1103        .unwrap();
1104        let plan = compiler_command_plan(
1105            &root,
1106            &model,
1107            RustCargoRunnerProgram::SearchPath {
1108                value: "rustc".into(),
1109            },
1110            &model_inputs(),
1111        )
1112        .unwrap();
1113        assert_eq!(
1114            plan.rustc_workspace_wrapper,
1115            Some(RustCargoRunnerProgram::SearchPath {
1116                value: "workspace-wrapper".into()
1117            })
1118        );
1119
1120        let model = load_cargo_configuration(&root, None, &[]).unwrap();
1121        let plan = compiler_command_plan(
1122            &root,
1123            &model,
1124            RustCargoRunnerProgram::SearchPath {
1125                value: "rustc".into(),
1126            },
1127            &model_inputs_with([(
1128                OsString::from("RUSTC_WRAPPER"),
1129                OsString::from("environment-wrapper"),
1130            )]),
1131        )
1132        .unwrap();
1133        assert_eq!(
1134            plan.rustc_wrapper,
1135            Some(RustCargoRunnerProgram::SearchPath {
1136                value: "environment-wrapper".into()
1137            })
1138        );
1139        fs::remove_dir_all(root).unwrap();
1140    }
1141
1142    #[cfg(unix)]
1143    #[test]
1144    fn included_build_rustc_drives_host_and_cfg_runner_selection() {
1145        use std::os::unix::fs::PermissionsExt;
1146
1147        let root = fixture();
1148        let rustc = which::which("rustc").unwrap();
1149        let proxy = root.join("compiler-proxy");
1150        let log = root.join("compiler.log");
1151        fs::write(
1152            &proxy,
1153            format!(
1154                "#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"{}\"\nexec \"{}\" \"$@\"\n",
1155                log.display(),
1156                rustc.display()
1157            ),
1158        )
1159        .unwrap();
1160        fs::set_permissions(&proxy, fs::Permissions::from_mode(0o755)).unwrap();
1161        fs::write(
1162            root.join(".cargo/compiler.toml"),
1163            concat!(
1164                "[build]\nrustc=\"./compiler-proxy\"\n",
1165                "[target.'cfg(unix)']\nrunner=[\"cfg-runner\",\"--cfg\"]\n",
1166            ),
1167        )
1168        .unwrap();
1169        fs::write(
1170            root.join(".cargo/config.toml"),
1171            "include=[\"compiler.toml\"]\n",
1172        )
1173        .unwrap();
1174        let mut inputs = model_inputs();
1175        inputs.host_override = None;
1176        let plan = resolve_with_inputs(&root, &root, &invocation(&["test"]), inputs).unwrap();
1177        assert_eq!(
1178            plan.compiler.rustc,
1179            RustCargoRunnerProgram::WorkspaceRelative {
1180                value: "compiler-proxy".into()
1181            }
1182        );
1183        assert_eq!(
1184            plan.targets[0].underlying_runner,
1185            Some(RustCargoUnderlyingRunner {
1186                program: RustCargoRunnerProgram::SearchPath {
1187                    value: "cfg-runner".into()
1188                },
1189                arguments: vec!["--cfg".into()]
1190            })
1191        );
1192        let invocations = fs::read_to_string(&log).unwrap();
1193        assert!(invocations.lines().any(|line| line == "-vV"));
1194        assert!(
1195            invocations
1196                .lines()
1197                .any(|line| line.contains("--print cfg --target"))
1198        );
1199        fs::remove_dir_all(root).unwrap();
1200    }
1201
1202    #[test]
1203    fn exact_target_beats_cfg_and_environment_beats_both() {
1204        let root = fixture();
1205        fs::write(
1206            root.join(".cargo/config.toml"),
1207            concat!(
1208                "[target.'cfg(target_vendor = \"apple\")']\n",
1209                "runner=[\"cfg-runner\",\"--cfg\"]\n",
1210                "[target.aarch64-apple-darwin]\n",
1211                "runner=\"exact-runner --exact\"\n",
1212            ),
1213        )
1214        .unwrap();
1215        let plan =
1216            resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs()).unwrap();
1217        assert_eq!(
1218            plan.targets[0].underlying_runner,
1219            Some(RustCargoUnderlyingRunner {
1220                program: RustCargoRunnerProgram::SearchPath {
1221                    value: "exact-runner".into(),
1222                },
1223                arguments: vec!["--exact".into()],
1224            })
1225        );
1226        let plan = resolve_with_inputs(
1227            &root,
1228            &root,
1229            &invocation(&["test"]),
1230            model_inputs_with([(
1231                OsString::from("CARGO_TARGET_AARCH64_APPLE_DARWIN_RUNNER"),
1232                OsString::from("environment-runner --environment"),
1233            )]),
1234        )
1235        .unwrap();
1236        assert_eq!(
1237            plan.targets[0].underlying_runner,
1238            Some(RustCargoUnderlyingRunner {
1239                program: RustCargoRunnerProgram::SearchPath {
1240                    value: "environment-runner".into(),
1241                },
1242                arguments: vec!["--environment".into()],
1243            })
1244        );
1245        fs::remove_dir_all(root).unwrap();
1246    }
1247
1248    #[test]
1249    fn resolves_exact_array_runner_and_relocates_workspace_program() {
1250        let root = fixture();
1251        fs::write(
1252            root.join(".cargo/config.toml"),
1253            "[target.aarch64-apple-darwin]\nrunner=[\"bin with spaces/runner\",\"--fixed\"]\n",
1254        )
1255        .unwrap();
1256        let plan =
1257            resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs()).unwrap();
1258        assert_eq!(plan.targets[0].target, "aarch64-apple-darwin");
1259        assert_eq!(
1260            plan.targets[0].underlying_runner,
1261            Some(RustCargoUnderlyingRunner {
1262                program: RustCargoRunnerProgram::WorkspaceRelative {
1263                    value: PathBuf::from("bin with spaces/runner")
1264                },
1265                arguments: vec!["--fixed".into()],
1266            })
1267        );
1268        fs::remove_dir_all(root).unwrap();
1269    }
1270
1271    #[test]
1272    fn ordinary_files_use_cargo_195_runner_parsing_and_duplicate_cfg_rules() {
1273        let root = fixture();
1274        fs::write(
1275            root.join(".cargo/config.toml"),
1276            "[target.aarch64-apple-darwin]\nrunner=\"runner\\t--one\\n--two\"\n",
1277        )
1278        .unwrap();
1279        let plan =
1280            resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs()).unwrap();
1281        assert_eq!(
1282            plan.targets[0].underlying_runner,
1283            Some(RustCargoUnderlyingRunner {
1284                program: RustCargoRunnerProgram::SearchPath {
1285                    value: "runner".into()
1286                },
1287                arguments: vec!["--one".into(), "--two".into()]
1288            })
1289        );
1290        fs::write(
1291            root.join(".cargo/config.toml"),
1292            concat!(
1293                "[target.'cfg(target_vendor = \"apple\")']\nrunner=\"vendor-cfg\"\n",
1294                "[target.'cfg(target_os = \"macos\")']\nrunner=\"os-cfg\"\n",
1295            ),
1296        )
1297        .unwrap();
1298        let error = resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs())
1299            .unwrap_err()
1300            .to_string();
1301        assert!(error.contains("several matching instances"), "{error}");
1302        fs::remove_dir_all(root).unwrap();
1303    }
1304
1305    #[test]
1306    fn cargo_duplicate_and_host_tuple_targets_collapse_before_runner_selection() {
1307        let root = fixture();
1308        fs::write(
1309            root.join(".cargo/config.toml"),
1310            "[target.aarch64-apple-darwin]\nrunner=\"runner\"\n",
1311        )
1312        .unwrap();
1313        let plan = resolve_with_inputs(
1314            &root,
1315            &root,
1316            &invocation(&[
1317                "test",
1318                "--target=host-tuple",
1319                "--target=aarch64-apple-darwin",
1320                "--target=aarch64-apple-darwin",
1321            ]),
1322            model_inputs(),
1323        )
1324        .unwrap();
1325        assert_eq!(plan.targets[0].target, "aarch64-apple-darwin");
1326        assert_eq!(
1327            plan.targets[0].underlying_runner,
1328            Some(RustCargoUnderlyingRunner {
1329                program: RustCargoRunnerProgram::SearchPath {
1330                    value: "runner".into()
1331                },
1332                arguments: Vec::new()
1333            })
1334        );
1335        fs::remove_dir_all(root).unwrap();
1336    }
1337
1338    #[test]
1339    fn custom_target_paths_are_project_relative_and_name_collisions_fail_closed() {
1340        let root = fixture();
1341        fs::create_dir_all(root.join("targets/first")).unwrap();
1342        fs::create_dir_all(root.join("targets/second")).unwrap();
1343        fs::write(root.join("targets/first/custom.json"), "{}").unwrap();
1344        fs::write(root.join("targets/second/custom.json"), "{}").unwrap();
1345        let model = CargoConfigValue {
1346            kind: CargoConfigKind::Table(std::collections::BTreeMap::new()),
1347            definition: CargoConfigDefinition::BuiltIn,
1348        };
1349        let inputs = model_inputs();
1350        let target = model_targets(
1351            &root,
1352            &model,
1353            vec!["targets/first/custom.json".into()],
1354            "aarch64-apple-darwin",
1355            &inputs,
1356        )
1357        .unwrap();
1358        assert_eq!(target[0].name, "custom");
1359        assert_eq!(
1360            Path::new(&target[0].cargo_argument),
1361            fs::canonicalize(root.join("targets/first/custom.json")).unwrap()
1362        );
1363        let error = model_targets(
1364            &root,
1365            &model,
1366            vec![
1367                "targets/first/custom.json".into(),
1368                "targets/second/custom.json".into(),
1369            ],
1370            "aarch64-apple-darwin",
1371            &inputs,
1372        )
1373        .unwrap_err()
1374        .to_string();
1375        assert!(error.contains("same configuration identity"), "{error}");
1376        fs::remove_dir_all(root).unwrap();
1377    }
1378
1379    #[test]
1380    fn direct_cargo_path_keeps_cargos_default_rustc_search_semantics() {
1381        let root = fixture();
1382        fs::write(root.join(".cargo/config.toml"), "").unwrap();
1383        let cargo = which::which("cargo").unwrap();
1384        let plan = resolve_with_inputs(
1385            &root,
1386            &root,
1387            &CargoTestInvocation {
1388                program: cargo.to_string_lossy().into_owned(),
1389                kind: crate::rust_test_runner::RustCargoCommandKind::CargoTest,
1390                arguments: vec!["test".into()],
1391                runner_arguments: Vec::new(),
1392            },
1393            model_inputs(),
1394        )
1395        .unwrap();
1396        assert_eq!(
1397            plan.compiler.rustc,
1398            RustCargoRunnerProgram::SearchPath {
1399                value: format!("rustc{}", std::env::consts::EXE_SUFFIX)
1400            }
1401        );
1402        fs::remove_dir_all(root).unwrap();
1403    }
1404
1405    #[test]
1406    fn resolves_an_explicit_installed_rustup_toolchain_before_target_configuration() {
1407        let root = fixture();
1408        // The toolchain has to be really installed for `+1.95.0` to resolve to
1409        // a real rustc, which is what this test is about.
1410        let rustc = Command::new("rustc")
1411            .args(["+1.95.0", "-vV"])
1412            .output()
1413            .unwrap();
1414        assert!(
1415            rustc.status.success(),
1416            "{}",
1417            String::from_utf8_lossy(&rustc.stderr)
1418        );
1419        // The target comes from `model_inputs`, which pins the host the way
1420        // every other test in this file does. Reading it from the machine
1421        // instead made the test pass only on an Apple silicon Mac: everywhere
1422        // else the plan honoured the pinned host and the assertion compared it
1423        // against the real one.
1424        let host = "aarch64-apple-darwin";
1425        fs::write(
1426            root.join(".cargo/config.toml"),
1427            format!("[target.{host}]\nrunner=[\"selected-runner\",\"--selected\"]\n"),
1428        )
1429        .unwrap();
1430        let plan = resolve_with_inputs(
1431            &root,
1432            &root,
1433            &invocation(&["+1.95.0", "test"]),
1434            model_inputs(),
1435        )
1436        .unwrap();
1437        let rustc_executable = format!("rustc{}", std::env::consts::EXE_SUFFIX);
1438        assert!(matches!(
1439            &plan.compiler.rustc,
1440            RustCargoRunnerProgram::Absolute { value }
1441                if value.file_name().and_then(|name| name.to_str())
1442                    == Some(rustc_executable.as_str())
1443        ));
1444        assert_eq!(plan.targets[0].target, host);
1445        assert_eq!(
1446            plan.targets[0].underlying_runner,
1447            Some(RustCargoUnderlyingRunner {
1448                program: RustCargoRunnerProgram::SearchPath {
1449                    value: "selected-runner".into(),
1450                },
1451                arguments: vec!["--selected".into()],
1452            })
1453        );
1454        fs::remove_dir_all(root).unwrap();
1455    }
1456
1457    #[test]
1458    fn resolves_include_cli_cfg_and_multiple_target_runners_but_rejects_open_surfaces() {
1459        let root = fixture();
1460        fs::write(
1461            root.join(".cargo/extra.toml"),
1462            "[target.aarch64-apple-darwin]\nrunner=[\"included\",\"--included\"]\n",
1463        )
1464        .unwrap();
1465        fs::write(
1466            root.join(".cargo/config.toml"),
1467            "include=[\"extra.toml\"]\n",
1468        )
1469        .unwrap();
1470        assert_eq!(
1471            resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs(),)
1472                .unwrap()
1473                .targets[0]
1474                .underlying_runner,
1475            Some(RustCargoUnderlyingRunner {
1476                program: RustCargoRunnerProgram::SearchPath {
1477                    value: "included".into()
1478                },
1479                arguments: vec!["--included".into()]
1480            })
1481        );
1482        assert_eq!(
1483            resolve_with_inputs(
1484                &root,
1485                &root,
1486                &invocation(&[
1487                    "test",
1488                    "--config",
1489                    "target.aarch64-apple-darwin.runner=[\"cli\",\"--cli\"]",
1490                ]),
1491                model_inputs_with([(
1492                    OsString::from("CARGO_TARGET_AARCH64_APPLE_DARWIN_RUNNER"),
1493                    OsString::from("environment"),
1494                )]),
1495            )
1496            .unwrap()
1497            .targets[0]
1498                .underlying_runner,
1499            Some(RustCargoUnderlyingRunner {
1500                program: RustCargoRunnerProgram::SearchPath {
1501                    value: "cli".into()
1502                },
1503                arguments: vec!["--cli".into()]
1504            })
1505        );
1506        let multi_target_environment = [
1507            (
1508                OsString::from("CARGO_TARGET_A_RUNNER"),
1509                OsString::from("runner-a"),
1510            ),
1511            (
1512                OsString::from("CARGO_TARGET_B_RUNNER"),
1513                OsString::from("runner-b"),
1514            ),
1515        ];
1516        let plan = resolve_with_inputs(
1517            &root,
1518            &root,
1519            &invocation(&["test", "--target=a", "--target=b"]),
1520            model_inputs_with(multi_target_environment),
1521        );
1522        let plan = plan.unwrap();
1523        assert_eq!(
1524            plan.targets
1525                .iter()
1526                .map(|target| target.target.as_str())
1527                .collect::<Vec<_>>(),
1528            ["a", "b"]
1529        );
1530        assert_eq!(
1531            plan.targets
1532                .iter()
1533                .map(|target| {
1534                    target
1535                        .underlying_runner
1536                        .as_ref()
1537                        .map(|runner| &runner.program)
1538                })
1539                .collect::<Vec<_>>(),
1540            [
1541                Some(&RustCargoRunnerProgram::SearchPath {
1542                    value: "runner-a".into()
1543                }),
1544                Some(&RustCargoRunnerProgram::SearchPath {
1545                    value: "runner-b".into()
1546                })
1547            ]
1548        );
1549        fs::write(
1550            root.join(".cargo/extra.toml"),
1551            "[target.'cfg(target_vendor = \"apple\")']\nrunner=\"included-cfg\"\n",
1552        )
1553        .unwrap();
1554        assert_eq!(
1555            resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs(),)
1556                .unwrap()
1557                .targets[0]
1558                .underlying_runner,
1559            Some(RustCargoUnderlyingRunner {
1560                program: RustCargoRunnerProgram::SearchPath {
1561                    value: "included-cfg".into()
1562                },
1563                arguments: Vec::new()
1564            })
1565        );
1566        fs::write(
1567            root.join(".cargo/extra.toml"),
1568            concat!(
1569                "[target.'cfg(target_vendor = \"apple\")']\nrunner=\"vendor-cfg\"\n",
1570                "[target.'cfg(target_os = \"macos\")']\nrunner=\"os-cfg\"\n",
1571            ),
1572        )
1573        .unwrap();
1574        let error = resolve_with_inputs(&root, &root, &invocation(&["test"]), model_inputs())
1575            .unwrap_err()
1576            .to_string();
1577        assert!(error.contains("several matching instances"), "{error}");
1578        assert!(
1579            command_targets(&invocation(&["--quiet", "+nightly", "test"]))
1580                .unwrap_err()
1581                .to_string()
1582                .contains("must be the first")
1583        );
1584        assert!(
1585            resolve_with_inputs(
1586                &root,
1587                &root,
1588                &invocation(&["-Ztarget-applies-to-host", "test"]),
1589                model_inputs(),
1590            )
1591            .unwrap_err()
1592            .to_string()
1593            .contains("-Z configuration semantics")
1594        );
1595        fs::remove_dir_all(root).unwrap();
1596    }
1597}