Skip to main content

supercov_engine/
rust_test_runner.rs

1//! Stable Cargo/libtest execution for the owned Rust frontend.
2//!
3//! Source preparation happens in an isolated workspace. For `cargo test`,
4//! Cargo builds each test artifact once and every libtest case then runs in a
5//! process of its own with an evidence directory of its own, so attribution
6//! is exact by construction; doctests run through `rust_owned_doctests`, with
7//! this program standing in for rustdoc. For `cargo nextest run`, nextest
8//! keeps its own scheduling and retries and this program serves as its target
9//! runner (`rust_owned_nextest`), recording each attempt it launches.
10
11use std::{
12    collections::{BTreeMap, BTreeSet},
13    ffi::OsString,
14    fs,
15    io::Write,
16    path::{Component, Path, PathBuf},
17    process::{Command, Output},
18    sync::{
19        Mutex,
20        atomic::{AtomicUsize, Ordering},
21    },
22    time::Instant,
23};
24
25use serde::Deserialize;
26use supercov_contracts::{
27    AttributionPrecision, ExecutionModel, FrontendAttribution, FrontendLimitation,
28    FrontendLimitationScope, FrontendRunDeclaration, FrontendRunnerDeclaration,
29    LANGUAGE_FRONTEND_PROTOCOL_VERSION, StructuralSource,
30};
31
32use crate::{
33    coverage_analysis::McdcVector,
34    coverage_report::{
35        CoverageManifest, CoverageModelDeclaration, CoveragePhase, CoverageReportRequest,
36        DecisionMeta, DecisionSnapshot, ExecutionScope, ExitCodeInput, PersistedCoverageModel,
37        RawTestResult, RuntimeEvent, RuntimeSnapshot, TestProvenance,
38    },
39    evidence_archive::EvidenceArchiveEntry,
40    rust_project::PreparedRustProject,
41    rust_runtime::{RustProbeObservation, read_rust_region_directory},
42    rust_test_context::preflight_rust_test_contexts,
43};
44
45#[derive(Debug, Clone, PartialEq)]
46pub struct RustFrontendRun {
47    pub declaration: FrontendRunDeclaration,
48    pub request: CoverageReportRequest,
49    pub exit_code: i32,
50    pub artifacts: usize,
51    pub artifact_files: Vec<PathBuf>,
52    pub build_ms: f64,
53    pub execution_ms: f64,
54}
55
56impl RustFrontendRun {
57    pub fn archive_entries(&self) -> Result<Vec<EvidenceArchiveEntry>, serde_json::Error> {
58        let model = PersistedCoverageModel::from_declaration(
59            self.request
60                .coverage_model
61                .as_ref()
62                .expect("Rust frontend always declares a coverage model"),
63        )
64        .expect("Rust coverage model is contract-valid");
65        let mut entries = vec![
66            EvidenceArchiveEntry {
67                path: "coverage-model.json".into(),
68                contents: serde_json::to_vec(&model)?,
69            },
70            EvidenceArchiveEntry {
71                path: "frontend.json".into(),
72                contents: serde_json::to_vec(&self.declaration)?,
73            },
74            EvidenceArchiveEntry {
75                path: "manifest.json".into(),
76                contents: serde_json::to_vec(&self.request.manifest)?,
77            },
78        ];
79        for (index, result) in self.request.raw_results.iter().enumerate() {
80            entries.push(EvidenceArchiveEntry {
81                path: format!("results/{index:08}/mcdc.json"),
82                contents: serde_json::to_vec(result)?,
83            });
84        }
85        Ok(entries)
86    }
87}
88
89#[derive(Debug)]
90pub enum RustTestRunnerError {
91    UnsupportedCommand(String),
92    Launch(String),
93    CargoFailed(String),
94    CargoJson(String),
95    UnsafeArtifact(String),
96    ListFailed(String),
97    Probe(String),
98    Context(String),
99    UnknownProbe(String),
100    InvalidVector {
101        id: String,
102        expected: usize,
103        actual: usize,
104    },
105    Json(serde_json::Error),
106    Io(String),
107}
108
109impl std::fmt::Display for RustTestRunnerError {
110    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            Self::UnsupportedCommand(reason) => formatter.write_str(reason),
113            Self::Launch(reason) => {
114                write!(formatter, "could not launch Rust test process: {reason}")
115            }
116            Self::CargoFailed(reason) => write!(formatter, "Cargo test build failed: {reason}"),
117            Self::CargoJson(reason) => write!(formatter, "invalid Cargo JSON output: {reason}"),
118            Self::UnsafeArtifact(path) => {
119                write!(formatter, "Cargo emitted an unsafe test artifact: {path}")
120            }
121            Self::ListFailed(reason) => {
122                write!(formatter, "could not enumerate Rust tests: {reason}")
123            }
124            Self::Probe(reason) => write!(formatter, "invalid Rust probe evidence: {reason}"),
125            Self::Context(reason) => write!(formatter, "invalid Rust test context: {reason}"),
126            Self::UnknownProbe(id) => write!(
127                formatter,
128                "Rust runtime emitted an unknown obligation: {id}"
129            ),
130            Self::InvalidVector {
131                id,
132                expected,
133                actual,
134            } => write!(
135                formatter,
136                "Rust decision {id} emitted vector width {actual}; expected {expected}"
137            ),
138            Self::Json(error) => write!(formatter, "could not encode Rust evidence: {error}"),
139            Self::Io(reason) => formatter.write_str(reason),
140        }
141    }
142}
143
144impl std::error::Error for RustTestRunnerError {}
145
146impl From<serde_json::Error> for RustTestRunnerError {
147    fn from(value: serde_json::Error) -> Self {
148        Self::Json(value)
149    }
150}
151
152#[derive(Debug, Deserialize)]
153struct CargoMessage {
154    reason: String,
155    #[serde(default)]
156    target: Option<CargoArtifactTarget>,
157    #[serde(default)]
158    profile: Option<CargoArtifactProfile>,
159    executable: Option<PathBuf>,
160    manifest_path: Option<PathBuf>,
161}
162
163#[derive(Debug, Deserialize)]
164struct CargoArtifactTarget {
165    name: String,
166    kind: Vec<String>,
167    src_path: PathBuf,
168}
169
170#[derive(Debug, Deserialize)]
171struct CargoArtifactProfile {
172    test: bool,
173}
174
175#[derive(Debug, Clone)]
176struct TestArtifact {
177    executable: PathBuf,
178    name: String,
179    kind: String,
180    source: String,
181    /// Where Cargo runs the binary: the directory of its package's manifest.
182    /// tokio's `basic_fs` reads `Cargo.toml` from there.
183    package_directory: PathBuf,
184}
185
186#[derive(Debug)]
187struct ProcessTask {
188    ordinal: usize,
189    artifact_index: usize,
190    test_index: usize,
191    artifact: TestArtifact,
192    test: String,
193    context_id: u64,
194    directory: PathBuf,
195}
196
197#[derive(Debug)]
198struct ProcessOutcome {
199    task: ProcessTask,
200    output: Output,
201}
202
203/// A command line split the way POSIX `sh` splits a script's: quotes group,
204/// `''` and `""` are an empty word, and inside double quotes a backslash
205/// escapes only `$`, `` ` ``, `"`, `\` and a newline.
206fn shell_words(value: &str) -> Result<Vec<String>, RustTestRunnerError> {
207    let mut words = Vec::new();
208    let mut current = String::new();
209    // A word can be empty (`''`), so whether one has begun is its own fact.
210    let mut started = false;
211    let mut quote = None;
212    let mut characters = value.chars().peekable();
213    while let Some(character) = characters.next() {
214        match (quote, character) {
215            (Some('\''), '\'') | (Some('"'), '"') => quote = None,
216            (Some('\''), _) => current.push(character),
217            (Some(_), '\\') => match characters.peek() {
218                // A backslash before a line break continues the line.
219                Some('\n') => {
220                    characters.next();
221                }
222                Some('$' | '`' | '"' | '\\') => {
223                    current.extend(characters.next());
224                }
225                Some(_) => current.push('\\'),
226                None => quote = Some('\\'),
227            },
228            (Some(_), _) => current.push(character),
229            (None, '\\') => match characters.next() {
230                Some('\n') => {}
231                Some(escaped) => {
232                    current.push(escaped);
233                    started = true;
234                }
235                None => quote = Some('\\'),
236            },
237            (None, '\'' | '"') => {
238                quote = Some(character);
239                started = true;
240            }
241            (None, _) if character.is_whitespace() => {
242                if std::mem::take(&mut started) {
243                    words.push(std::mem::take(&mut current));
244                }
245            }
246            (None, _) => {
247                current.push(character);
248                started = true;
249            }
250        }
251    }
252    if quote.is_some() {
253        return Err(RustTestRunnerError::UnsupportedCommand(
254            "the expanded Cargo command contains an incomplete quote or escape".into(),
255        ));
256    }
257    if started {
258        words.push(current);
259    }
260    Ok(words)
261}
262
263fn executable_name(value: &str) -> &str {
264    Path::new(value)
265        .file_name()
266        .and_then(|name| name.to_str())
267        .unwrap_or(value)
268        .trim_end_matches(".exe")
269        .trim_end_matches(".cmd")
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub(crate) struct CargoTestInvocation {
274    pub program: String,
275    pub kind: RustCargoCommandKind,
276    pub arguments: Vec<String>,
277    pub runner_arguments: Vec<String>,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub(crate) enum RustCargoCommandKind {
282    CargoTest,
283    NextestRun,
284}
285
286impl CargoTestInvocation {
287    /// Where `test` (or `nextest run`) sits in the arguments. Only
288    /// `cargo_invocation` builds an invocation, and it refuses a command
289    /// without one.
290    pub(crate) fn command_position(&self) -> usize {
291        match self.kind {
292            RustCargoCommandKind::CargoTest => self
293                .arguments
294                .iter()
295                .position(|argument| argument == "test"),
296            RustCargoCommandKind::NextestRun => self
297                .arguments
298                .windows(2)
299                .position(|pair| pair == ["nextest", "run"]),
300        }
301        .expect("cargo_invocation keeps the test subcommand")
302    }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub(crate) struct RustLibtestSelection {
307    pub list_arguments: Vec<String>,
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
311pub(crate) struct RustCargoExecutionSelection {
312    pub run_libtests: bool,
313    pub run_doctests: bool,
314    /// The command asked for every test, not `--doc` by name: Cargo then
315    /// runs doctests only for the selected packages that can have them.
316    pub implicit_doctests: bool,
317    pub doctest_arguments: Vec<String>,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub(crate) struct NextestListInvocation {
322    pub arguments: Vec<String>,
323    pub runner_arguments: Vec<String>,
324}
325
326pub(crate) fn nextest_version_arguments(invocation: &CargoTestInvocation) -> Vec<String> {
327    let command = invocation.command_position();
328    let mut arguments = invocation.arguments[..command].to_vec();
329    arguments.extend(["nextest".into(), "--version".into()]);
330    arguments
331}
332
333fn nextest_run_only_option(argument: &str) -> Option<bool> {
334    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
335    match name {
336        "-j"
337        | "--jobs"
338        | "--test-threads"
339        | "--retries"
340        | "--flaky-result"
341        | "--max-fail"
342        | "--no-tests"
343        | "--failure-output"
344        | "--success-output"
345        | "--status-level"
346        | "--final-status-level"
347        | "--show-progress"
348        | "--max-progress-running"
349        | "--message-format"
350        | "--message-format-version" => Some(!argument.contains('=')),
351        "--fail-fast"
352        | "--ff"
353        | "--no-fail-fast"
354        | "--nff"
355        | "--no-capture"
356        | "--nocapture"
357        | "--no-output-indent"
358        | "--hide-progress-bar"
359        | "--no-input-handler" => Some(false),
360        _ if argument.starts_with("-j") && argument.len() > 2 => Some(false),
361        _ => None,
362    }
363}
364
365fn nextest_unsupported_run_option(argument: &str) -> Option<bool> {
366    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
367    match name {
368        "-R"
369        | "--rerun"
370        | "--debugger"
371        | "--tracer"
372        | "--stress-count"
373        | "--stress-duration"
374        | "--archive-file"
375        | "--archive-format"
376        | "--extract-to"
377        | "--cargo-metadata"
378        | "--workspace-remap"
379        | "--binaries-metadata"
380        | "--target-dir-remap"
381        | "--build-dir-remap" => Some(!argument.contains('=')),
382        "--no-run" | "--extract-overwrite" | "--persist-extract-tempdir" => Some(false),
383        _ => None,
384    }
385}
386
387fn nextest_shared_option(argument: &str) -> Option<bool> {
388    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
389    match name {
390        "--color"
391        | "-p"
392        | "--package"
393        | "--exclude"
394        | "--bin"
395        | "--example"
396        | "--test"
397        | "--bench"
398        | "-F"
399        | "--features"
400        | "--build-jobs"
401        | "--cargo-profile"
402        | "--target"
403        | "--target-dir"
404        | "--cargo-message-format"
405        | "--config"
406        | "--timings"
407        | "-Z"
408        | "--run-ignored"
409        | "--partition"
410        | "--platform-filter"
411        | "-E"
412        | "--filterset"
413        | "--filter-expr"
414        | "--manifest-path"
415        | "--config-file"
416        | "--user-config-file"
417        | "--tool-config-file"
418        | "-P"
419        | "--profile" => Some(!argument.contains('=')),
420        "--no-pager"
421        | "-v"
422        | "--verbose"
423        | "--workspace"
424        | "--all"
425        | "--lib"
426        | "--bins"
427        | "--examples"
428        | "--tests"
429        | "--benches"
430        | "--all-targets"
431        | "--all-features"
432        | "--no-default-features"
433        | "-r"
434        | "--release"
435        | "--unit-graph"
436        | "--frozen"
437        | "--locked"
438        | "--offline"
439        | "--cargo-quiet"
440        | "--cargo-verbose"
441        | "--ignore-rust-version"
442        | "--future-incompat-report"
443        | "--ignore-default-filter"
444        | "--override-version-check" => Some(false),
445        _ if argument.starts_with("-p") && argument.len() > 2 => Some(false),
446        _ if argument.starts_with("-F") && argument.len() > 2 => Some(false),
447        _ if argument.starts_with("-E") && argument.len() > 2 => Some(false),
448        _ if argument.starts_with("-P") && argument.len() > 2 => Some(false),
449        _ if argument.starts_with("-Z") && argument.len() > 2 => Some(false),
450        _ if argument.len() > 2
451            && argument.starts_with('-')
452            && argument[1..].bytes().all(|byte| byte == b'v') =>
453        {
454            Some(false)
455        }
456        _ if argument.starts_with("--timings=") => Some(false),
457        _ => None,
458    }
459}
460
461/// Reprojects a pinned `nextest run` invocation into the stable machine-readable
462/// `nextest list` contract. Selection/build/configuration arguments are kept
463/// byte-for-byte. Runner and presentation arguments are removed because they
464/// do not affect the selected-test catalog. Options whose selection semantics
465/// require an external recording or a different execution mode fail closed.
466pub(crate) fn nextest_list_invocation(
467    invocation: &CargoTestInvocation,
468) -> Result<NextestListInvocation, RustTestRunnerError> {
469    let command = invocation.command_position();
470    let mut arguments = invocation.arguments[..command].to_vec();
471    arguments.extend(["nextest".into(), "list".into()]);
472    let mut index = command + 2;
473    while index < invocation.arguments.len() {
474        let argument = &invocation.arguments[index];
475        if argument == "--" {
476            arguments.extend(invocation.arguments[index..].iter().cloned());
477            break;
478        }
479        if let Some(takes_value) = nextest_unsupported_run_option(argument) {
480            if takes_value && invocation.arguments.get(index + 1).is_none() {
481                return Err(RustTestRunnerError::UnsupportedCommand(format!(
482                    "nextest option {argument} has no value"
483                )));
484            }
485            return Err(RustTestRunnerError::UnsupportedCommand(format!(
486                "nextest option {argument} cannot yet be assigned exact selected-test identity"
487            )));
488        }
489        if let Some(takes_value) = nextest_run_only_option(argument) {
490            if takes_value {
491                index += 1;
492                if index == invocation.arguments.len() {
493                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
494                        "nextest option {argument} has no value"
495                    )));
496                }
497            }
498        } else if let Some(takes_value) = nextest_shared_option(argument) {
499            arguments.push(argument.clone());
500            if takes_value {
501                index += 1;
502                let value = invocation.arguments.get(index).ok_or_else(|| {
503                    RustTestRunnerError::UnsupportedCommand(format!(
504                        "nextest option {argument} has no value"
505                    ))
506                })?;
507                arguments.push(value.clone());
508            }
509        } else if argument.starts_with('-') {
510            return Err(RustTestRunnerError::UnsupportedCommand(format!(
511                "the pinned nextest run contract does not recognize option {argument}"
512            )));
513        } else {
514            arguments.push(argument.clone());
515        }
516        index += 1;
517    }
518    arguments.extend(["--message-format".into(), "json".into()]);
519    Ok(NextestListInvocation {
520        arguments,
521        runner_arguments: invocation.runner_arguments.clone(),
522    })
523}
524
525pub(crate) fn cargo_invocation(
526    root: &Path,
527    command: &[String],
528) -> Result<CargoTestInvocation, RustTestRunnerError> {
529    // A process argv is already tokenized. Joining and shell-parsing a direct
530    // Cargo command destroys quotes that are payload (not shell syntax), most
531    // notably TOML strings passed to Cargo's --config. Only opaque wrapper or
532    // package-script commands need textual expansion and shell tokenization.
533    let words = if command.iter().any(|word| executable_name(word) == "cargo") {
534        command.to_vec()
535    } else {
536        // A package script runs with the arguments after its name appended,
537        // so a filter given to `npm test -- adds` reaches Cargo.
538        let expanded = crate::project_discovery::script_command(root, command)
539            .unwrap_or_else(|| crate::project_discovery::expanded_command(root, command));
540        shell_words(&expanded)?
541    };
542    let cargo = words
543        .iter()
544        .position(|word| executable_name(word) == "cargo")
545        .ok_or_else(|| RustTestRunnerError::UnsupportedCommand(
546            "Rust was detected, but the expanded command does not expose a stable Cargo invocation".into(),
547        ))?;
548    let cargo_test = words[cargo + 1..]
549        .iter()
550        .position(|word| word == "test")
551        .map(|position| cargo + 1 + position);
552    let nextest = words[cargo + 1..]
553        .windows(2)
554        .position(|pair| pair == ["nextest", "run"])
555        .map(|position| cargo + 1 + position);
556    let (kind, command) = match (cargo_test, nextest) {
557        (Some(test), None) => (RustCargoCommandKind::CargoTest, test),
558        (None, Some(nextest)) => (RustCargoCommandKind::NextestRun, nextest),
559        (Some(_), Some(_)) => {
560            return Err(RustTestRunnerError::UnsupportedCommand(
561                "the Cargo invocation ambiguously contains both test and nextest run".into(),
562            ));
563        }
564        (None, None) => {
565            return Err(RustTestRunnerError::UnsupportedCommand(
566                "the owned Rust runner currently requires `cargo test` or `cargo nextest run`; cross remains explicitly unsupported"
567                    .into(),
568            ));
569        }
570    };
571    if words[cargo + 1..command]
572        .iter()
573        .any(|word| matches!(word.as_str(), "&&" | "||" | ";" | "|"))
574    {
575        return Err(RustTestRunnerError::UnsupportedCommand(
576            "the Cargo invocation contains a shell boundary before `test`".into(),
577        ));
578    }
579    let command_end = command
580        + if kind == RustCargoCommandKind::NextestRun {
581            1
582        } else {
583            0
584        };
585    let mut arguments = words[cargo + 1..=command_end].to_vec();
586    let mut runner_arguments = Vec::new();
587    let mut after_separator = false;
588    for argument in &words[command_end + 1..] {
589        if argument == "--" && !after_separator {
590            after_separator = true;
591            continue;
592        }
593        if matches!(argument.as_str(), "&&" | "||" | ";" | "|") {
594            return Err(RustTestRunnerError::UnsupportedCommand(
595                "the Cargo test command contains an unsupported shell boundary".into(),
596            ));
597        }
598        if after_separator {
599            runner_arguments.push(argument.clone());
600        } else {
601            arguments.push(argument.clone());
602        }
603    }
604    Ok(CargoTestInvocation {
605        program: words[cargo].clone(),
606        kind,
607        arguments,
608        runner_arguments,
609    })
610}
611
612fn cargo_option_takes_value(argument: &str) -> Option<bool> {
613    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
614    match name {
615        "-p" | "--package" | "--exclude" | "--bin" | "--example" | "--test" | "--bench" | "-F"
616        | "--features" | "-j" | "--jobs" | "--profile" | "--target" | "--target-dir"
617        | "--message-format" | "--color" | "--config" | "-Z" | "--manifest-path" => {
618            Some(!argument.contains('='))
619        }
620        "--no-run"
621        | "--no-fail-fast"
622        | "--future-incompat-report"
623        | "-q"
624        | "--quiet"
625        | "-v"
626        | "--verbose"
627        | "--workspace"
628        | "--all"
629        | "--lib"
630        | "--bins"
631        | "--examples"
632        | "--tests"
633        | "--benches"
634        | "--all-targets"
635        | "--doc"
636        | "--all-features"
637        | "--no-default-features"
638        | "-r"
639        | "--release"
640        | "--timings"
641        | "--ignore-rust-version"
642        | "--locked"
643        | "--offline"
644        | "--frozen" => Some(false),
645        _ if argument.starts_with("-vv") => Some(false),
646        _ if argument.starts_with("-p") && argument.len() > 2 => Some(false),
647        _ if argument.starts_with("-F") && argument.len() > 2 => Some(false),
648        _ if argument.starts_with("-j") && argument.len() > 2 => Some(false),
649        _ => None,
650    }
651}
652
653pub(crate) fn rust_libtest_selection(
654    invocation: &CargoTestInvocation,
655) -> Result<RustLibtestSelection, RustTestRunnerError> {
656    // `rust_cargo_execution_selection` has already refused an option Cargo's
657    // contract does not know and an option missing its value.
658    let mut cargo_filter = None;
659    let mut index = invocation.command_position() + 1;
660    while index < invocation.arguments.len() {
661        let argument = &invocation.arguments[index];
662        if argument.starts_with('-') {
663            if cargo_option_takes_value(argument) == Some(true) {
664                index += 1;
665            }
666        } else {
667            // The build has run by now, and Cargo refuses a second TESTNAME.
668            cargo_filter = Some(argument.clone());
669        }
670        index += 1;
671    }
672
673    let mut list_arguments = cargo_filter.into_iter().collect::<Vec<_>>();
674    let mut test_threads = None;
675    let mut index = 0;
676    while index < invocation.runner_arguments.len() {
677        let argument = &invocation.runner_arguments[index];
678        match argument.as_str() {
679            "--ignored" | "--include-ignored" | "--exclude-should-panic" | "--test" | "--bench" => {
680                list_arguments.push(argument.clone());
681            }
682            "--exact" => list_arguments.push(argument.clone()),
683            "--skip" => {
684                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
685                    RustTestRunnerError::UnsupportedCommand(
686                        "libtest --skip has no filter value".into(),
687                    )
688                })?;
689                list_arguments.extend([argument.clone(), value.clone()]);
690                index += 1;
691            }
692            _ if argument.starts_with("--skip=") && argument.len() > "--skip=".len() => {
693                list_arguments.push(argument.clone());
694            }
695            "--test-threads" => {
696                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
697                    RustTestRunnerError::UnsupportedCommand(
698                        "libtest --test-threads has no value".into(),
699                    )
700                })?;
701                let parsed = parse_libtest_threads(value)?;
702                if test_threads.replace(parsed).is_some() {
703                    return Err(RustTestRunnerError::UnsupportedCommand(
704                        "libtest --test-threads was provided more than once".into(),
705                    ));
706                }
707                index += 1;
708            }
709            _ if argument.starts_with("--test-threads=") => {
710                let value = &argument["--test-threads=".len()..];
711                let parsed = parse_libtest_threads(value)?;
712                if test_threads.replace(parsed).is_some() {
713                    return Err(RustTestRunnerError::UnsupportedCommand(
714                        "libtest --test-threads was provided more than once".into(),
715                    ));
716                }
717            }
718            "-Z" => {
719                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
720                    RustTestRunnerError::UnsupportedCommand(
721                        "libtest -Z has no feature value".into(),
722                    )
723                })?;
724                // `exclude-should-panic` and the other unstable selection
725                // flags must be parsed under the same libtest feature gate
726                // during discovery. The user's exact pair is also preserved
727                // unchanged for the real artifact execution.
728                list_arguments.extend([argument.clone(), value.clone()]);
729                index += 1;
730            }
731            _ if argument.starts_with("-Z") && argument.len() > 2 => {
732                list_arguments.push(argument.clone());
733            }
734            "--logfile" | "--color" | "--format" | "--shuffle-seed" => {
735                if invocation.runner_arguments.get(index + 1).is_none() {
736                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
737                        "libtest {argument} has no value"
738                    )));
739                }
740                // Presentation-only values must not leak into the synthetic
741                // terse listing. The original argument and value are passed
742                // byte-for-byte to the one stock artifact execution.
743                index += 1;
744            }
745            _ if ["--logfile=", "--color=", "--format=", "--shuffle-seed="]
746                .iter()
747                .any(|prefix| argument.starts_with(prefix) && argument.len() > prefix.len()) => {}
748            "--force-run-in-process"
749            | "--fail-fast"
750            | "--no-capture"
751            | "--nocapture"
752            | "-q"
753            | "--quiet"
754            | "--show-output"
755            | "--report-time"
756            | "--ensure-time"
757            | "--shuffle" => {
758                // These affect only scheduling, execution or presentation.
759                // They are intentionally absent from discovery and retained
760                // unchanged in the real artifact argv.
761            }
762            "--list" | "-h" | "--help" => {
763                return Err(RustTestRunnerError::UnsupportedCommand(format!(
764                    "libtest {argument} does not execute a test suite; exact non-execution mode support is not implemented"
765                )));
766            }
767            _ if !argument.starts_with('-') => list_arguments.push(argument.clone()),
768            _ => {
769                return Err(RustTestRunnerError::UnsupportedCommand(format!(
770                    "the pinned Rust 1.95 libtest discovery contract does not recognize option {argument}"
771                )));
772            }
773        }
774        index += 1;
775    }
776    Ok(RustLibtestSelection { list_arguments })
777}
778
779fn parse_libtest_threads(value: &str) -> Result<usize, RustTestRunnerError> {
780    match value.parse::<usize>() {
781        Ok(0) => Err(RustTestRunnerError::UnsupportedCommand(
782            "argument for --test-threads must not be 0".into(),
783        )),
784        Ok(value) => Ok(value),
785        Err(error) => Err(RustTestRunnerError::UnsupportedCommand(format!(
786            "argument for --test-threads must be a number > 0 (error: {error})"
787        ))),
788    }
789}
790
791pub(crate) fn rust_cargo_execution_selection(
792    invocation: &CargoTestInvocation,
793) -> Result<RustCargoExecutionSelection, RustTestRunnerError> {
794    if invocation.kind == RustCargoCommandKind::NextestRun {
795        return Ok(RustCargoExecutionSelection {
796            run_libtests: true,
797            run_doctests: false,
798            implicit_doctests: false,
799            doctest_arguments: Vec::new(),
800        });
801    }
802    let test = invocation.command_position();
803    let mut doc = false;
804    let mut other_target = false;
805    let mut index = test + 1;
806    while index < invocation.arguments.len() {
807        let argument = &invocation.arguments[index];
808        let name = argument
809            .split_once('=')
810            .map_or(argument.as_str(), |(name, _)| name);
811        match name {
812            "--doc" => doc = true,
813            "--lib" | "--bins" | "--bin" | "--examples" | "--example" | "--tests" | "--test"
814            | "--benches" | "--bench" | "--all-targets" => other_target = true,
815            _ => {}
816        }
817        if argument.starts_with('-') {
818            let takes_value = cargo_option_takes_value(argument).ok_or_else(|| {
819                RustTestRunnerError::UnsupportedCommand(format!(
820                    "the pinned Cargo test contract does not recognize option {argument}"
821                ))
822            })?;
823            if takes_value {
824                index += 1;
825                if index == invocation.arguments.len() {
826                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
827                        "Cargo option {argument} has no value"
828                    )));
829                }
830            }
831        }
832        index += 1;
833    }
834    if doc && other_target {
835        return Err(RustTestRunnerError::UnsupportedCommand(
836            "Cargo --doc cannot be combined with another explicit target selection".into(),
837        ));
838    }
839    let run_doctests = doc || !other_target;
840    let run_libtests = !doc;
841    let mut doctest_arguments = invocation.arguments.clone();
842    if run_doctests && !doc {
843        doctest_arguments.insert(test + 1, "--doc".into());
844    }
845    if !invocation.runner_arguments.is_empty() {
846        doctest_arguments.push("--".into());
847        doctest_arguments.extend(invocation.runner_arguments.iter().cloned());
848    }
849    Ok(RustCargoExecutionSelection {
850        run_libtests,
851        run_doctests,
852        implicit_doctests: run_doctests && !doc,
853        doctest_arguments,
854    })
855}
856
857pub(crate) fn relative_source(root: &Path, path: &Path) -> Result<String, RustTestRunnerError> {
858    let relative = path
859        .strip_prefix(root)
860        .map_err(|_| RustTestRunnerError::UnsafeArtifact(path.display().to_string()))?;
861    if relative.as_os_str().is_empty()
862        || relative
863            .components()
864            .any(|part| !matches!(part, Component::Normal(_)))
865    {
866        return Err(RustTestRunnerError::UnsafeArtifact(
867            path.display().to_string(),
868        ));
869    }
870    Ok(relative.to_string_lossy().replace('\\', "/"))
871}
872
873/// The build step decides three things for itself: it builds without running,
874/// reads Cargo's JSON, and builds into Supercov's own target directory. The
875/// command's own `--message-format` and `--target-dir` would collide with
876/// those, so they are left out of the build; `--no-run` asks for no test at
877/// all, which leaves nothing to measure.
878fn build_owned_arguments(arguments: Vec<String>) -> Result<Vec<String>, RustTestRunnerError> {
879    let mut kept = Vec::with_capacity(arguments.len());
880    let mut arguments = arguments.into_iter();
881    while let Some(argument) = arguments.next() {
882        let name = argument
883            .split_once('=')
884            .map_or(argument.as_str(), |(name, _)| name);
885        match name {
886            "--no-run" => {
887                return Err(RustTestRunnerError::UnsupportedCommand(
888                    "`--no-run` builds the tests without running any, so there is nothing to measure; run the command without it".into(),
889                ));
890            }
891            "--message-format" | "--target-dir" => {
892                if !argument.contains('=') {
893                    arguments.next();
894                }
895            }
896            _ => kept.push(argument),
897        }
898    }
899    Ok(kept)
900}
901
902fn build_test_artifacts(
903    project: &PreparedRustProject,
904    command: &[String],
905) -> Result<Vec<TestArtifact>, RustTestRunnerError> {
906    let mut invocation = cargo_invocation(&project.workspace_root, command)?;
907    let lint_invocation = invocation.clone();
908    invocation.arguments = build_owned_arguments(invocation.arguments)?;
909    invocation
910        .arguments
911        .extend(["--no-run".into(), "--message-format=json".into()]);
912    // The instrumented workspace is ephemeral and its sources are generated,
913    // so the HOST crate's lint policy must not reject them: serde builds with
914    // `#![deny(warnings)]`, and http's `if ({ frame ... })` decision wrapping
915    // trips `unused_parens` into a hard error under it. Capping lints to warn
916    // changes nothing about the user's own `cargo test` runs. The user's
917    // RUSTFLAGS are preserved ahead of the cap.
918    let output = Command::new(&invocation.program)
919        .args(invocation.arguments)
920        .current_dir(&project.workspace_root)
921        .env("CARGO_TARGET_DIR", &project.target_directory)
922        .env("RUSTFLAGS", capped_rustflags(project, &lint_invocation))
923        .output()
924        .map_err(|error| RustTestRunnerError::Launch(error.to_string()))?;
925    if !output.status.success() {
926        // With --message-format=json the compiler's diagnostics travel on
927        // stdout as JSON and Cargo's own summary on stderr; show both, or a
928        // failed build says only which crate failed.
929        let rendered = output
930            .stdout
931            .split(|byte| *byte == b'\n')
932            .filter_map(|line| serde_json::from_slice::<serde_json::Value>(line).ok())
933            .filter(|message| {
934                message["reason"] == "compiler-message" && message["message"]["level"] == "error"
935            })
936            .filter_map(|message| message["message"]["rendered"].as_str().map(str::to_owned))
937            .collect::<String>();
938        return Err(RustTestRunnerError::CargoFailed(format!(
939            "{rendered}{}",
940            String::from_utf8_lossy(&output.stderr).trim()
941        )));
942    }
943    let canonical_target = fs::canonicalize(&project.target_directory)
944        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
945    let mut artifacts = Vec::new();
946    for line in output
947        .stdout
948        .split(|byte| *byte == b'\n')
949        .filter(|line| !line.is_empty())
950    {
951        let message: CargoMessage = serde_json::from_slice(line)
952            .map_err(|error| RustTestRunnerError::CargoJson(error.to_string()))?;
953        if message.reason != "compiler-artifact"
954            || !message.profile.as_ref().is_some_and(|profile| profile.test)
955        {
956            continue;
957        }
958        let (Some(executable), Some(target)) = (message.executable, message.target) else {
959            continue;
960        };
961        let executable = fs::canonicalize(&executable)
962            .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
963        if !executable.starts_with(&canonical_target)
964            || !fs::metadata(&executable).is_ok_and(|metadata| metadata.is_file())
965        {
966            return Err(RustTestRunnerError::UnsafeArtifact(
967                executable.display().to_string(),
968            ));
969        }
970        let source = fs::canonicalize(target.src_path)
971            .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
972        let package_directory = message
973            .manifest_path
974            .as_deref()
975            .and_then(Path::parent)
976            .map_or_else(|| project.workspace_root.clone(), Path::to_path_buf);
977        artifacts.push(TestArtifact {
978            executable,
979            package_directory,
980            name: target.name,
981            kind: if target.kind.iter().any(|kind| kind == "test") {
982                "integration".into()
983            } else {
984                "unit".into()
985            },
986            source: relative_source(&project.workspace_root, &source)?,
987        });
988    }
989    artifacts.sort_by(|left, right| left.executable.cmp(&right.executable));
990    artifacts.dedup_by(|left, right| left.executable == right.executable);
991    if artifacts.is_empty() {
992        return Err(RustTestRunnerError::CargoJson(
993            "Cargo emitted no libtest artifacts".into(),
994        ));
995    }
996    Ok(artifacts)
997}
998
999/// How a test artifact runs: as itself, or through the runner Cargo would
1000/// run it with (`target.<triple>.runner`) -- `wasm-bindgen-test-runner` for a
1001/// wasm32 module, an emulator for another architecture. Running it any other
1002/// way runs something Cargo would not.
1003#[derive(Debug, Clone, Default)]
1004pub(crate) struct ArtifactLauncher {
1005    runner: Option<crate::rust_cargo_configuration::RustCargoResolvedRunner>,
1006    /// `NODE_OPTIONS` for a wasm module the runner runs under Node: a hook
1007    /// that writes each crate's region, which lives in the module's memory,
1008    /// to the test's evidence directory as the process ends.
1009    node_options: Option<OsString>,
1010}
1011
1012impl ArtifactLauncher {
1013    /// The launcher for the one target this invocation builds for.
1014    pub(crate) fn for_invocation(
1015        project: &PreparedRustProject,
1016        invocation: &CargoTestInvocation,
1017        hook_directory: &Path,
1018    ) -> Result<Self, RustTestRunnerError> {
1019        let Ok(plan) = crate::rust_cargo_configuration::resolve_cargo_runner_plan(
1020            &project.workspace_root,
1021            &project.workspace_root,
1022            invocation,
1023        ) else {
1024            return Ok(Self::default());
1025        };
1026        let [target] = plan.targets.as_slice() else {
1027            if plan
1028                .targets
1029                .iter()
1030                .any(|target| target.underlying_runner.is_some())
1031            {
1032                return Err(RustTestRunnerError::UnsupportedCommand(
1033                    "a command that builds for several targets, one of them with a target runner, cannot be run test by test".into(),
1034                ));
1035            }
1036            return Ok(Self::default());
1037        };
1038        let Some(runner) = &target.underlying_runner else {
1039            return Ok(Self::default());
1040        };
1041        let node_options = if target.target.starts_with("wasm32-") {
1042            fs::create_dir_all(hook_directory).map_err(io_error)?;
1043            let hook = hook_directory.join("rust-wasm-node.mjs");
1044            fs::write(&hook, RUST_WASM_NODE_HOOK).map_err(io_error)?;
1045            let mut options = std::env::var_os("NODE_OPTIONS").unwrap_or_default();
1046            if !options.is_empty() {
1047                options.push(" ");
1048            }
1049            options.push(format!("--import={}", file_url(&hook)));
1050            Some(options)
1051        } else {
1052            None
1053        };
1054        Ok(Self {
1055            runner: Some(runner.resolve(&project.workspace_root)),
1056            node_options,
1057        })
1058    }
1059
1060    /// The command that runs `executable`, writing its evidence to
1061    /// `evidence` when it is a test run.
1062    pub(crate) fn command(&self, executable: &Path, evidence: Option<&Path>) -> Command {
1063        let mut command = match &self.runner {
1064            Some(runner) => {
1065                let mut command = Command::new(&runner.program);
1066                command.args(&runner.arguments);
1067                // A WASI runtime gives the module neither the host's
1068                // directories nor its environment: the evidence directory,
1069                // and the variable naming it, are handed over by name.
1070                if let Some(evidence) = evidence
1071                    && is_wasi_runtime(&runner.program)
1072                {
1073                    command
1074                        .arg(format!("--dir={}", evidence.display()))
1075                        .arg(format!(
1076                            "--env=SUPERCOV_RUST_EVIDENCE_DIR={}",
1077                            evidence.display()
1078                        ));
1079                }
1080                command.arg(executable);
1081                command
1082            }
1083            None => Command::new(executable),
1084        };
1085        if let Some(options) = &self.node_options {
1086            command.env("NODE_OPTIONS", options);
1087        }
1088        command
1089    }
1090}
1091
1092/// Whether a runner program is a WASI runtime, by its name: wasmtime and
1093/// wasmer both take `--dir` and `--env` before the module.
1094fn is_wasi_runtime(program: &Path) -> bool {
1095    program
1096        .file_stem()
1097        .and_then(|name| name.to_str())
1098        .is_some_and(|name| matches!(name, "wasmtime" | "wasmer"))
1099}
1100
1101const RUST_WASM_NODE_HOOK: &str = include_str!("../runtime-assets/rust-wasm-node.mjs");
1102
1103/// A `file:` URL for a path, as `node --import` takes it: bytes outside the
1104/// unreserved set are percent-encoded, so a space or a `#` is the path's.
1105fn file_url(path: &Path) -> String {
1106    let mut url = String::from("file://");
1107    let text = path.to_string_lossy().replace('\\', "/");
1108    if !text.starts_with('/') {
1109        url.push('/');
1110    }
1111    for byte in text.bytes() {
1112        if byte.is_ascii_alphanumeric() || b"-._~/:".contains(&byte) {
1113            url.push(byte as char);
1114        } else {
1115            url.push_str(&format!("%{byte:02X}"));
1116        }
1117    }
1118    url
1119}
1120
1121/// The tests an artifact would run for this invocation. `filters` are the
1122/// user's libtest arguments that select tests -- a name, `--skip`, `--exact`,
1123/// `--ignored` -- and libtest applies them to `--list` exactly as it applies
1124/// them to a run, so the enumeration is the set plain Cargo would execute.
1125fn list_tests(
1126    launcher: &ArtifactLauncher,
1127    executable: &Path,
1128    filters: &[String],
1129    environment: &[(&'static str, OsString)],
1130) -> Result<Vec<String>, RustTestRunnerError> {
1131    let output = launcher
1132        .command(executable, None)
1133        .args(["--list", "--format", "terse"])
1134        .args(filters)
1135        .envs(environment.iter().map(|(key, value)| (key, value)))
1136        .output()
1137        .map_err(|error| RustTestRunnerError::Launch(error.to_string()))?;
1138    if !output.status.success() {
1139        // A libtest binary that dies on a signal writes nothing to stderr, so
1140        // reporting stderr alone produces an empty, undiagnosable message.
1141        // Name the artifact and how it ended, and fall back to stdout.
1142        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned();
1143        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned();
1144        let detail = if !stderr.is_empty() {
1145            stderr
1146        } else if !stdout.is_empty() {
1147            format!("no stderr; stdout was {stdout}")
1148        } else {
1149            "no output on either stream".to_owned()
1150        };
1151        return Err(RustTestRunnerError::ListFailed(format!(
1152            "{} exited with {} when asked to --list: {detail}",
1153            executable.display(),
1154            output.status
1155        )));
1156    }
1157    let mut tests = String::from_utf8_lossy(&output.stdout)
1158        .lines()
1159        .filter_map(|line| line.strip_suffix(": test"))
1160        .map(str::to_owned)
1161        .collect::<Vec<_>>();
1162    tests.sort();
1163    tests.dedup();
1164    Ok(tests)
1165}
1166
1167/// What one test process recorded: the coverage snapshot and the assertion
1168/// phases the evidence witnesses.
1169pub(crate) struct RustEvidence {
1170    pub(crate) snapshot: RuntimeSnapshot,
1171    pub(crate) phases: Vec<CoveragePhase>,
1172}
1173
1174/// One record a thread wrote, waiting to learn whether an assertion follows.
1175struct PendingRecord {
1176    event_type: &'static str,
1177    id: String,
1178    vector: Option<McdcVector>,
1179    sequence: i64,
1180}
1181
1182/// `attempt` names what this evidence belongs to, so its phase IDs are
1183/// unique across the run: every test runs in a process of its own and numbers
1184/// its records from one.
1185pub(crate) fn snapshot(
1186    manifest: &CoverageManifest,
1187    probes: &crate::rust_instrumenter::RustProbeLayout,
1188    directory: &Path,
1189    attempt: &str,
1190) -> Result<RustEvidence, RustTestRunnerError> {
1191    // Evidence files are named by the instrumentation that wrote them. A
1192    // test may build and run a program instrumented on its own -- a fixture
1193    // prepared inside the instrumented workspace -- and that program
1194    // inherits the evidence directory; its obligations are not this run's.
1195    let regions = read_rust_region_directory(directory, &probes.token, probes)
1196        .map_err(|error| RustTestRunnerError::Probe(error.to_string()))?
1197        .into_iter()
1198        .map(|(_, observations)| observations);
1199    snapshot_regions(manifest, regions, attempt)
1200}
1201
1202/// `snapshot` of regions already read: each one's records in the order its
1203/// process wrote them. A server's windows arrive this way.
1204pub(crate) fn snapshot_regions(
1205    manifest: &CoverageManifest,
1206    regions: impl IntoIterator<Item = Vec<crate::rust_runtime::RustProbeEntry>>,
1207    attempt: &str,
1208) -> Result<RustEvidence, RustTestRunnerError> {
1209    let points = manifest
1210        .points
1211        .iter()
1212        .map(|point| (point.id.as_str(), point))
1213        .collect::<BTreeMap<_, _>>();
1214    // An assertion in test code, which is not measured, names a site instead.
1215    let asserting = manifest
1216        .points
1217        .iter()
1218        .chain(&manifest.assertion_sites)
1219        .map(|point| (point.id.as_str(), point))
1220        .collect::<BTreeMap<_, _>>();
1221    let alternatives = manifest
1222        .branches
1223        .iter()
1224        .flat_map(|branch| {
1225            branch
1226                .alternatives
1227                .iter()
1228                .map(|alternative| alternative.id.as_str())
1229        })
1230        .collect::<BTreeSet<_>>();
1231    let decisions = manifest
1232        .decisions
1233        .iter()
1234        .map(|decision| (decision.id.as_str(), decision))
1235        .collect::<BTreeMap<_, _>>();
1236    let mut hits = BTreeSet::new();
1237    let mut vectors = BTreeMap::<String, BTreeSet<(Vec<Option<bool>>, bool)>>::new();
1238    // Records a thread wrote since its last assertion. An assertion witnesses
1239    // its own thread's records and no other's: a test that spawns threads
1240    // interleaves them in one file.
1241    let mut pending = BTreeMap::<u64, Vec<PendingRecord>>::new();
1242    let mut events = Vec::new();
1243    let mut phases = Vec::new();
1244    // Records carry no clock. The order they were written in is what matters,
1245    // and a sequence number preserves exactly that.
1246    let mut sequence = 0_i64;
1247    for observations in regions {
1248        for entry in observations {
1249            sequence += 1;
1250            let thread = entry.thread;
1251            match entry.observation {
1252                RustProbeObservation::Hit { id } => {
1253                    if !points.contains_key(id.as_str()) && !alternatives.contains(id.as_str()) {
1254                        return Err(RustTestRunnerError::UnknownProbe(id));
1255                    }
1256                    hits.insert(id.clone());
1257                    pending.entry(thread).or_default().push(PendingRecord {
1258                        event_type: "hit",
1259                        id,
1260                        vector: None,
1261                        sequence,
1262                    });
1263                }
1264                RustProbeObservation::Decision {
1265                    id,
1266                    values,
1267                    outcome,
1268                } => {
1269                    let Some(meta) = decisions.get(id.as_str()) else {
1270                        return Err(RustTestRunnerError::UnknownProbe(id));
1271                    };
1272                    if values.len() != meta.conditions.len() {
1273                        return Err(RustTestRunnerError::InvalidVector {
1274                            id,
1275                            expected: meta.conditions.len(),
1276                            actual: values.len(),
1277                        });
1278                    }
1279                    hits.insert(format!(
1280                        "{}:outcome:{}",
1281                        meta.id,
1282                        if outcome { "true" } else { "false" }
1283                    ));
1284                    vectors
1285                        .entry(meta.id.clone())
1286                        .or_default()
1287                        .insert((values.clone(), outcome));
1288                    pending.entry(thread).or_default().push(PendingRecord {
1289                        event_type: "decision",
1290                        id,
1291                        vector: Some(McdcVector { values, outcome }),
1292                        sequence,
1293                    });
1294                }
1295                RustProbeObservation::Assertion { id } => {
1296                    // The marker names the statement that asserts: a point
1297                    // of this manifest, or an assertion site in test code.
1298                    let Some(point) = asserting.get(id.as_str()) else {
1299                        return Err(RustTestRunnerError::UnknownProbe(id));
1300                    };
1301                    let witnessed = pending.remove(&thread).unwrap_or_default();
1302                    let phase_id = format!("{attempt}:assertion:{sequence}");
1303                    phases.push(CoveragePhase {
1304                        id: phase_id.clone(),
1305                        kind: "assertion".into(),
1306                        operation: format!(
1307                            "Rust assertion at {}:{}:{}",
1308                            point.file, point.line, point.column
1309                        ),
1310                        source: Some(point.source.clone()),
1311                        caused_by_phase_id: None,
1312                        started_at_ms: witnessed.first().map_or(sequence, |record| record.sequence),
1313                        ended_at_ms: Some(sequence),
1314                        // Reaching the marker is the proof: a failing
1315                        // assertion panics before it.
1316                        status: Some("passed".into()),
1317                        error: None,
1318                    });
1319                    for record in witnessed {
1320                        events.push(RuntimeEvent {
1321                            event_type: record.event_type.into(),
1322                            id: record.id,
1323                            vector: record.vector,
1324                            timestamp_ms: record.sequence,
1325                            phase_id: Some(phase_id.clone()),
1326                            statement_id: None,
1327                            environment: "server".into(),
1328                        });
1329                    }
1330                }
1331            }
1332        }
1333    }
1334    let mut decision_snapshots = Vec::new();
1335    for (id, observed) in vectors {
1336        let meta: DecisionMeta = (*decisions[id.as_str()]).clone();
1337        decision_snapshots.push(DecisionSnapshot {
1338            meta,
1339            vectors: observed
1340                .into_iter()
1341                .map(|(values, outcome)| McdcVector { values, outcome })
1342                .collect(),
1343        });
1344    }
1345    Ok(RustEvidence {
1346        snapshot: RuntimeSnapshot {
1347            decisions: decision_snapshots,
1348            hits: hits.into_iter().collect(),
1349            events,
1350            logicals: Vec::new(),
1351            phase_id: None,
1352        },
1353        phases,
1354    })
1355}
1356
1357pub(crate) fn rust_coverage_model() -> CoverageModelDeclaration {
1358    CoverageModelDeclaration {
1359        language: "rust".into(),
1360        variant: "rust-owned-probes-v1".into(),
1361        name: "supercov-rust-owned-v1".into(),
1362        completeness_meaning: "Every semantics-proven Rust obligation in the owned source denominator was observed; explicit manifest limitations identify unmeasured Rust surfaces.".into(),
1363        measured: vec![
1364            "owned Rust statements and function entries".into(),
1365            "owned atomic condition vectors and decision outcomes".into(),
1366            "exact process-per-libtest attribution".into(),
1367            "exact process-per-doctest attribution".into(),
1368            "evidence a passing assertion of the same thread witnessed".into(),
1369        ],
1370        not_measured: vec![
1371            "macro-expanded and generated Rust code".into(),
1372            "const-evaluated code and unsupported structural branch probes".into(),
1373            "causal linkage to individual actions".into(),
1374            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
1375            "mutation score or assertion fault-detection strength".into(),
1376        ],
1377    }
1378}
1379
1380/// The user's RUSTFLAGS, then a cap so the HOST crate's lint policy cannot
1381/// reject generated sources: serde builds with `#![deny(warnings)]`, and
1382/// http's `if ({ frame ... })` decision wrapping trips `unused_parens` into a
1383/// hard error under it. The user's own `cargo test` runs are unaffected.
1384/// The stack an instrumented test thread gets. Probes add stack to every
1385/// frame -- a decision frame, a `?` operand copied through its probe, a
1386/// wrapper's temporaries -- and a test that recurses to a depth chosen against
1387/// the default 2 MiB (serde_json's recursion-limit test) overflows under
1388/// instrumentation. libtest sizes test threads from `RUST_MIN_STACK`, so an
1389/// instrumented process gets 16 MiB unless the user chose a size. The
1390/// reservation is virtual; only touched pages cost memory.
1391pub(crate) fn instrumented_stack_environment() -> Vec<(&'static str, &'static str)> {
1392    if std::env::var_os("RUST_MIN_STACK").is_some() {
1393        Vec::new()
1394    } else {
1395        vec![("RUST_MIN_STACK", "16777216")]
1396    }
1397}
1398
1399/// One path the selected toolchain reports through `rustc --print`.
1400fn rustc_print_path(request: &str) -> Result<PathBuf, RustTestRunnerError> {
1401    let output = Command::new("rustc")
1402        .args(["--print", request])
1403        .output()
1404        .map_err(|error| {
1405            RustTestRunnerError::Launch(format!("rustc --print {request}: {error}"))
1406        })?;
1407    if !output.status.success() {
1408        return Err(RustTestRunnerError::Launch(format!(
1409            "rustc --print {request} exited with {}",
1410            output.status
1411        )));
1412    }
1413    Ok(PathBuf::from(
1414        String::from_utf8_lossy(&output.stdout).trim(),
1415    ))
1416}
1417
1418/// The selected toolchain's sysroot, as `rustc --print sysroot` reports it.
1419pub(crate) fn rustc_sysroot() -> Result<PathBuf, RustTestRunnerError> {
1420    rustc_print_path("sysroot")
1421}
1422
1423/// The directory holding the toolchain's own dynamic libraries
1424/// (`<sysroot>/lib/rustlib/<host>/lib`), where libstd's dylib lives; the
1425/// plain `<sysroot>/lib` no longer holds it.
1426pub(crate) fn rustc_target_libdir() -> Result<PathBuf, RustTestRunnerError> {
1427    rustc_print_path("target-libdir")
1428}
1429
1430/// The dynamic library search path Cargo gives a test binary it runs: the
1431/// artifact's own directories and the toolchain's target library directory.
1432/// A proc-macro crate's test harness links libstd dynamically -- async-trait's
1433/// and serde_derive's cannot even list their tests without this -- and any
1434/// dylib dependency is found the same way. The user's own path follows.
1435pub(crate) fn dynamic_library_environment(
1436    target_libdir: &Path,
1437    executable: &Path,
1438) -> Vec<(&'static str, OsString)> {
1439    let variable = if cfg!(target_os = "macos") {
1440        "DYLD_FALLBACK_LIBRARY_PATH"
1441    } else if cfg!(windows) {
1442        "PATH"
1443    } else {
1444        "LD_LIBRARY_PATH"
1445    };
1446    let mut entries = Vec::new();
1447    if let Some(deps) = executable.parent() {
1448        entries.push(deps.to_path_buf());
1449        if let Some(profile) = deps.parent() {
1450            entries.push(profile.to_path_buf());
1451        }
1452    }
1453    entries.push(target_libdir.to_path_buf());
1454    if let Some(existing) = std::env::var_os(variable) {
1455        entries.extend(std::env::split_paths(&existing));
1456    }
1457    match std::env::join_paths(entries) {
1458        Ok(value) => vec![(variable, value)],
1459        Err(_) => Vec::new(),
1460    }
1461}
1462
1463/// The compiler's flags for what Supercov builds: the project's own, with no
1464/// lint reported. What is compiled is the rewritten source, and a lint on it
1465/// is about Supercov's text: `cargo nextest run`, which shows its build,
1466/// printed five warnings of unnecessary parentheses for a ten-line file, each
1467/// quoting a line of probes. A project that denies warnings still builds.
1468///
1469/// Building for wasm, lints are capped at `warn` instead: Cargo learns which
1470/// crate types a target supports from rustc's "dropping unsupported crate
1471/// type" warnings, and wasm32 drops `dylib` and `proc-macro`, so with every
1472/// lint allowed it cannot read the answer and stops. The probes'
1473/// parentheses, the one lint they raise, are allowed by name.
1474pub(crate) fn capped_rustflags(
1475    project: &PreparedRustProject,
1476    invocation: &CargoTestInvocation,
1477) -> String {
1478    let wasm = crate::rust_cargo_configuration::resolve_cargo_runner_plan(
1479        &project.workspace_root,
1480        &project.workspace_root,
1481        invocation,
1482    )
1483    .is_ok_and(|plan| {
1484        plan.targets
1485            .iter()
1486            .any(|target| target.target.starts_with("wasm"))
1487    });
1488    let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
1489    if !rustflags.is_empty() {
1490        rustflags.push(' ');
1491    }
1492    rustflags.push_str(if wasm {
1493        "--cap-lints=warn -A unused_parens"
1494    } else {
1495        "--cap-lints=allow"
1496    });
1497    rustflags
1498}
1499
1500pub(crate) fn io_error(error: impl std::fmt::Display) -> RustTestRunnerError {
1501    RustTestRunnerError::Io(error.to_string())
1502}
1503
1504/// A libtest case that exited cleanly but ran nothing, or ran its one test as
1505/// ignored, was skipped rather than passed.
1506/// Cargo's line for a test binary: `Running unittests src/lib.rs (target/…)`.
1507/// The source is named from its package and the binary from the workspace, as
1508/// Cargo names them.
1509fn running_line(
1510    workspace: &Path,
1511    target: &Path,
1512    artifact: &TestArtifact,
1513    coloured: bool,
1514) -> String {
1515    // Both as text with one separator and no `\\?\` prefix, which Windows
1516    // puts on one of them and not the other: the source then came out
1517    // whole and with `/`, `tests/outside.rs` where Cargo writes
1518    // `tests\outside.rs`.
1519    let text = |path: &Path| {
1520        let text = path.to_string_lossy().replace('\\', "/");
1521        text.strip_prefix("//?/").unwrap_or(&text).to_owned()
1522    };
1523    let package = text(&artifact.package_directory);
1524    let package = package
1525        .strip_prefix(&text(workspace))
1526        .map_or("", |inside| inside.trim_matches('/'));
1527    let source = artifact
1528        .source
1529        .strip_prefix(package)
1530        .map_or(artifact.source.as_str(), |local| {
1531            local.trim_start_matches('/')
1532        })
1533        .split('/')
1534        .collect::<PathBuf>();
1535    // Supercov builds into a target directory of its own; the binary is named
1536    // where Cargo would have put it.
1537    let executable = artifact.executable.strip_prefix(target).map_or_else(
1538        |_| artifact.executable.clone(),
1539        |built| Path::new("target").join(built),
1540    );
1541    format!(
1542        "{} {}{} ({})",
1543        if coloured {
1544            "\u{1b}[1m\u{1b}[92m     Running\u{1b}[0m"
1545        } else {
1546            "     Running"
1547        },
1548        if artifact.kind == "integration" {
1549            ""
1550        } else {
1551            "unittests "
1552        },
1553        source.display(),
1554        executable.display()
1555    )
1556}
1557
1558/// Whether Cargo colours its own lines: as `--color` or `CARGO_TERM_COLOR`
1559/// says, and otherwise when they go to a terminal that has not asked for
1560/// none.
1561fn cargo_colours(invocation: &CargoTestInvocation) -> bool {
1562    use std::io::IsTerminal;
1563    let arguments = &invocation.arguments;
1564    let asked = arguments
1565        .iter()
1566        .enumerate()
1567        .find_map(|(index, argument)| {
1568            argument
1569                .strip_prefix("--color=")
1570                .map(str::to_owned)
1571                .or_else(|| (argument == "--color").then(|| arguments.get(index + 1).cloned())?)
1572        })
1573        .or_else(|| std::env::var("CARGO_TERM_COLOR").ok());
1574    let set = |name: &str| std::env::var(name).ok().filter(|value| !value.is_empty());
1575    match asked.as_deref() {
1576        Some("always") => true,
1577        Some("never") => false,
1578        // As Cargo decides it when left to: forced, refused, and otherwise
1579        // for a terminal that can show colours.
1580        _ if set("CLICOLOR_FORCE").is_some_and(|value| value != "0") => true,
1581        _ if set("NO_COLOR").is_some() || set("CLICOLOR").is_some_and(|value| value == "0") => {
1582            false
1583        }
1584        _ => {
1585            std::io::stderr().is_terminal()
1586                && (cfg!(windows) || set("TERM").is_some_and(|term| term != "dumb"))
1587        }
1588    }
1589}
1590
1591/// Whether libtest colours its results: as its own `--color` says, and
1592/// otherwise when they go to a terminal that can show colours. A test's
1593/// process writes to a pipe, so it is told.
1594fn libtest_colours(invocation: &CargoTestInvocation) -> bool {
1595    use std::io::IsTerminal;
1596    let arguments = &invocation.runner_arguments;
1597    let asked = arguments.iter().enumerate().find_map(|(index, argument)| {
1598        argument
1599            .strip_prefix("--color=")
1600            .map(str::to_owned)
1601            .or_else(|| (argument == "--color").then(|| arguments.get(index + 1).cloned())?)
1602    });
1603    match asked.as_deref() {
1604        Some("always") => true,
1605        Some("never") => false,
1606        _ => {
1607            std::io::stdout().is_terminal()
1608                && (cfg!(windows)
1609                    || std::env::var("TERM").is_ok_and(|term| !term.is_empty() && term != "dumb"))
1610        }
1611    }
1612}
1613
1614/// `cargo test -q`.
1615pub(crate) fn cargo_quiet(invocation: &CargoTestInvocation) -> bool {
1616    invocation.arguments[invocation.command_position() + 1..]
1617        .iter()
1618        .any(|argument| matches!(argument.as_str(), "-q" | "--quiet"))
1619}
1620
1621/// libtest's own `-q` or `--format terse`: a character for each test.
1622pub(crate) fn libtest_quiet(invocation: &CargoTestInvocation) -> bool {
1623    let runner = &invocation.runner_arguments;
1624    runner
1625        .iter()
1626        .any(|argument| matches!(argument.as_str(), "-q" | "--quiet" | "--format=terse"))
1627        || runner
1628            .windows(2)
1629            .any(|pair| pair[0] == "--format" && pair[1] == "terse")
1630}
1631
1632pub(crate) fn libtest_skipped(exit: i32, stdout: &str) -> bool {
1633    exit == 0 && (stdout.contains("running 0 tests") || stdout.contains("; 1 ignored;"))
1634}
1635
1636/// Limitation IDs are unique across a declaration, so each runner names its
1637/// own: the libtest runner keeps the original IDs, rustdoc's carry its name.
1638fn rust_runner_limitations(runner: &str) -> Vec<FrontendLimitation> {
1639    let prefix = if runner == "rust-libtest" {
1640        "rust".to_owned()
1641    } else {
1642        runner.to_owned()
1643    };
1644    vec![
1645        FrontendLimitation {
1646            id: format!("{prefix}-action-linkage-unavailable"),
1647            scopes: vec![FrontendLimitationScope::Action],
1648            reason: "Rust test frameworks expose no general action lifecycle".into(),
1649        },
1650        FrontendLimitation {
1651            id: format!("{prefix}-assertion-linkage-unavailable"),
1652            scopes: vec![FrontendLimitationScope::Assertion],
1653            reason: "assertion macros do not expose a stable per-assertion success lifecycle"
1654                .into(),
1655        },
1656    ]
1657}
1658
1659pub(crate) fn rust_runner_declaration(runner: &str) -> FrontendRunnerDeclaration {
1660    FrontendRunnerDeclaration {
1661        runner: runner.into(),
1662        execution_model: ExecutionModel::ProcessPerTest,
1663        attribution: FrontendAttribution {
1664            run: AttributionPrecision::Exact,
1665            worker: AttributionPrecision::Exact,
1666            test: AttributionPrecision::Exact,
1667            retry: AttributionPrecision::Exact,
1668            phase: AttributionPrecision::Exact,
1669            action: AttributionPrecision::Unavailable,
1670            assertion: AttributionPrecision::Unavailable,
1671        },
1672        limitations: rust_runner_limitations(runner),
1673    }
1674}
1675
1676/// Obligations in files the build never read, declined rather than counted.
1677/// Returns the manifest to report against and the files it declined.
1678///
1679/// The obligations stay in the manifest: the evidence files are named by a
1680/// token derived from its obligation IDs, so removing them would orphan every
1681/// record the run wrote. `unmeasured` is what the report reads to take an
1682/// obligation out of the covered/uncovered denominator without pretending it
1683/// was never there.
1684fn decline_uncompiled_sources(
1685    project: &PreparedRustProject,
1686    also_compiled: &BTreeSet<String>,
1687) -> (CoverageManifest, BTreeSet<String>) {
1688    let mut manifest = project.manifest.clone();
1689    let Some(compiled) = crate::rust_project::compiled_source_files(
1690        &project.workspace_root,
1691        &project.target_directory,
1692    ) else {
1693        return (manifest, BTreeSet::new());
1694    };
1695    let declined = project
1696        .source_files
1697        .iter()
1698        .filter(|file| !compiled.contains(*file) && !also_compiled.contains(*file))
1699        .cloned()
1700        .collect::<BTreeSet<_>>();
1701    // Depinfo that accounts for no crate root at all is depinfo this run did
1702    // not produce; declining on it would empty the denominator.
1703    if declined.is_empty()
1704        || project
1705            .crate_roots
1706            .iter()
1707            .all(|root| !compiled.contains(root))
1708    {
1709        return (manifest, BTreeSet::new());
1710    }
1711    let mut unmeasured = manifest.unmeasured.iter().cloned().collect::<BTreeSet<_>>();
1712    unmeasured.extend(
1713        manifest
1714            .points
1715            .iter()
1716            .filter(|point| declined.contains(&point.file))
1717            .map(|point| point.id.clone()),
1718    );
1719    unmeasured.extend(
1720        manifest
1721            .decisions
1722            .iter()
1723            .filter(|decision| declined.contains(&decision.file))
1724            .map(|decision| decision.id.clone()),
1725    );
1726    unmeasured.extend(
1727        manifest
1728            .branches
1729            .iter()
1730            .filter(|branch| declined.contains(&branch.file))
1731            .map(|branch| branch.id.clone()),
1732    );
1733    manifest.unmeasured = unmeasured.into_iter().collect();
1734    // A boundary declared inside a file that was never compiled says nothing.
1735    manifest.limitations.retain(|limitation| {
1736        limitation
1737            .get("file")
1738            .and_then(|file| file.as_str())
1739            .is_none_or(|file| !declined.contains(file))
1740    });
1741    // And the scope says so, file by file.
1742    if let Some(entries) = manifest
1743        .scope
1744        .as_mut()
1745        .and_then(|scope| scope.get_mut("entries"))
1746        .and_then(serde_json::Value::as_array_mut)
1747    {
1748        for entry in entries {
1749            let named = entry
1750                .get("file")
1751                .and_then(serde_json::Value::as_str)
1752                .is_some_and(|file| declined.contains(file));
1753            if named && let Some(entry) = entry.as_object_mut() {
1754                entry.insert("status".into(), "excluded".into());
1755                entry.insert(
1756                    "reason".into(),
1757                    crate::rust_project::RUST_NOT_COMPILED.into(),
1758                );
1759            }
1760        }
1761    }
1762    (manifest, declined)
1763}
1764
1765/// Take out the code a `#[cfg]` kept from every build that ran. Each crate's
1766/// runtime wrote what `cfg!` said of the predicates in its files; code no
1767/// build compiled could never have run, as the code of a file no build read
1768/// (`decline_uncompiled_sources`). A gate no process of its crate answered
1769/// for stays counted, and is said to.
1770fn settle_cfg_gates(
1771    project: &PreparedRustProject,
1772    evidence_root: &Path,
1773    diagnostics: &mut dyn Write,
1774) -> Result<CoverageManifest, RustTestRunnerError> {
1775    let probes = &project.probes;
1776    if probes.cfgs.is_empty() {
1777        return Ok(project.manifest.clone());
1778    }
1779    let settled =
1780        crate::rust_runtime::read_rust_cfg_settlement(evidence_root, &probes.token, probes)
1781            .map_err(|error| RustTestRunnerError::Probe(error.to_string()))?;
1782    settle_cfg_gates_with(&project.manifest, probes, &settled, diagnostics)
1783}
1784
1785/// `settle_cfg_gates` with what the builds said already gathered: a byte per
1786/// gate, bit 0 left out somewhere, bit 1 built somewhere.
1787pub(crate) fn settle_cfg_gates_with(
1788    manifest: &CoverageManifest,
1789    probes: &crate::rust_instrumenter::RustProbeLayout,
1790    settled: &[u8],
1791    diagnostics: &mut dyn Write,
1792) -> Result<CoverageManifest, RustTestRunnerError> {
1793    let mut manifest = manifest.clone();
1794    let left_out = probes
1795        .cfgs
1796        .iter()
1797        .zip(settled)
1798        .filter(|(_, bits)| **bits == 1)
1799        .map(|(gate, _)| gate)
1800        .collect::<Vec<_>>();
1801    let removed = left_out
1802        .iter()
1803        .flat_map(|gate| gate.obligations.iter().map(String::as_str))
1804        .collect::<BTreeSet<_>>();
1805    let unanswered = probes
1806        .cfgs
1807        .iter()
1808        .zip(settled)
1809        .filter(|(gate, bits)| {
1810            **bits == 0
1811                && gate
1812                    .obligations
1813                    .iter()
1814                    .any(|id| !removed.contains(id.as_str()))
1815        })
1816        .map(|(gate, _)| gate)
1817        .collect::<Vec<_>>();
1818    let place = |gate: &&crate::rust_instrumenter::RustCfgGate| {
1819        format!("{}:{} cfg({})", gate.file, gate.line, gate.predicate)
1820    };
1821    let listed = |gates: &[&crate::rust_instrumenter::RustCfgGate]| {
1822        let mut text = gates
1823            .iter()
1824            .take(3)
1825            .map(place)
1826            .collect::<Vec<_>>()
1827            .join(", ");
1828        if gates.len() > 3 {
1829            text.push_str(", ...");
1830        }
1831        text
1832    };
1833    if !left_out.is_empty() {
1834        writeln!(
1835            diagnostics,
1836            "[supercov] {} item(s) behind a #[cfg] no build compiled are outside the denominator: {}",
1837            left_out.len(),
1838            listed(&left_out)
1839        )
1840        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1841    }
1842    if !unanswered.is_empty() {
1843        writeln!(
1844            diagnostics,
1845            "[supercov] {} item(s) behind a #[cfg] are counted though no test ran code of their crate to show whether it was built: {}",
1846            unanswered.len(),
1847            listed(&unanswered)
1848        )
1849        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1850    }
1851    if removed.is_empty() {
1852        return Ok(manifest);
1853    }
1854    manifest
1855        .points
1856        .retain(|point| !removed.contains(point.id.as_str()));
1857    manifest
1858        .decisions
1859        .retain(|decision| !removed.contains(decision.id.as_str()));
1860    manifest
1861        .branches
1862        .retain(|branch| !removed.contains(branch.id.as_str()));
1863    manifest
1864        .unmeasured
1865        .retain(|id| !removed.contains(id.as_str()));
1866    // A boundary declared inside code no build compiled says nothing.
1867    manifest.limitations.retain(|limitation| {
1868        let file = limitation.get("file").and_then(serde_json::Value::as_str);
1869        let line = limitation
1870            .get("line")
1871            .and_then(serde_json::Value::as_u64)
1872            .map(|line| line as usize);
1873        !left_out.iter().any(|gate| {
1874            file == Some(gate.file.as_str())
1875                && line.is_some_and(|line| (gate.line..=gate.end_line).contains(&line))
1876        })
1877    });
1878    Ok(manifest)
1879}
1880
1881/// The manifest's structural limitation IDs, as the declaration references them.
1882pub(crate) fn structural_limitations(manifest: &CoverageManifest) -> Vec<String> {
1883    manifest
1884        .limitations
1885        .iter()
1886        .filter_map(|item| {
1887            item.get("id")
1888                .and_then(|value| value.as_str())
1889                .map(str::to_owned)
1890        })
1891        .collect()
1892}
1893
1894/// Says which files the build left out of the denominator.
1895fn report_declined(
1896    declined: &BTreeSet<String>,
1897    diagnostics: &mut dyn Write,
1898) -> Result<(), RustTestRunnerError> {
1899    if declined.is_empty() {
1900        return Ok(());
1901    }
1902    writeln!(
1903        diagnostics,
1904        "[supercov] {} source file(s) this build did not compile are outside the denominator: {}{}",
1905        declined.len(),
1906        declined
1907            .iter()
1908            .take(3)
1909            .cloned()
1910            .collect::<Vec<_>>()
1911            .join(", "),
1912        if declined.len() > 3 { ", ..." } else { "" }
1913    )
1914    .map_err(|error| RustTestRunnerError::Io(error.to_string()))
1915}
1916
1917/// `rust_workers`: the stand-in for `wrangler` the test processes find
1918/// first, when the workspace has a Rust Worker a test may start.
1919pub fn run_prepared_rust_tests(
1920    project: &PreparedRustProject,
1921    command: &[String],
1922    run_id: &str,
1923    generated_at: &str,
1924    rust_workers: Option<&crate::rust_worker::CargoTestWorkers>,
1925    diagnostics: &mut dyn Write,
1926) -> Result<RustFrontendRun, RustTestRunnerError> {
1927    let invocation = cargo_invocation(&project.workspace_root, command)?;
1928    let selection = rust_cargo_execution_selection(&invocation)?;
1929    let build_started = Instant::now();
1930    // `cargo test --doc` alone builds nothing here: Cargo refuses `--no-run`
1931    // with `--doc`, and the doctest phase below builds what it runs.
1932    // nextest builds for itself; Cargo builds the libtest artifacts here.
1933    let artifacts = if selection.run_libtests && invocation.kind == RustCargoCommandKind::CargoTest
1934    {
1935        build_test_artifacts(project, command)?
1936    } else {
1937        Vec::new()
1938    };
1939    let build_ms = build_started.elapsed().as_secs_f64() * 1000.0;
1940    // The denominator is what the compiler built, not what the module tree
1941    // resolves: a module behind a `#[cfg]` that is off is resolved, never
1942    // compiled, and could never be covered.
1943    let (manifest, declined) = decline_uncompiled_sources(project, &BTreeSet::new());
1944    let original = project;
1945    let project = &PreparedRustProject {
1946        manifest,
1947        ..project.clone()
1948    };
1949    // A signal that reaches the runner alone must not leave the test
1950    // processes running: on 2026-09-07 an orphaned tokio test spun on two
1951    // cores for an hour. The guard covers every child spawned below.
1952    let _signal_guard = crate::child_signal_guard::ChildSignalGuard::install()
1953        .map_err(|error| RustTestRunnerError::Launch(error.to_string()))?;
1954    let evidence_root = project
1955        .workspace_root
1956        .join(".supercov/rust-evidence")
1957        .join(run_id);
1958    fs::create_dir_all(&evidence_root)
1959        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1960    let mut results = Vec::new();
1961    let mut overall_exit = 0;
1962    let execution_started = Instant::now();
1963    if invocation.kind == RustCargoCommandKind::NextestRun {
1964        // nextest builds, schedules and retries on its own; this program is
1965        // its target runner and records every attempt it launches.
1966        report_declined(&declined, diagnostics)?;
1967        let outcome = crate::rust_owned_nextest::run_nextest(
1968            project,
1969            &invocation,
1970            &evidence_root.join("nextest"),
1971            run_id,
1972            diagnostics,
1973        )?;
1974        let artifact_count = outcome.artifact_files.len();
1975        let project = &PreparedRustProject {
1976            manifest: settle_cfg_gates(project, &evidence_root, diagnostics)?,
1977            ..project.clone()
1978        };
1979        return Ok(RustFrontendRun {
1980            declaration: FrontendRunDeclaration {
1981                protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
1982                frontend_id: "rust".into(),
1983                frontend_version: "rust-owned-v1".into(),
1984                language: "rust".into(),
1985                structural_source: StructuralSource::OwnedProbes,
1986                runners: vec![rust_runner_declaration("nextest")],
1987                structural_limitations: structural_limitations(&project.manifest),
1988            },
1989            request: CoverageReportRequest {
1990                run_id: run_id.into(),
1991                manifest: project.manifest.clone(),
1992                raw_results: outcome.results,
1993                generated_at: generated_at.into(),
1994                coverage_model: Some(rust_coverage_model()),
1995                integrity: None,
1996                test_exit_code: ExitCodeInput::Present(Some(outcome.exit_code)),
1997            },
1998            exit_code: outcome.exit_code,
1999            artifacts: artifact_count,
2000            artifact_files: outcome.artifact_files,
2001            build_ms,
2002            execution_ms: execution_started.elapsed().as_secs_f64() * 1000.0,
2003        });
2004    }
2005    // The user's libtest filters, validated against the discovery contract.
2006    // This was computed and never used: every artifact was listed bare, and
2007    // Supercov ran tests plain Cargo had been told to skip. It is read here,
2008    // after the nextest branch: a nextest command carries no libtest
2009    // selection to reconstruct, and nextest applies the user's filters itself.
2010    let libtest_selection = rust_libtest_selection(&invocation)?;
2011    // An ignored test the user asked for with `--ignored` is listed, and must
2012    // then run: `--exact name` alone would report it ignored again.
2013    let coloured_results = libtest_colours(&invocation);
2014    let ignored_mode = libtest_selection
2015        .list_arguments
2016        .iter()
2017        .filter(|argument| {
2018            matches!(
2019                argument.as_str(),
2020                "--ignored" | "--include-ignored" | "--exclude-should-panic"
2021            )
2022        })
2023        .cloned()
2024        .collect::<Vec<_>>();
2025    let mut tasks = Vec::new();
2026    // Through the target runner the user configured, when there is one.
2027    let launcher =
2028        ArtifactLauncher::for_invocation(project, &invocation, &evidence_root.join("hooks"))?;
2029    let target_libdir = if artifacts.is_empty() {
2030        PathBuf::new()
2031    } else {
2032        rustc_target_libdir()?
2033    };
2034    // A binary none of whose tests were asked for, and how many it has.
2035    let mut unasked = Vec::new();
2036    for (artifact_index, artifact) in artifacts.iter().enumerate() {
2037        let tests = list_tests(
2038            &launcher,
2039            &artifact.executable,
2040            &libtest_selection.list_arguments,
2041            &dynamic_library_environment(&target_libdir, &artifact.executable),
2042        )?;
2043        if tests.is_empty() {
2044            let all = list_tests(
2045                &launcher,
2046                &artifact.executable,
2047                &["--include-ignored".to_owned()],
2048                &dynamic_library_environment(&target_libdir, &artifact.executable),
2049            )?;
2050            unasked.push((artifact, all.len()));
2051        }
2052        let contexts = preflight_rust_test_contexts(tests.clone())
2053            .map_err(|error| RustTestRunnerError::Context(error.to_string()))?;
2054        for (test_index, test) in tests.into_iter().enumerate() {
2055            let directory = evidence_root.join(format!("{artifact_index:04}-{test_index:08}"));
2056            fs::create_dir(&directory)
2057                .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
2058            tasks.push(ProcessTask {
2059                ordinal: tasks.len(),
2060                artifact_index,
2061                test_index,
2062                artifact: artifact.clone(),
2063                context_id: contexts[&test],
2064                test,
2065                directory,
2066            });
2067        }
2068    }
2069    let workers = std::thread::available_parallelism()
2070        .map(usize::from)
2071        .unwrap_or(1)
2072        .min(tasks.len().max(1));
2073    let next = AtomicUsize::new(0);
2074    let outcomes = Mutex::new(Vec::<Result<ProcessOutcome, String>>::with_capacity(
2075        tasks.len(),
2076    ));
2077    std::thread::scope(|scope| {
2078        for _ in 0..workers {
2079            scope.spawn(|| {
2080                loop {
2081                    let index = next.fetch_add(1, Ordering::Relaxed);
2082                    let Some(task) = tasks.get(index) else { break };
2083                    let result = crate::child_signal_guard::output(
2084                        launcher
2085                            .command(&task.artifact.executable, Some(&task.directory))
2086                            // No --nocapture: libtest's in-memory capture is what
2087                            // plain `cargo test` gives users, and it re-emits a
2088                            // failing test's output, which `.output()` still
2089                            // receives. Streaming instead turns every print in a
2090                            // hot loop into an unbuffered stderr syscall: bytes'
2091                            // advance_bytes_mut_remaining_capacity prints per
2092                            // iteration, and --nocapture alone cost 6.9s of its
2093                            // 14.4s (baseline 8.0s streamed vs 1.0s captured).
2094                            .args(["--exact", &task.test])
2095                            .args(&ignored_mode)
2096                            .args(coloured_results.then_some("--color=always"))
2097                            .current_dir(&task.artifact.package_directory)
2098                            .envs(instrumented_stack_environment())
2099                            .envs(dynamic_library_environment(
2100                                &target_libdir,
2101                                &task.artifact.executable,
2102                            ))
2103                            .env("SUPERCOV_RUST_EVIDENCE_DIR", &task.directory)
2104                            .envs(
2105                                rust_workers
2106                                    .iter()
2107                                    .flat_map(|workers| workers.environment()),
2108                            ),
2109                    )
2110                    .map(|output| ProcessOutcome {
2111                        task: ProcessTask {
2112                            ordinal: task.ordinal,
2113                            artifact_index: task.artifact_index,
2114                            test_index: task.test_index,
2115                            artifact: task.artifact.clone(),
2116                            test: task.test.clone(),
2117                            context_id: task.context_id,
2118                            directory: task.directory.clone(),
2119                        },
2120                        output,
2121                    })
2122                    .map_err(|error| error.to_string());
2123                    outcomes
2124                        .lock()
2125                        .expect("Rust test result lock poisoned")
2126                        .push(result);
2127                }
2128            });
2129        }
2130    });
2131    let mut outcomes = outcomes
2132        .into_inner()
2133        .map_err(|_| RustTestRunnerError::Io("Rust test result lock poisoned".into()))?
2134        .into_iter()
2135        .map(|result| result.map_err(RustTestRunnerError::Launch))
2136        .collect::<Result<Vec<_>, _>>()?;
2137    outcomes.sort_by_key(|outcome| outcome.task.ordinal);
2138    // What each test's Worker ran, as region files of its own evidence; and
2139    // the crates `wrangler dev` built for them, whose files count though the
2140    // run's own build never compiled them.
2141    let mut built_crates = BTreeSet::new();
2142    if rust_workers.is_some() {
2143        for outcome in &outcomes {
2144            built_crates.extend(
2145                crate::rust_worker::materialize_worker_windows(
2146                    &outcome.task.directory,
2147                    &project.probes.token,
2148                )
2149                .map_err(RustTestRunnerError::Io)?,
2150            );
2151        }
2152    }
2153    let redeclined;
2154    let (project, declined) = if built_crates.is_empty() {
2155        (project, declined)
2156    } else {
2157        let built = built_crates
2158            .iter()
2159            .filter_map(|key| original.probes.crates.get(key))
2160            .flatten()
2161            .cloned()
2162            .collect::<BTreeSet<_>>();
2163        let (manifest, declined) = decline_uncompiled_sources(original, &built);
2164        redeclined = PreparedRustProject {
2165            manifest,
2166            ..project.clone()
2167        };
2168        (&redeclined, declined)
2169    };
2170    report_declined(&declined, diagnostics)?;
2171    // What `cargo test` prints for each binary: every test, the failures
2172    // together, one result.
2173    // Cargo's `-q` drops its own line for the binary and asks libtest for
2174    // its terse form; libtest's own `-q` is the terse form alone.
2175    let unannounced = cargo_quiet(&invocation);
2176    let terse = unannounced || libtest_quiet(&invocation);
2177    let coloured = cargo_colours(&invocation);
2178    let announced = |artifact: &TestArtifact, report: String| {
2179        (
2180            (artifact.kind == "integration", artifact.name.clone()),
2181            crate::rust_libtest_report::Announced {
2182                heading: (!unannounced).then(|| {
2183                    running_line(
2184                        &project.workspace_root,
2185                        &project.target_directory,
2186                        artifact,
2187                        coloured,
2188                    )
2189                }),
2190                report,
2191            },
2192        )
2193    };
2194    // In Cargo's order: unit tests, then each integration test by name.
2195    let mut reports = Vec::new();
2196    for binary in
2197        outcomes.chunk_by(|left, right| left.task.artifact_index == right.task.artifact_index)
2198    {
2199        let texts = binary
2200            .iter()
2201            .map(|outcome| {
2202                (
2203                    String::from_utf8_lossy(&outcome.output.stdout),
2204                    String::from_utf8_lossy(&outcome.output.stderr),
2205                )
2206            })
2207            .collect::<Vec<_>>();
2208        let cases = binary
2209            .iter()
2210            .zip(&texts)
2211            .map(
2212                |(outcome, (stdout, stderr))| crate::rust_libtest_report::CaseOutput {
2213                    name: &outcome.task.test,
2214                    exit: outcome.output.status.code().unwrap_or(1),
2215                    stdout,
2216                    stderr,
2217                },
2218            )
2219            .collect::<Vec<_>>();
2220        let artifact = &binary[0].task.artifact;
2221        reports.push(announced(
2222            artifact,
2223            crate::rust_libtest_report::libtest_report(&cases, terse),
2224        ));
2225    }
2226    for (artifact, all) in unasked {
2227        reports.push(announced(
2228            artifact,
2229            crate::rust_libtest_report::unasked_report(all),
2230        ));
2231    }
2232    reports.sort_by(|(left, _), (right, _)| left.cmp(right));
2233    let reports = reports
2234        .into_iter()
2235        .map(|(_, report)| report)
2236        .collect::<Vec<_>>();
2237    crate::rust_libtest_report::write_reports(diagnostics, &reports)
2238        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
2239    for outcome in outcomes {
2240        let ProcessTask {
2241            artifact_index,
2242            test_index,
2243            artifact,
2244            test,
2245            directory,
2246            ..
2247        } = outcome.task;
2248        // Target names are not workspace-unique: two packages may both
2249        // expose `lib` or the same integration-test target. Source path +
2250        // libtest name is stable and unique within the frozen workspace.
2251        let test_id = format!("{}::{test}", artifact.source);
2252        let worker_id = format!("artifact-{artifact_index:04}");
2253        let attempt_id = format!("{run_id}:{artifact_index:04}:{test_index:08}");
2254        let output = outcome.output;
2255        let exit = output.status.code().unwrap_or(1);
2256        let stdout = String::from_utf8_lossy(&output.stdout);
2257        let skipped = libtest_skipped(exit, &stdout);
2258        if exit != 0 {
2259            overall_exit = exit;
2260        }
2261        let evidence = snapshot(&project.manifest, &project.probes, &directory, &attempt_id)?;
2262        results.push(RawTestResult {
2263            span: None,
2264            test_id: Some(test_id.clone()),
2265            scope: Some(ExecutionScope {
2266                version: 1,
2267                run_id: run_id.into(),
2268                worker_id,
2269                test_id: test_id.clone(),
2270                test_key: format!("{}::{test}", artifact.source),
2271                retry: 0,
2272                attempt_id,
2273            }),
2274            test: test_id,
2275            test_file: Some(artifact.source.clone()),
2276            title: Some(test),
2277            retry: Some(0),
2278            status: Some(
2279                if exit != 0 {
2280                    "failed"
2281                } else if skipped {
2282                    "skipped"
2283                } else {
2284                    "passed"
2285                }
2286                .into(),
2287            ),
2288            expected_status: Some("passed".into()),
2289            flaky: false,
2290            provenance: TestProvenance {
2291                runner: "rust-libtest".into(),
2292                kind: artifact.kind,
2293                project: Some(artifact.name),
2294                source: "supercov-owned-process-per-test".into(),
2295                tags: Vec::new(),
2296            },
2297            role: "test".into(),
2298            attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
2299            phases: evidence.phases,
2300            runtime: vec![evidence.snapshot],
2301            browser: Vec::new(),
2302            server: Vec::new(),
2303        });
2304    }
2305    let doctest_results =
2306        if selection.run_doctests && invocation.kind == RustCargoCommandKind::CargoTest {
2307            crate::rust_owned_doctests::run_doctests(
2308                project,
2309                &invocation,
2310                &selection,
2311                &evidence_root.join("doctests"),
2312                run_id,
2313                diagnostics,
2314                &mut overall_exit,
2315            )?
2316        } else {
2317            Vec::new()
2318        };
2319    // Only observed runners may be declared. A run with no tests at all keeps
2320    // the libtest declaration, as it always has.
2321    let project = &PreparedRustProject {
2322        manifest: settle_cfg_gates(project, &evidence_root, diagnostics)?,
2323        ..project.clone()
2324    };
2325    let ran_libtests = !results.is_empty();
2326    let ran_doctests = !doctest_results.is_empty();
2327    results.extend(doctest_results);
2328    let mut runners = Vec::new();
2329    if ran_libtests || !ran_doctests {
2330        runners.push(rust_runner_declaration("rust-libtest"));
2331    }
2332    if ran_doctests {
2333        runners.push(rust_runner_declaration("rustdoc"));
2334    }
2335    let structural_limitations = structural_limitations(&project.manifest);
2336    Ok(RustFrontendRun {
2337        declaration: FrontendRunDeclaration {
2338            protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
2339            frontend_id: "rust".into(),
2340            frontend_version: "rust-owned-v1".into(),
2341            language: "rust".into(),
2342            structural_source: StructuralSource::OwnedProbes,
2343            runners,
2344            structural_limitations,
2345        },
2346        request: CoverageReportRequest {
2347            run_id: run_id.into(),
2348            manifest: project.manifest.clone(),
2349            raw_results: results,
2350            generated_at: generated_at.into(),
2351            coverage_model: Some(rust_coverage_model()),
2352            integrity: None,
2353            test_exit_code: ExitCodeInput::Present(Some(overall_exit)),
2354        },
2355        exit_code: overall_exit,
2356        artifacts: artifacts.len(),
2357        artifact_files: artifacts
2358            .iter()
2359            .map(|artifact| artifact.executable.clone())
2360            .collect(),
2361        build_ms,
2362        execution_ms: execution_started.elapsed().as_secs_f64() * 1000.0,
2363    })
2364}
2365
2366#[cfg(test)]
2367mod tests {
2368
2369    #[test]
2370    fn node_takes_a_hook_by_a_file_url_with_its_bytes_encoded() {
2371        assert_eq!(
2372            file_url(Path::new("/tmp/a b/#hook.mjs")),
2373            "file:///tmp/a%20b/%23hook.mjs"
2374        );
2375        assert_eq!(file_url(Path::new("/srv/hook.mjs")), "file:///srv/hook.mjs");
2376    }
2377
2378    #[test]
2379    fn a_wasi_runtime_is_known_by_its_name() {
2380        assert!(is_wasi_runtime(Path::new("wasmtime")));
2381        assert!(is_wasi_runtime(Path::new("/opt/homebrew/bin/wasmtime")));
2382        assert!(is_wasi_runtime(Path::new("wasmer")));
2383        assert!(!is_wasi_runtime(Path::new("wasm-bindgen-test-runner")));
2384        assert!(!is_wasi_runtime(Path::new("qemu-aarch64")));
2385    }
2386
2387    use std::time::{SystemTime, UNIX_EPOCH};
2388
2389    use super::*;
2390    use crate::{
2391        coverage_report::{ArchiveReportRequest, analyze_coverage_archive},
2392        evidence_archive::write_archive,
2393        frontend_protocol::validate_frontend_report_request,
2394        rust_project::prepare_rust_project,
2395    };
2396
2397    #[test]
2398    fn a_test_binary_is_announced_as_cargo_announces_it() {
2399        // The source from its package, the binary from where Cargo would
2400        // have built it, and neither with the prefix Windows puts on a path
2401        // it has resolved.
2402        let artifact = |package: &str, source: &str, kind: &str| TestArtifact {
2403            executable: PathBuf::from(
2404                "/work/copy/.supercov/cache/rust-target/debug/deps/shop-0123456789abcdef",
2405            ),
2406            name: "shop".into(),
2407            kind: kind.into(),
2408            source: source.into(),
2409            package_directory: PathBuf::from(package),
2410        };
2411        let line = |artifact: &TestArtifact| {
2412            running_line(
2413                Path::new("/work/copy"),
2414                Path::new("/work/copy/.supercov/cache/rust-target"),
2415                artifact,
2416                false,
2417            )
2418            .replace('\\', "/")
2419        };
2420        assert_eq!(
2421            line(&artifact("/work/copy", "src/lib.rs", "unit")),
2422            "     Running unittests src/lib.rs (target/debug/deps/shop-0123456789abcdef)"
2423        );
2424        assert_eq!(
2425            line(&artifact(
2426                "/work/copy/crates/shop",
2427                "crates/shop/tests/outside.rs",
2428                "integration"
2429            )),
2430            "     Running tests/outside.rs (target/debug/deps/shop-0123456789abcdef)"
2431        );
2432        // As Windows gives them: the package resolved, the workspace not.
2433        let windows = running_line(
2434            Path::new("C:\\work\\copy"),
2435            Path::new("C:\\work\\copy\\target"),
2436            &artifact(
2437                "\\\\?\\C:\\work\\copy\\crates\\shop",
2438                "crates/shop/src/lib.rs",
2439                "unit",
2440            ),
2441            false,
2442        )
2443        .replace('\\', "/");
2444        assert!(
2445            windows.starts_with("     Running unittests src/lib.rs ("),
2446            "{windows}"
2447        );
2448        assert!(
2449            running_line(
2450                Path::new("/work/copy"),
2451                Path::new("/work/copy/.supercov/cache/rust-target"),
2452                &artifact("/work/copy", "src/lib.rs", "unit"),
2453                true,
2454            )
2455            .starts_with("\u{1b}[1m\u{1b}[92m     Running\u{1b}[0m unittests ")
2456        );
2457    }
2458
2459    /// `npm test -- adds` runs the script with `adds` after it: the filter
2460    /// reaches Cargo, so only the tests it names run.
2461    #[test]
2462    fn a_filter_given_to_a_package_script_reaches_cargo() {
2463        let root = std::env::temp_dir().join(format!(
2464            "supercov-npm-cargo-{}-{}",
2465            std::process::id(),
2466            std::time::SystemTime::now()
2467                .duration_since(std::time::UNIX_EPOCH)
2468                .unwrap()
2469                .as_nanos()
2470        ));
2471        std::fs::create_dir(&root).unwrap();
2472        std::fs::write(
2473            root.join("package.json"),
2474            r#"{ "scripts": { "test": "cargo test --lib" } }"#,
2475        )
2476        .unwrap();
2477        let invocation = cargo_invocation(
2478            &root,
2479            &["npm".into(), "test".into(), "--".into(), "adds".into()],
2480        )
2481        .unwrap();
2482        std::fs::remove_dir_all(&root).unwrap();
2483        assert_eq!(invocation.kind, RustCargoCommandKind::CargoTest);
2484        assert_eq!(invocation.arguments, ["test", "--lib", "adds"]);
2485    }
2486
2487    #[test]
2488    fn cargo_and_libtest_selection_is_preserved_without_presentation_guessing() {
2489        let root = Path::new(".");
2490        let invocation = cargo_invocation(
2491            root,
2492            &[
2493                "cargo".into(),
2494                "test".into(),
2495                "-p".into(),
2496                "fixture".into(),
2497                "authored".into(),
2498                "--".into(),
2499                "generated".into(),
2500                "--skip".into(),
2501                "slow".into(),
2502                "--include-ignored".into(),
2503            ],
2504        )
2505        .unwrap();
2506        assert_eq!(invocation.arguments, ["test", "-p", "fixture", "authored"]);
2507        assert_eq!(
2508            invocation.runner_arguments,
2509            ["generated", "--skip", "slow", "--include-ignored"]
2510        );
2511        let selection = rust_libtest_selection(&invocation).unwrap();
2512        assert_eq!(
2513            selection.list_arguments,
2514            [
2515                "authored",
2516                "generated",
2517                "--skip",
2518                "slow",
2519                "--include-ignored"
2520            ]
2521        );
2522    }
2523
2524    #[test]
2525    fn direct_cargo_argv_preserves_toml_quotes_inside_config_values() {
2526        let config = "target.host.runner=[\"runner with spaces\",\"--fixed\"]";
2527        let invocation = cargo_invocation(
2528            Path::new("."),
2529            &[
2530                "cargo".into(),
2531                "test".into(),
2532                "--config".into(),
2533                config.into(),
2534            ],
2535        )
2536        .unwrap();
2537        assert_eq!(invocation.arguments, ["test", "--config", config]);
2538    }
2539
2540    #[test]
2541    fn nextest_run_is_detected_without_reclassifying_its_filters_or_retries() {
2542        let invocation = cargo_invocation(
2543            Path::new("."),
2544            &[
2545                "cargo".into(),
2546                "+1.95.0".into(),
2547                "nextest".into(),
2548                "run".into(),
2549                "--retries".into(),
2550                "2".into(),
2551                "-E".into(),
2552                "test(/flaky/)".into(),
2553                "--".into(),
2554                "--nocapture".into(),
2555            ],
2556        )
2557        .unwrap();
2558        assert_eq!(invocation.kind, RustCargoCommandKind::NextestRun);
2559        assert_eq!(
2560            invocation.arguments,
2561            [
2562                "+1.95.0",
2563                "nextest",
2564                "run",
2565                "--retries",
2566                "2",
2567                "-E",
2568                "test(/flaky/)",
2569            ]
2570        );
2571        assert_eq!(invocation.runner_arguments, ["--nocapture"]);
2572        let execution = rust_cargo_execution_selection(&invocation).unwrap();
2573        assert!(execution.run_libtests);
2574        assert!(!execution.run_doctests);
2575        assert!(execution.doctest_arguments.is_empty());
2576        assert_eq!(
2577            nextest_list_invocation(&invocation).unwrap(),
2578            NextestListInvocation {
2579                arguments: vec![
2580                    "+1.95.0".into(),
2581                    "nextest".into(),
2582                    "list".into(),
2583                    "-E".into(),
2584                    "test(/flaky/)".into(),
2585                    "--message-format".into(),
2586                    "json".into(),
2587                ],
2588                runner_arguments: vec!["--nocapture".into()],
2589            }
2590        );
2591    }
2592
2593    #[test]
2594    fn nextest_list_projection_preserves_selection_and_rejects_external_state() {
2595        let invocation = CargoTestInvocation {
2596            program: "cargo".into(),
2597            kind: RustCargoCommandKind::NextestRun,
2598            arguments: vec![
2599                "nextest".into(),
2600                "run".into(),
2601                "--package=fixture".into(),
2602                "--partition".into(),
2603                "hash:1/2".into(),
2604                "--test-threads=8".into(),
2605                "--failure-output".into(),
2606                "final".into(),
2607                "name".into(),
2608            ],
2609            runner_arguments: vec!["--exact".into(), "full::name".into()],
2610        };
2611        assert_eq!(
2612            nextest_list_invocation(&invocation).unwrap(),
2613            NextestListInvocation {
2614                arguments: vec![
2615                    "nextest".into(),
2616                    "list".into(),
2617                    "--package=fixture".into(),
2618                    "--partition".into(),
2619                    "hash:1/2".into(),
2620                    "name".into(),
2621                    "--message-format".into(),
2622                    "json".into(),
2623                ],
2624                runner_arguments: vec!["--exact".into(), "full::name".into()],
2625            }
2626        );
2627
2628        let mut rerun = invocation;
2629        rerun.arguments.extend(["--rerun".into(), "latest".into()]);
2630        assert!(
2631            nextest_list_invocation(&rerun)
2632                .unwrap_err()
2633                .to_string()
2634                .contains("cannot yet be assigned exact selected-test identity")
2635        );
2636    }
2637
2638    /// A package script's command split as `sh` runs it.
2639    #[test]
2640    fn a_script_command_splits_the_way_sh_splits_it() {
2641        let words = |text: &str| shell_words(text).unwrap();
2642        assert_eq!(
2643            words("cargo  test --workspace"),
2644            ["cargo", "test", "--workspace"]
2645        );
2646        assert_eq!(
2647            words(r#"cargo test --config 'target.x.runner="a b"' -- "two words""#),
2648            [
2649                "cargo",
2650                "test",
2651                "--config",
2652                r#"target.x.runner="a b""#,
2653                "--",
2654                "two words"
2655            ]
2656        );
2657        // An empty argument is an argument.
2658        assert_eq!(
2659            words(r#"cargo test -- '' """#),
2660            ["cargo", "test", "--", "", ""]
2661        );
2662        // In double quotes a backslash escapes only what `sh` lets it.
2663        assert_eq!(
2664            words(r#""C:\work\app" "say \"hi\" \$HOME""#),
2665            [r"C:\work\app", r#"say "hi" $HOME"#]
2666        );
2667        // Outside quotes it escapes anything; in single quotes nothing.
2668        assert_eq!(words(r"a\ b 'c\d'"), ["a b", r"c\d"]);
2669        assert_eq!(words("joined'' x\"\"y"), ["joined", "xy"]);
2670        assert_eq!(
2671            words("cargo test \\\n  --workspace \"a\\\nb\""),
2672            ["cargo", "test", "--workspace", "ab"]
2673        );
2674        for broken in ["cargo test 'open", "cargo test \"open", r"cargo test \"] {
2675            assert!(shell_words(broken).is_err(), "{broken}");
2676        }
2677    }
2678
2679    #[test]
2680    fn nextest_list_projection_keeps_short_options_with_attached_values() {
2681        let invocation = CargoTestInvocation {
2682            program: "cargo".into(),
2683            kind: RustCargoCommandKind::NextestRun,
2684            arguments: [
2685                "nextest",
2686                "run",
2687                "-pfixture",
2688                "-Ffast",
2689                "-Etest(=a)",
2690                "-Pci",
2691                "-Zbuild-std",
2692                "-vv",
2693                "name",
2694            ]
2695            .map(String::from)
2696            .to_vec(),
2697            runner_arguments: Vec::new(),
2698        };
2699        let listed = nextest_list_invocation(&invocation).unwrap();
2700        // Each option carries its own value, so the filter after them is
2701        // still read as the filter.
2702        assert_eq!(
2703            listed.arguments,
2704            [
2705                "nextest",
2706                "list",
2707                "-pfixture",
2708                "-Ffast",
2709                "-Etest(=a)",
2710                "-Pci",
2711                "-Zbuild-std",
2712                "-vv",
2713                "name",
2714                "--message-format",
2715                "json",
2716            ]
2717        );
2718    }
2719
2720    #[test]
2721    fn nextest_list_projection_preserves_post_separator_libtest_selection() {
2722        let invocation = cargo_invocation(
2723            Path::new("."),
2724            &[
2725                "cargo".into(),
2726                "nextest".into(),
2727                "run".into(),
2728                "--timings".into(),
2729                "-vv".into(),
2730                "--".into(),
2731                "--include-ignored".into(),
2732                "--skip".into(),
2733                "slow".into(),
2734                "--exact".into(),
2735                "tests::selected".into(),
2736            ],
2737        )
2738        .unwrap();
2739        assert_eq!(
2740            nextest_list_invocation(&invocation).unwrap(),
2741            NextestListInvocation {
2742                arguments: vec![
2743                    "nextest".to_owned(),
2744                    "list".to_owned(),
2745                    "--timings".to_owned(),
2746                    "-vv".to_owned(),
2747                    "--message-format".to_owned(),
2748                    "json".to_owned(),
2749                ],
2750                runner_arguments: vec![
2751                    "--include-ignored".to_owned(),
2752                    "--skip".to_owned(),
2753                    "slow".to_owned(),
2754                    "--exact".to_owned(),
2755                    "tests::selected".to_owned(),
2756                ],
2757            }
2758        );
2759    }
2760
2761    #[test]
2762    fn nextest_version_handshake_preserves_the_cargo_toolchain_selector() {
2763        let invocation = CargoTestInvocation {
2764            program: "cargo".into(),
2765            kind: RustCargoCommandKind::NextestRun,
2766            arguments: vec![
2767                "+1.95.0".into(),
2768                "nextest".into(),
2769                "run".into(),
2770                "-p".into(),
2771                "fixture".into(),
2772            ],
2773            runner_arguments: Vec::new(),
2774        };
2775        assert_eq!(
2776            nextest_version_arguments(&invocation),
2777            ["+1.95.0", "nextest", "--version"]
2778        );
2779    }
2780
2781    #[test]
2782    fn stock_libtest_presentation_and_scheduling_options_do_not_change_discovery() {
2783        let invocation = CargoTestInvocation {
2784            program: "cargo".into(),
2785            kind: RustCargoCommandKind::CargoTest,
2786            arguments: vec!["test".into(), "cargo-filter".into()],
2787            runner_arguments: [
2788                "runner-filter",
2789                "--nocapture",
2790                "--show-output",
2791                "--format=json",
2792                "--color",
2793                "never",
2794                "--test-threads=4",
2795                "--fail-fast",
2796                "--shuffle-seed",
2797                "17",
2798                "-Zunstable-options",
2799                "--exclude-should-panic",
2800            ]
2801            .into_iter()
2802            .map(str::to_owned)
2803            .collect(),
2804        };
2805        let selection = rust_libtest_selection(&invocation).unwrap();
2806        assert_eq!(
2807            selection.list_arguments,
2808            [
2809                "cargo-filter",
2810                "runner-filter",
2811                "-Zunstable-options",
2812                "--exclude-should-panic"
2813            ]
2814        );
2815    }
2816
2817    #[test]
2818    fn cargo_test_options_are_not_mistaken_for_the_test_name_filter() {
2819        let invocation = CargoTestInvocation {
2820            program: "cargo".into(),
2821            kind: RustCargoCommandKind::CargoTest,
2822            arguments: vec![
2823                "test".into(),
2824                "--manifest-path".into(),
2825                "nested/Cargo.toml".into(),
2826                "--features=one,two".into(),
2827                "needle".into(),
2828            ],
2829            runner_arguments: vec!["--ignored".into(), "other".into()],
2830        };
2831        let selection = rust_libtest_selection(&invocation).unwrap();
2832        assert_eq!(selection.list_arguments, ["needle", "--ignored", "other"]);
2833    }
2834
2835    #[test]
2836    fn libtest_thread_count_is_preserved_as_runner_scheduling() {
2837        for arguments in [vec!["--test-threads", "1"], vec!["--test-threads=8"]] {
2838            let invocation = CargoTestInvocation {
2839                program: "cargo".into(),
2840                kind: RustCargoCommandKind::CargoTest,
2841                arguments: vec!["test".into()],
2842                runner_arguments: arguments.into_iter().map(str::to_owned).collect(),
2843            };
2844            let selection = rust_libtest_selection(&invocation).unwrap();
2845            assert!(selection.list_arguments.is_empty());
2846        }
2847    }
2848
2849    #[test]
2850    fn invalid_or_duplicate_libtest_thread_counts_fail_closed() {
2851        for arguments in [
2852            vec!["--test-threads"],
2853            vec!["--test-threads=0"],
2854            vec!["--test-threads=abc"],
2855            vec!["--test-threads", "1", "--test-threads=2"],
2856        ] {
2857            let invocation = CargoTestInvocation {
2858                program: "cargo".into(),
2859                kind: RustCargoCommandKind::CargoTest,
2860                arguments: vec!["test".into()],
2861                runner_arguments: arguments.into_iter().map(str::to_owned).collect(),
2862            };
2863            assert!(rust_libtest_selection(&invocation).is_err());
2864        }
2865    }
2866
2867    #[test]
2868    fn cargo_target_selection_reproduces_when_cargo_runs_doctests() {
2869        let invocation = CargoTestInvocation {
2870            program: "cargo".into(),
2871            kind: RustCargoCommandKind::CargoTest,
2872            arguments: vec![
2873                "test".into(),
2874                "-p".into(),
2875                "fixture".into(),
2876                "needle".into(),
2877            ],
2878            runner_arguments: vec!["--include-ignored".into()],
2879        };
2880        let selection = rust_cargo_execution_selection(&invocation).unwrap();
2881        assert!(selection.run_libtests);
2882        assert!(selection.run_doctests);
2883        assert_eq!(
2884            selection.doctest_arguments,
2885            [
2886                "test",
2887                "--doc",
2888                "-p",
2889                "fixture",
2890                "needle",
2891                "--",
2892                "--include-ignored"
2893            ]
2894        );
2895
2896        let mut explicit_doc = invocation.clone();
2897        explicit_doc.arguments.insert(1, "--doc".into());
2898        let selection = rust_cargo_execution_selection(&explicit_doc).unwrap();
2899        assert!(!selection.run_libtests);
2900        assert!(selection.run_doctests);
2901
2902        for target in ["--lib", "--tests", "--all-targets", "--example=demo"] {
2903            let mut selected = invocation.clone();
2904            selected.arguments.insert(1, target.into());
2905            let selection = rust_cargo_execution_selection(&selected).unwrap();
2906            assert!(selection.run_libtests);
2907            assert!(!selection.run_doctests);
2908        }
2909    }
2910
2911    #[test]
2912    fn a_module_the_build_never_compiles_leaves_the_denominator() {
2913        // `#[cfg(feature = ...)]` with the feature off resolves as a module
2914        // and is never compiled, so nothing in it can ever be covered.
2915        // memchr carries thirteen such files for other architectures.
2916        let nonce = SystemTime::now()
2917            .duration_since(UNIX_EPOCH)
2918            .unwrap()
2919            .as_nanos();
2920        let root = std::env::temp_dir().join(format!(
2921            "supercov-rust-runner-cfg-{}-{nonce}",
2922            std::process::id()
2923        ));
2924        fs::create_dir_all(root.join("src")).unwrap();
2925        fs::write(
2926            root.join("Cargo.toml"),
2927            "[package]\nname='fixture'\nversion='0.0.0'\nedition='2024'\n\n[features]\nextra=[]\n",
2928        )
2929        .unwrap();
2930        fs::write(
2931            root.join("src/lib.rs"),
2932            r#"
2933#[cfg(feature = "extra")]
2934mod extra;
2935
2936pub fn kept(value: i32) -> i32 {
2937    if value > 0 { value } else { -value }
2938}
2939#[cfg(test)]
2940mod tests {
2941    #[test] fn positive() { assert_eq!(super::kept(2), 2); }
2942    #[test] fn negative() { assert_eq!(super::kept(-2), 2); }
2943}
2944"#,
2945        )
2946        .unwrap();
2947        fs::write(
2948            root.join("src/extra.rs"),
2949            "pub fn never_built(value: i32) -> i32 {\n    if value > 0 { 1 } else { 0 }\n}\n",
2950        )
2951        .unwrap();
2952        let project = prepare_rust_project(&root, None).unwrap();
2953        // Discovery still reaches it: the module tree is what rustc resolves.
2954        assert!(
2955            project
2956                .source_files
2957                .iter()
2958                .any(|file| file == "src/extra.rs"),
2959            "{:?}",
2960            project.source_files
2961        );
2962        let run = run_prepared_rust_tests(
2963            &project,
2964            &["cargo".into(), "test".into(), "--lib".into()],
2965            "rust-fixture-cfg",
2966            "2026-08-26T00:00:00.000Z",
2967            None,
2968            &mut Vec::new(),
2969        )
2970        .unwrap();
2971        assert_eq!(run.exit_code, 0);
2972        // Its obligations stay in the manifest, so the evidence token still
2973        // matches and the file is still addressable, but they are declined.
2974        let manifest = &run.request.manifest;
2975        assert!(
2976            manifest
2977                .points
2978                .iter()
2979                .any(|point| point.file == "src/extra.rs"),
2980            "the obligations must stay in the manifest"
2981        );
2982        let declined = manifest.unmeasured.iter().collect::<BTreeSet<_>>();
2983        for point in &manifest.points {
2984            assert_eq!(
2985                declined.contains(&point.id),
2986                point.file == "src/extra.rs",
2987                "{} in {}",
2988                point.id,
2989                point.file
2990            );
2991        }
2992        for decision in &manifest.decisions {
2993            assert_eq!(
2994                declined.contains(&decision.id),
2995                decision.file == "src/extra.rs"
2996            );
2997        }
2998        // The tests cover everything the build compiled.
2999        let report =
3000            crate::frontend_protocol::analyze_frontend_results(&run.declaration, &run.request)
3001                .unwrap();
3002        assert_eq!(
3003            report.view.summary.lines.percentage, 100.0,
3004            "declined obligations must not read as uncovered"
3005        );
3006        fs::remove_dir_all(&root).ok();
3007    }
3008
3009    #[test]
3010    fn assertion_occurrences_are_retained_without_automatic_asserted_credit() {
3011        // Records are appended in execution order, so what a thread wrote
3012        // before it passed an assertion was in scope for that check. Without
3013        // this every Rust line read "execution only", however thoroughly the
3014        // tests checked it.
3015        let nonce = SystemTime::now()
3016            .duration_since(UNIX_EPOCH)
3017            .unwrap()
3018            .as_nanos();
3019        let root = std::env::temp_dir().join(format!(
3020            "supercov-rust-runner-assert-{}-{nonce}",
3021            std::process::id()
3022        ));
3023        fs::create_dir_all(root.join("src")).unwrap();
3024        fs::write(
3025            root.join("Cargo.toml"),
3026            "[package]\nname='fixture'\nversion='0.0.0'\nedition='2024'\n",
3027        )
3028        .unwrap();
3029        fs::write(
3030            root.join("src/lib.rs"),
3031            r#"
3032pub fn checked(value: i32) -> i32 {
3033    value * 2
3034}
3035pub fn unchecked(value: i32) -> i32 {
3036    value + 1
3037}
3038#[cfg(test)]
3039mod tests {
3040    #[test]
3041    fn asserts() {
3042        let doubled = super::checked(2);
3043        assert_eq!(doubled, 4);
3044    }
3045    #[test]
3046    fn asserts_nothing() {
3047        let _ = super::unchecked(1);
3048    }
3049}
3050"#,
3051        )
3052        .unwrap();
3053        let project = prepare_rust_project(&root, None).unwrap();
3054        let run = run_prepared_rust_tests(
3055            &project,
3056            &["cargo".into(), "test".into(), "--lib".into()],
3057            "rust-fixture-assert",
3058            "2026-08-26T00:00:00.000Z",
3059            None,
3060            &mut Vec::new(),
3061        )
3062        .unwrap();
3063        assert_eq!(run.exit_code, 0);
3064
3065        // The asserting test carries an assertion phase that passed; the
3066        // other carries none.
3067        let phases = |test: &str| {
3068            run.request
3069                .raw_results
3070                .iter()
3071                .find(|result| result.test.ends_with(test))
3072                .unwrap_or_else(|| panic!("no test {test}"))
3073                .phases
3074                .clone()
3075        };
3076        let asserting = phases("asserts");
3077        assert!(
3078            !asserting.is_empty(),
3079            "the asserting test recorded no phase"
3080        );
3081        assert!(
3082            asserting
3083                .iter()
3084                .all(|phase| phase.kind == "assertion" && phase.status.as_deref() == Some("passed"))
3085        );
3086        assert!(
3087            phases("asserts_nothing").is_empty(),
3088            "a test that checks nothing witnesses nothing"
3089        );
3090
3091        let report =
3092            crate::frontend_protocol::analyze_frontend_results(&run.declaration, &run.request)
3093                .unwrap();
3094        let level = |line: usize| {
3095            report
3096                .view
3097                .lines
3098                .iter()
3099                .find(|entry| entry.file == "src/lib.rs" && entry.line == line)
3100                .map(|entry| entry.confidence.level.clone())
3101                .unwrap_or_else(|| panic!("no line {line}"))
3102        };
3103        // Both executed. Assertion meaning is supplied later by assertions.json.
3104        assert_eq!(level(3), "executed");
3105        assert_eq!(level(6), "executed");
3106        fs::remove_dir_all(&root).ok();
3107    }
3108
3109    #[test]
3110    fn libtest_filters_select_the_tests_plain_cargo_would_run() {
3111        // The user's filters were parsed and never applied: every artifact
3112        // was listed bare, so `--skip` and a name filter changed nothing and
3113        // Supercov ran a test plain Cargo did not (tokio, 1414 against 1413).
3114        let nonce = SystemTime::now()
3115            .duration_since(UNIX_EPOCH)
3116            .unwrap()
3117            .as_nanos();
3118        let root = std::env::temp_dir().join(format!(
3119            "supercov-rust-runner-filters-{}-{nonce}",
3120            std::process::id()
3121        ));
3122        fs::create_dir_all(root.join("src")).unwrap();
3123        fs::write(
3124            root.join("Cargo.toml"),
3125            "[package]\nname='fixture'\nversion='0.0.0'\nedition='2024'\n",
3126        )
3127        .unwrap();
3128        fs::write(
3129            root.join("src/lib.rs"),
3130            r#"
3131pub fn value(flag: bool) -> i32 { if flag { 1 } else { 0 } }
3132#[cfg(test)]
3133mod tests {
3134    #[test] fn alpha() { assert_eq!(super::value(true), 1); }
3135    #[test] fn beta() { assert_eq!(super::value(false), 0); }
3136    #[test] #[ignore] fn gamma_slow() { assert_eq!(super::value(true), 1); }
3137}
3138"#,
3139        )
3140        .unwrap();
3141        let project = prepare_rust_project(&root, None).unwrap();
3142        let names = |run: &str, command: &[&str]| {
3143            let run = run_prepared_rust_tests(
3144                &project,
3145                &command
3146                    .iter()
3147                    .map(|word| (*word).into())
3148                    .collect::<Vec<String>>(),
3149                run,
3150                "2026-08-26T00:00:00.000Z",
3151                None,
3152                &mut Vec::new(),
3153            )
3154            .unwrap();
3155            assert_eq!(run.exit_code, 0, "{:?}", run.request.raw_results);
3156            let mut names = run
3157                .request
3158                .raw_results
3159                .iter()
3160                .map(|result| {
3161                    (
3162                        result.test.rsplit("::").next().unwrap().to_owned(),
3163                        result.status.clone().unwrap(),
3164                    )
3165                })
3166                .collect::<Vec<_>>();
3167            names.sort();
3168            names
3169        };
3170        // Everything: the ignored test is listed and reported skipped.
3171        assert_eq!(
3172            names("rust-fixture-filters-1", &["cargo", "test", "--lib"]),
3173            [
3174                ("alpha".to_owned(), "passed".to_owned()),
3175                ("beta".to_owned(), "passed".to_owned()),
3176                ("gamma_slow".to_owned(), "skipped".to_owned()),
3177            ]
3178        );
3179        // A name filter and `--skip` narrow the enumeration as libtest does.
3180        assert_eq!(
3181            names(
3182                "rust-fixture-filters-2",
3183                &["cargo", "test", "--lib", "alpha"]
3184            ),
3185            [("alpha".to_owned(), "passed".to_owned())]
3186        );
3187        assert_eq!(
3188            names(
3189                "rust-fixture-filters-3",
3190                &["cargo", "test", "--lib", "--", "--skip", "beta"]
3191            ),
3192            [
3193                ("alpha".to_owned(), "passed".to_owned()),
3194                ("gamma_slow".to_owned(), "skipped".to_owned()),
3195            ]
3196        );
3197        // `--ignored` lists the ignored test, and then it has to RUN.
3198        assert_eq!(
3199            names(
3200                "rust-fixture-filters-4",
3201                &["cargo", "test", "--lib", "--", "--ignored"]
3202            ),
3203            [("gamma_slow".to_owned(), "passed".to_owned())]
3204        );
3205        fs::remove_dir_all(&root).ok();
3206    }
3207
3208    #[test]
3209    fn libtest_processes_run_in_their_package_directory() {
3210        // Cargo runs a test binary in its package's directory, and tokio's
3211        // `basic_fs` reads `Cargo.toml` from there; the workspace root has a
3212        // different manifest.
3213        let nonce = SystemTime::now()
3214            .duration_since(UNIX_EPOCH)
3215            .unwrap()
3216            .as_nanos();
3217        let root = std::env::temp_dir().join(format!(
3218            "supercov-rust-runner-cwd-{}-{nonce}",
3219            std::process::id()
3220        ));
3221        fs::create_dir_all(root.join("member/src")).unwrap();
3222        fs::write(
3223            root.join("Cargo.toml"),
3224            "[workspace]\nmembers = ['member']\nresolver = '2'\n",
3225        )
3226        .unwrap();
3227        fs::write(
3228            root.join("member/Cargo.toml"),
3229            "[package]\nname='member'\nversion='0.0.0'\nedition='2024'\n",
3230        )
3231        .unwrap();
3232        fs::write(
3233            root.join("member/src/lib.rs"),
3234            r#"
3235pub fn manifest() -> String {
3236    std::fs::read_to_string("Cargo.toml").unwrap()
3237}
3238#[cfg(test)]
3239mod tests {
3240    #[test] fn reads_own_manifest() { assert!(super::manifest().contains("name='member'")); }
3241}
3242"#,
3243        )
3244        .unwrap();
3245        let project = prepare_rust_project(&root, None).unwrap();
3246        let run = run_prepared_rust_tests(
3247            &project,
3248            &["cargo".into(), "test".into(), "--lib".into()],
3249            "rust-fixture-cwd",
3250            "2026-08-26T00:00:00.000Z",
3251            None,
3252            &mut Vec::new(),
3253        )
3254        .unwrap();
3255        assert_eq!(run.exit_code, 0, "{:?}", run.request.raw_results);
3256        assert_eq!(run.request.raw_results.len(), 1);
3257        assert_eq!(run.request.raw_results[0].status.as_deref(), Some("passed"));
3258        fs::remove_dir_all(&root).ok();
3259    }
3260
3261    #[test]
3262    fn cargo_libtest_runs_produce_queryable_owned_evidence() {
3263        let nonce = SystemTime::now()
3264            .duration_since(UNIX_EPOCH)
3265            .unwrap()
3266            .as_nanos();
3267        let root = std::env::temp_dir().join(format!(
3268            "supercov-rust-runner-{}-{nonce}",
3269            std::process::id()
3270        ));
3271        fs::create_dir_all(root.join("src")).unwrap();
3272        fs::write(
3273            root.join("Cargo.toml"),
3274            "[package]\nname='fixture'\nversion='0.0.0'\nedition='2024'\n",
3275        )
3276        .unwrap();
3277        fs::write(
3278            root.join("src/lib.rs"),
3279            r#"
3280pub fn choose(left: bool, right: bool) -> i32 {
3281    if left && right { 1 } else { 0 }
3282}
3283pub fn pick(value: i32) -> &'static str {
3284    match value {
3285        0 => "zero",
3286        1 => "one",
3287        _ => "many",
3288    }
3289}
3290pub fn total(values: &[i32]) -> i32 {
3291    let mut sum = 0;
3292    for value in values {
3293        sum += value;
3294    }
3295    sum
3296}
3297pub fn first_even(values: &[i32]) -> Option<i32> {
3298    let mut index = 0;
3299    while index < values.len() {
3300        if values[index] % 2 == 0 {
3301            return Some(values[index]);
3302        }
3303        index += 1;
3304    }
3305    None
3306}
3307pub fn parse_twice(text: &str) -> Option<i32> {
3308    let value: i32 = text.parse().ok()?;
3309    Some(value * 2)
3310}
3311pub fn describe(value: Option<i32>, flag: bool) -> &'static str {
3312    if let Some(inner) = value && inner > 0 && flag {
3313        "positive"
3314    } else {
3315        "other"
3316    }
3317}
3318pub fn depth(n: u32) -> Result<u32, String> {
3319    if n == 0 {
3320        Ok(0)
3321    } else {
3322        let below = depth(n - 1)?;
3323        Ok(below + 1)
3324    }
3325}
3326#[cfg(test)]
3327mod tests {
3328    #[test] fn false_path() { assert_eq!(super::choose(false, true), 0); }
3329    #[test] fn true_path() { assert_eq!(super::choose(true, true), 1); }
3330    #[test] #[ignore] fn ignored_path() { unreachable!(); }
3331    #[test] fn pick_zero() { assert_eq!(super::pick(0), "zero"); }
3332    #[test] fn pick_many() { assert_eq!(super::pick(7), "many"); }
3333    #[test] fn total_empty() { assert_eq!(super::total(&[]), 0); }
3334    #[test] fn total_some() { assert_eq!(super::total(&[1, 2]), 3); }
3335    #[test] fn first_even_empty() { assert_eq!(super::first_even(&[]), None); }
3336    #[test] fn first_even_found() { assert_eq!(super::first_even(&[1, 4]), Some(4)); }
3337    #[test] fn parse_ok() { assert_eq!(super::parse_twice("4"), Some(8)); }
3338    #[test] fn parse_bad() { assert_eq!(super::parse_twice("x"), None); }
3339    #[test] fn chain_taken() { assert_eq!(super::describe(Some(1), true), "positive"); }
3340    #[test] fn chain_pattern_fails() { assert_eq!(super::describe(None, true), "other"); }
3341    #[test] fn chain_negative() { assert_eq!(super::describe(Some(-1), true), "other"); }
3342    #[test] fn chain_flag_fails() { assert_eq!(super::describe(Some(1), false), "other"); }
3343    #[test] fn deep_recursion() { assert_eq!(super::depth(5_000), Ok(5_000)); }
3344}
3345"#,
3346        )
3347        .unwrap();
3348        let project = prepare_rust_project(&root, None).unwrap();
3349        // Doctests need the CLI binary standing in for rustdoc, which this
3350        // test binary cannot do; scripts/rust-public-cargo-integration.mjs
3351        // covers them end to end. `--lib` keeps this run to the libtests.
3352        let run = run_prepared_rust_tests(
3353            &project,
3354            &["cargo".into(), "test".into(), "--lib".into()],
3355            "rust-fixture",
3356            "2026-08-26T00:00:00.000Z",
3357            None,
3358            &mut Vec::new(),
3359        )
3360        .unwrap();
3361        assert_eq!(run.exit_code, 0);
3362        assert_eq!(run.request.raw_results.len(), 16);
3363        let statuses = run
3364            .request
3365            .raw_results
3366            .iter()
3367            .filter_map(|result| result.status.as_deref())
3368            .collect::<Vec<_>>();
3369        assert_eq!(
3370            statuses
3371                .iter()
3372                .filter(|status| **status == "skipped")
3373                .count(),
3374            1
3375        );
3376        assert_eq!(
3377            statuses
3378                .iter()
3379                .filter(|status| **status == "passed")
3380                .count(),
3381            15
3382        );
3383
3384        // The let chain's condition vectors, with the pattern's outcome
3385        // derived: [let Some(inner) = value, inner > 0, flag].
3386        let chain_vectors = |test: &str| {
3387            let result = run
3388                .request
3389                .raw_results
3390                .iter()
3391                .find(|result| result.test.ends_with(test))
3392                .unwrap_or_else(|| panic!("no test {test}"));
3393            let snapshot = result
3394                .runtime
3395                .iter()
3396                .flat_map(|snapshot| &snapshot.decisions)
3397                .find(|decision| decision.meta.source.starts_with("let Some(inner) = value"))
3398                .unwrap_or_else(|| panic!("{test} recorded no chain decision"));
3399            snapshot
3400                .vectors
3401                .iter()
3402                .map(|vector| (vector.values.clone(), vector.outcome))
3403                .collect::<Vec<_>>()
3404        };
3405        assert_eq!(
3406            chain_vectors("chain_taken"),
3407            [(vec![Some(true), Some(true), Some(true)], true)]
3408        );
3409        assert_eq!(
3410            chain_vectors("chain_pattern_fails"),
3411            [(vec![Some(false), None, None], false)]
3412        );
3413        assert_eq!(
3414            chain_vectors("chain_negative"),
3415            [(vec![Some(true), Some(false), None], false)]
3416        );
3417        assert_eq!(
3418            chain_vectors("chain_flag_fails"),
3419            [(vec![Some(true), Some(true), Some(false)], false)]
3420        );
3421        validate_frontend_report_request(&run.declaration, &run.request).unwrap();
3422        let archive = root.join("evidence.raw.gz");
3423        write_archive(run.archive_entries().unwrap(), &archive).unwrap();
3424        let report = analyze_coverage_archive(&ArchiveReportRequest {
3425            archive_path: archive,
3426            run_id: "rust-fixture".into(),
3427            generated_at: "2026-08-26T00:00:00.000Z".into(),
3428            integrity: None,
3429            test_exit_code: ExitCodeInput::Present(Some(0)),
3430            test_filter: None,
3431        })
3432        .unwrap();
3433        assert_eq!(report.view.tests.len(), 16);
3434        assert!(report.view.summary.lines.covered > 0);
3435        assert!(report.view.summary.decisions > 0);
3436
3437        // Loops, the try operator and the logical operator, each with both
3438        // outcomes attributed to the test that produced it.
3439        let single = |kind: &str| {
3440            let mut found = report
3441                .view
3442                .branches
3443                .iter()
3444                .filter(|branch| branch.meta.kind == kind);
3445            let branch = found.next().unwrap_or_else(|| panic!("no {kind} branch"));
3446            assert!(found.next().is_none(), "more than one {kind} branch");
3447            branch
3448        };
3449        let tests_of = |branch: &crate::coverage_report::BranchResult, label: &str| {
3450            branch
3451                .alternatives
3452                .iter()
3453                .find(|alternative| alternative.label == label)
3454                .unwrap_or_else(|| panic!("{} has no alternative {label}", branch.meta.kind))
3455                .tests
3456                .clone()
3457        };
3458        let for_loop = single("for-loop");
3459        assert_eq!(
3460            tests_of(for_loop, "zero iterations"),
3461            ["src/lib.rs::tests::total_empty"]
3462        );
3463        assert_eq!(
3464            tests_of(for_loop, "entered"),
3465            ["src/lib.rs::tests::total_some"]
3466        );
3467        let while_loop = single("while-loop");
3468        assert_eq!(
3469            tests_of(while_loop, "zero iterations"),
3470            ["src/lib.rs::tests::first_even_empty"]
3471        );
3472        assert_eq!(
3473            tests_of(while_loop, "entered"),
3474            ["src/lib.rs::tests::first_even_found"]
3475        );
3476        // `depth` adds a second `?`; the one in `parse_twice` is the one
3477        // exercised both ways.
3478        let try_operator = report
3479            .view
3480            .branches
3481            .iter()
3482            .find(|branch| {
3483                branch.meta.kind == "try-operator" && branch.meta.source.contains("parse().ok()")
3484            })
3485            .expect("parse_twice's try operator");
3486        assert_eq!(
3487            tests_of(try_operator, "continued"),
3488            ["src/lib.rs::tests::parse_ok"]
3489        );
3490        assert_eq!(
3491            tests_of(try_operator, "early return"),
3492            ["src/lib.rs::tests::parse_bad"]
3493        );
3494        let mut logical = report
3495            .view
3496            .branches
3497            .iter()
3498            .filter(|branch| branch.meta.kind == "logical-and")
3499            .collect::<Vec<_>>();
3500        logical.sort_by_key(|branch| (branch.meta.line, branch.meta.column));
3501        // `left && right` in choose, then the chain's two operators.
3502        assert_eq!(logical.len(), 3);
3503        assert_eq!(
3504            tests_of(logical[0], "short-circuited"),
3505            ["src/lib.rs::tests::false_path"]
3506        );
3507        assert_eq!(
3508            tests_of(logical[0], "right operand evaluated"),
3509            ["src/lib.rs::tests::true_path"]
3510        );
3511        assert_eq!(
3512            tests_of(logical[1], "short-circuited"),
3513            ["src/lib.rs::tests::chain_pattern_fails"]
3514        );
3515        assert_eq!(
3516            tests_of(logical[1], "right operand evaluated"),
3517            [
3518                "src/lib.rs::tests::chain_flag_fails",
3519                "src/lib.rs::tests::chain_negative",
3520                "src/lib.rs::tests::chain_taken",
3521            ]
3522        );
3523        assert_eq!(
3524            tests_of(logical[2], "short-circuited"),
3525            [
3526                "src/lib.rs::tests::chain_negative",
3527                "src/lib.rs::tests::chain_pattern_fails",
3528            ]
3529        );
3530        assert_eq!(
3531            tests_of(logical[2], "right operand evaluated"),
3532            [
3533                "src/lib.rs::tests::chain_flag_fails",
3534                "src/lib.rs::tests::chain_taken",
3535            ]
3536        );
3537        assert!(for_loop.covered && while_loop.covered && try_operator.covered);
3538        assert!(logical.iter().all(|branch| branch.covered));
3539
3540        // The match in `pick`: pick(0) selects the first arm; pick(7) passes
3541        // the first two over and selects the last. Nothing selects `1`.
3542        let mut arms = report
3543            .view
3544            .branches
3545            .iter()
3546            .filter(|branch| branch.meta.kind == "match-arm")
3547            .collect::<Vec<_>>();
3548        arms.sort_by_key(|branch| branch.meta.line);
3549        assert_eq!(arms.len(), 3);
3550        let alternative = |arm: usize, label: &str| {
3551            arms[arm]
3552                .alternatives
3553                .iter()
3554                .find(|alternative| alternative.label == label)
3555                .unwrap_or_else(|| panic!("arm {arm} has no alternative {label}"))
3556        };
3557        assert_eq!(
3558            alternative(0, "selected").tests,
3559            ["src/lib.rs::tests::pick_zero"]
3560        );
3561        assert_eq!(
3562            alternative(0, "not selected").tests,
3563            ["src/lib.rs::tests::pick_many"]
3564        );
3565        assert!(!alternative(1, "selected").covered);
3566        assert_eq!(
3567            alternative(1, "not selected").tests,
3568            ["src/lib.rs::tests::pick_many"]
3569        );
3570        assert_eq!(arms[2].alternatives.len(), 1);
3571        assert_eq!(
3572            alternative(2, "selected").tests,
3573            ["src/lib.rs::tests::pick_many"]
3574        );
3575        assert!(arms[0].covered && !arms[1].covered && arms[2].covered);
3576
3577        fs::remove_dir_all(root).unwrap();
3578    }
3579}