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, CoverageReportRequest, DecisionMeta,
36        DecisionSnapshot, ExecutionScope, ExitCodeInput, PersistedCoverageModel, RawTestResult,
37        RuntimeSnapshot, TestProvenance,
38    },
39    evidence_archive::EvidenceArchiveEntry,
40    rust_project::PreparedRustProject,
41    rust_runtime::{RustProbeObservation, read_rust_probe_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
203fn shell_words(value: &str) -> Result<Vec<String>, RustTestRunnerError> {
204    let mut words = Vec::new();
205    let mut current = String::new();
206    let mut quote = None;
207    let mut escaped = false;
208    for character in value.chars() {
209        if escaped {
210            current.push(character);
211            escaped = false;
212        } else if character == '\\' && quote != Some('\'') {
213            escaped = true;
214        } else if matches!(character, '\'' | '"') {
215            if quote == Some(character) {
216                quote = None;
217            } else if quote.is_none() {
218                quote = Some(character);
219            } else {
220                current.push(character);
221            }
222        } else if character.is_whitespace() && quote.is_none() {
223            if !current.is_empty() {
224                words.push(std::mem::take(&mut current));
225            }
226        } else {
227            current.push(character);
228        }
229    }
230    if escaped || quote.is_some() {
231        return Err(RustTestRunnerError::UnsupportedCommand(
232            "the expanded Cargo command contains an incomplete quote or escape".into(),
233        ));
234    }
235    if !current.is_empty() {
236        words.push(current);
237    }
238    Ok(words)
239}
240
241fn executable_name(value: &str) -> &str {
242    Path::new(value)
243        .file_name()
244        .and_then(|name| name.to_str())
245        .unwrap_or(value)
246        .trim_end_matches(".exe")
247        .trim_end_matches(".cmd")
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub(crate) struct CargoTestInvocation {
252    pub program: String,
253    pub kind: RustCargoCommandKind,
254    pub arguments: Vec<String>,
255    pub runner_arguments: Vec<String>,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub(crate) enum RustCargoCommandKind {
260    CargoTest,
261    NextestRun,
262}
263
264impl CargoTestInvocation {
265    pub(crate) fn command_position(&self) -> Option<usize> {
266        match self.kind {
267            RustCargoCommandKind::CargoTest => self
268                .arguments
269                .iter()
270                .position(|argument| argument == "test"),
271            RustCargoCommandKind::NextestRun => self
272                .arguments
273                .windows(2)
274                .position(|pair| pair == ["nextest", "run"]),
275        }
276    }
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub(crate) struct RustLibtestSelection {
281    pub list_arguments: Vec<String>,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub(crate) struct RustCargoExecutionSelection {
286    pub run_libtests: bool,
287    pub run_doctests: bool,
288    pub doctest_arguments: Vec<String>,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292pub(crate) struct NextestListInvocation {
293    pub arguments: Vec<String>,
294    pub runner_arguments: Vec<String>,
295}
296
297pub(crate) fn nextest_version_arguments(
298    invocation: &CargoTestInvocation,
299) -> Result<Vec<String>, RustTestRunnerError> {
300    if invocation.kind != RustCargoCommandKind::NextestRun {
301        return Err(RustTestRunnerError::UnsupportedCommand(
302            "a nextest version handshake requires `cargo nextest run`".into(),
303        ));
304    }
305    let command = invocation.command_position().ok_or_else(|| {
306        RustTestRunnerError::UnsupportedCommand(
307            "the expanded Cargo invocation lost its nextest run subcommand".into(),
308        )
309    })?;
310    let mut arguments = invocation.arguments[..command].to_vec();
311    arguments.extend(["nextest".into(), "--version".into()]);
312    Ok(arguments)
313}
314
315fn nextest_run_only_option(argument: &str) -> Option<bool> {
316    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
317    match name {
318        "-j"
319        | "--jobs"
320        | "--test-threads"
321        | "--retries"
322        | "--flaky-result"
323        | "--max-fail"
324        | "--no-tests"
325        | "--failure-output"
326        | "--success-output"
327        | "--status-level"
328        | "--final-status-level"
329        | "--show-progress"
330        | "--max-progress-running"
331        | "--message-format"
332        | "--message-format-version" => Some(!argument.contains('=')),
333        "--fail-fast"
334        | "--ff"
335        | "--no-fail-fast"
336        | "--nff"
337        | "--no-capture"
338        | "--nocapture"
339        | "--no-output-indent"
340        | "--hide-progress-bar"
341        | "--no-input-handler" => Some(false),
342        _ if argument.starts_with("-j") && argument.len() > 2 => Some(false),
343        _ => None,
344    }
345}
346
347fn nextest_unsupported_run_option(argument: &str) -> Option<bool> {
348    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
349    match name {
350        "-R"
351        | "--rerun"
352        | "--debugger"
353        | "--tracer"
354        | "--stress-count"
355        | "--stress-duration"
356        | "--archive-file"
357        | "--archive-format"
358        | "--extract-to"
359        | "--cargo-metadata"
360        | "--workspace-remap"
361        | "--binaries-metadata"
362        | "--target-dir-remap"
363        | "--build-dir-remap" => Some(!argument.contains('=')),
364        "--no-run" | "--extract-overwrite" | "--persist-extract-tempdir" => Some(false),
365        _ => None,
366    }
367}
368
369fn nextest_shared_option(argument: &str) -> Option<bool> {
370    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
371    match name {
372        "--color"
373        | "-p"
374        | "--package"
375        | "--exclude"
376        | "--bin"
377        | "--example"
378        | "--test"
379        | "--bench"
380        | "-F"
381        | "--features"
382        | "--build-jobs"
383        | "--cargo-profile"
384        | "--target"
385        | "--target-dir"
386        | "--cargo-message-format"
387        | "--config"
388        | "--timings"
389        | "-Z"
390        | "--run-ignored"
391        | "--partition"
392        | "--platform-filter"
393        | "-E"
394        | "--filterset"
395        | "--filter-expr"
396        | "--manifest-path"
397        | "--config-file"
398        | "--user-config-file"
399        | "--tool-config-file"
400        | "-P"
401        | "--profile" => Some(!argument.contains('=')),
402        "--no-pager"
403        | "-v"
404        | "--verbose"
405        | "--workspace"
406        | "--all"
407        | "--lib"
408        | "--bins"
409        | "--examples"
410        | "--tests"
411        | "--benches"
412        | "--all-targets"
413        | "--all-features"
414        | "--no-default-features"
415        | "-r"
416        | "--release"
417        | "--unit-graph"
418        | "--frozen"
419        | "--locked"
420        | "--offline"
421        | "--cargo-quiet"
422        | "--cargo-verbose"
423        | "--ignore-rust-version"
424        | "--future-incompat-report"
425        | "--ignore-default-filter"
426        | "--override-version-check" => Some(false),
427        _ if argument.starts_with("-p") && argument.len() > 2 => Some(false),
428        _ if argument.starts_with("-F") && argument.len() > 2 => Some(false),
429        _ if argument.starts_with("-E") && argument.len() > 2 => Some(false),
430        _ if argument.starts_with("-P") && argument.len() > 2 => Some(false),
431        _ if argument.starts_with("-Z") && argument.len() > 2 => Some(false),
432        _ if argument.len() > 2
433            && argument.starts_with('-')
434            && argument[1..].bytes().all(|byte| byte == b'v') =>
435        {
436            Some(false)
437        }
438        _ if argument.starts_with("--timings=") => Some(false),
439        _ => None,
440    }
441}
442
443/// Reprojects a pinned `nextest run` invocation into the stable machine-readable
444/// `nextest list` contract. Selection/build/configuration arguments are kept
445/// byte-for-byte. Runner and presentation arguments are removed because they
446/// do not affect the selected-test catalog. Options whose selection semantics
447/// require an external recording or a different execution mode fail closed.
448pub(crate) fn nextest_list_invocation(
449    invocation: &CargoTestInvocation,
450) -> Result<NextestListInvocation, RustTestRunnerError> {
451    if invocation.kind != RustCargoCommandKind::NextestRun {
452        return Err(RustTestRunnerError::UnsupportedCommand(
453            "a nextest list projection requires `cargo nextest run`".into(),
454        ));
455    }
456    let command = invocation.command_position().ok_or_else(|| {
457        RustTestRunnerError::UnsupportedCommand(
458            "the expanded Cargo invocation lost its nextest run subcommand".into(),
459        )
460    })?;
461    let mut arguments = invocation.arguments[..command].to_vec();
462    arguments.extend(["nextest".into(), "list".into()]);
463    let mut index = command + 2;
464    while index < invocation.arguments.len() {
465        let argument = &invocation.arguments[index];
466        if argument == "--" {
467            arguments.extend(invocation.arguments[index..].iter().cloned());
468            break;
469        }
470        if let Some(takes_value) = nextest_unsupported_run_option(argument) {
471            if takes_value && invocation.arguments.get(index + 1).is_none() {
472                return Err(RustTestRunnerError::UnsupportedCommand(format!(
473                    "nextest option {argument} has no value"
474                )));
475            }
476            return Err(RustTestRunnerError::UnsupportedCommand(format!(
477                "nextest option {argument} cannot yet be assigned exact selected-test identity"
478            )));
479        }
480        if let Some(takes_value) = nextest_run_only_option(argument) {
481            if takes_value {
482                index += 1;
483                if index == invocation.arguments.len() {
484                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
485                        "nextest option {argument} has no value"
486                    )));
487                }
488            }
489        } else if let Some(takes_value) = nextest_shared_option(argument) {
490            arguments.push(argument.clone());
491            if takes_value {
492                index += 1;
493                let value = invocation.arguments.get(index).ok_or_else(|| {
494                    RustTestRunnerError::UnsupportedCommand(format!(
495                        "nextest option {argument} has no value"
496                    ))
497                })?;
498                arguments.push(value.clone());
499            }
500        } else if argument.starts_with('-') {
501            return Err(RustTestRunnerError::UnsupportedCommand(format!(
502                "the pinned nextest run contract does not recognize option {argument}"
503            )));
504        } else {
505            arguments.push(argument.clone());
506        }
507        index += 1;
508    }
509    arguments.extend(["--message-format".into(), "json".into()]);
510    Ok(NextestListInvocation {
511        arguments,
512        runner_arguments: invocation.runner_arguments.clone(),
513    })
514}
515
516pub(crate) fn cargo_invocation(
517    root: &Path,
518    command: &[String],
519) -> Result<CargoTestInvocation, RustTestRunnerError> {
520    // A process argv is already tokenized. Joining and shell-parsing a direct
521    // Cargo command destroys quotes that are payload (not shell syntax), most
522    // notably TOML strings passed to Cargo's --config. Only opaque wrapper or
523    // package-script commands need textual expansion and shell tokenization.
524    let words = if command.iter().any(|word| executable_name(word) == "cargo") {
525        command.to_vec()
526    } else {
527        let expanded = crate::project_discovery::expanded_command(root, command);
528        shell_words(&expanded)?
529    };
530    let cargo = words
531        .iter()
532        .position(|word| executable_name(word) == "cargo")
533        .ok_or_else(|| RustTestRunnerError::UnsupportedCommand(
534            "Rust was detected, but the expanded command does not expose a stable Cargo invocation".into(),
535        ))?;
536    let cargo_test = words[cargo + 1..]
537        .iter()
538        .position(|word| word == "test")
539        .map(|position| cargo + 1 + position);
540    let nextest = words[cargo + 1..]
541        .windows(2)
542        .position(|pair| pair == ["nextest", "run"])
543        .map(|position| cargo + 1 + position);
544    let (kind, command) = match (cargo_test, nextest) {
545        (Some(test), None) => (RustCargoCommandKind::CargoTest, test),
546        (None, Some(nextest)) => (RustCargoCommandKind::NextestRun, nextest),
547        (Some(_), Some(_)) => {
548            return Err(RustTestRunnerError::UnsupportedCommand(
549                "the Cargo invocation ambiguously contains both test and nextest run".into(),
550            ));
551        }
552        (None, None) => {
553            return Err(RustTestRunnerError::UnsupportedCommand(
554                "the owned Rust runner currently requires `cargo test` or `cargo nextest run`; cross remains explicitly unsupported"
555                    .into(),
556            ));
557        }
558    };
559    if words[cargo + 1..command]
560        .iter()
561        .any(|word| matches!(word.as_str(), "&&" | "||" | ";" | "|"))
562    {
563        return Err(RustTestRunnerError::UnsupportedCommand(
564            "the Cargo invocation contains a shell boundary before `test`".into(),
565        ));
566    }
567    let command_end = command
568        + if kind == RustCargoCommandKind::NextestRun {
569            1
570        } else {
571            0
572        };
573    let mut arguments = words[cargo + 1..=command_end].to_vec();
574    let mut runner_arguments = Vec::new();
575    let mut after_separator = false;
576    for argument in &words[command_end + 1..] {
577        if argument == "--" && !after_separator {
578            after_separator = true;
579            continue;
580        }
581        if matches!(argument.as_str(), "&&" | "||" | ";" | "|") {
582            return Err(RustTestRunnerError::UnsupportedCommand(
583                "the Cargo test command contains an unsupported shell boundary".into(),
584            ));
585        }
586        if after_separator {
587            runner_arguments.push(argument.clone());
588        } else {
589            arguments.push(argument.clone());
590        }
591    }
592    Ok(CargoTestInvocation {
593        program: words[cargo].clone(),
594        kind,
595        arguments,
596        runner_arguments,
597    })
598}
599
600fn cargo_option_takes_value(argument: &str) -> Option<bool> {
601    let name = argument.split_once('=').map_or(argument, |(name, _)| name);
602    match name {
603        "-p" | "--package" | "--exclude" | "--bin" | "--example" | "--test" | "--bench" | "-F"
604        | "--features" | "-j" | "--jobs" | "--profile" | "--target" | "--target-dir"
605        | "--message-format" | "--color" | "--config" | "-Z" | "--manifest-path" => {
606            Some(!argument.contains('='))
607        }
608        "--no-run"
609        | "--no-fail-fast"
610        | "--future-incompat-report"
611        | "-q"
612        | "--quiet"
613        | "-v"
614        | "--verbose"
615        | "--workspace"
616        | "--all"
617        | "--lib"
618        | "--bins"
619        | "--examples"
620        | "--tests"
621        | "--benches"
622        | "--all-targets"
623        | "--doc"
624        | "--all-features"
625        | "--no-default-features"
626        | "-r"
627        | "--release"
628        | "--timings"
629        | "--ignore-rust-version"
630        | "--locked"
631        | "--offline"
632        | "--frozen" => Some(false),
633        _ if argument.starts_with("-vv") => Some(false),
634        _ if argument.starts_with("-p") && argument.len() > 2 => Some(false),
635        _ if argument.starts_with("-F") && argument.len() > 2 => Some(false),
636        _ if argument.starts_with("-j") && argument.len() > 2 => Some(false),
637        _ => None,
638    }
639}
640
641pub(crate) fn rust_libtest_selection(
642    invocation: &CargoTestInvocation,
643) -> Result<RustLibtestSelection, RustTestRunnerError> {
644    if invocation.kind != RustCargoCommandKind::CargoTest {
645        return Err(RustTestRunnerError::UnsupportedCommand(
646            "libtest selection cannot be reconstructed from a nextest command".into(),
647        ));
648    }
649    let test = invocation
650        .arguments
651        .iter()
652        .position(|argument| argument == "test")
653        .ok_or_else(|| {
654            RustTestRunnerError::UnsupportedCommand(
655                "the expanded Cargo invocation lost its test subcommand".into(),
656            )
657        })?;
658    let mut cargo_filter = None;
659    let mut index = test + 1;
660    while index < invocation.arguments.len() {
661        let argument = &invocation.arguments[index];
662        if argument.starts_with('-') {
663            let takes_value = cargo_option_takes_value(argument).ok_or_else(|| {
664                RustTestRunnerError::UnsupportedCommand(format!(
665                    "the pinned Cargo test contract does not recognize option {argument}"
666                ))
667            })?;
668            if takes_value {
669                index += 1;
670                if index == invocation.arguments.len() {
671                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
672                        "Cargo option {argument} has no value"
673                    )));
674                }
675            }
676        } else if cargo_filter.replace(argument.clone()).is_some() {
677            return Err(RustTestRunnerError::UnsupportedCommand(
678                "Cargo test has more than one pre-separator TESTNAME".into(),
679            ));
680        }
681        index += 1;
682    }
683
684    let mut list_arguments = cargo_filter.into_iter().collect::<Vec<_>>();
685    let mut test_threads = None;
686    let mut index = 0;
687    while index < invocation.runner_arguments.len() {
688        let argument = &invocation.runner_arguments[index];
689        match argument.as_str() {
690            "--ignored" | "--include-ignored" | "--exclude-should-panic" | "--test" | "--bench" => {
691                list_arguments.push(argument.clone());
692            }
693            "--exact" => list_arguments.push(argument.clone()),
694            "--skip" => {
695                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
696                    RustTestRunnerError::UnsupportedCommand(
697                        "libtest --skip has no filter value".into(),
698                    )
699                })?;
700                list_arguments.extend([argument.clone(), value.clone()]);
701                index += 1;
702            }
703            _ if argument.starts_with("--skip=") && argument.len() > "--skip=".len() => {
704                list_arguments.push(argument.clone());
705            }
706            "--test-threads" => {
707                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
708                    RustTestRunnerError::UnsupportedCommand(
709                        "libtest --test-threads has no value".into(),
710                    )
711                })?;
712                let parsed = parse_libtest_threads(value)?;
713                if test_threads.replace(parsed).is_some() {
714                    return Err(RustTestRunnerError::UnsupportedCommand(
715                        "libtest --test-threads was provided more than once".into(),
716                    ));
717                }
718                index += 1;
719            }
720            _ if argument.starts_with("--test-threads=") => {
721                let value = &argument["--test-threads=".len()..];
722                let parsed = parse_libtest_threads(value)?;
723                if test_threads.replace(parsed).is_some() {
724                    return Err(RustTestRunnerError::UnsupportedCommand(
725                        "libtest --test-threads was provided more than once".into(),
726                    ));
727                }
728            }
729            "-Z" => {
730                let value = invocation.runner_arguments.get(index + 1).ok_or_else(|| {
731                    RustTestRunnerError::UnsupportedCommand(
732                        "libtest -Z has no feature value".into(),
733                    )
734                })?;
735                // `exclude-should-panic` and the other unstable selection
736                // flags must be parsed under the same libtest feature gate
737                // during discovery. The user's exact pair is also preserved
738                // unchanged for the real artifact execution.
739                list_arguments.extend([argument.clone(), value.clone()]);
740                index += 1;
741            }
742            _ if argument.starts_with("-Z") && argument.len() > 2 => {
743                list_arguments.push(argument.clone());
744            }
745            "--logfile" | "--color" | "--format" | "--shuffle-seed" => {
746                if invocation.runner_arguments.get(index + 1).is_none() {
747                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
748                        "libtest {argument} has no value"
749                    )));
750                }
751                // Presentation-only values must not leak into the synthetic
752                // terse listing. The original argument and value are passed
753                // byte-for-byte to the one stock artifact execution.
754                index += 1;
755            }
756            _ if ["--logfile=", "--color=", "--format=", "--shuffle-seed="]
757                .iter()
758                .any(|prefix| argument.starts_with(prefix) && argument.len() > prefix.len()) => {}
759            "--force-run-in-process"
760            | "--fail-fast"
761            | "--no-capture"
762            | "--nocapture"
763            | "-q"
764            | "--quiet"
765            | "--show-output"
766            | "--report-time"
767            | "--ensure-time"
768            | "--shuffle" => {
769                // These affect only scheduling, execution or presentation.
770                // They are intentionally absent from discovery and retained
771                // unchanged in the real artifact argv.
772            }
773            "--list" | "-h" | "--help" => {
774                return Err(RustTestRunnerError::UnsupportedCommand(format!(
775                    "libtest {argument} does not execute a test suite; exact non-execution mode support is not implemented"
776                )));
777            }
778            _ if !argument.starts_with('-') => list_arguments.push(argument.clone()),
779            _ => {
780                return Err(RustTestRunnerError::UnsupportedCommand(format!(
781                    "the pinned Rust 1.95 libtest discovery contract does not recognize option {argument}"
782                )));
783            }
784        }
785        index += 1;
786    }
787    Ok(RustLibtestSelection { list_arguments })
788}
789
790fn parse_libtest_threads(value: &str) -> Result<usize, RustTestRunnerError> {
791    match value.parse::<usize>() {
792        Ok(0) => Err(RustTestRunnerError::UnsupportedCommand(
793            "argument for --test-threads must not be 0".into(),
794        )),
795        Ok(value) => Ok(value),
796        Err(error) => Err(RustTestRunnerError::UnsupportedCommand(format!(
797            "argument for --test-threads must be a number > 0 (error: {error})"
798        ))),
799    }
800}
801
802pub(crate) fn rust_cargo_execution_selection(
803    invocation: &CargoTestInvocation,
804) -> Result<RustCargoExecutionSelection, RustTestRunnerError> {
805    if invocation.kind == RustCargoCommandKind::NextestRun {
806        return Ok(RustCargoExecutionSelection {
807            run_libtests: true,
808            run_doctests: false,
809            doctest_arguments: Vec::new(),
810        });
811    }
812    let test = invocation
813        .arguments
814        .iter()
815        .position(|argument| argument == "test")
816        .ok_or_else(|| {
817            RustTestRunnerError::UnsupportedCommand(
818                "the expanded Cargo invocation lost its test subcommand".into(),
819            )
820        })?;
821    let mut doc = false;
822    let mut other_target = false;
823    let mut index = test + 1;
824    while index < invocation.arguments.len() {
825        let argument = &invocation.arguments[index];
826        let name = argument
827            .split_once('=')
828            .map_or(argument.as_str(), |(name, _)| name);
829        match name {
830            "--doc" => doc = true,
831            "--lib" | "--bins" | "--bin" | "--examples" | "--example" | "--tests" | "--test"
832            | "--benches" | "--bench" | "--all-targets" => other_target = true,
833            _ => {}
834        }
835        if argument.starts_with('-') {
836            let takes_value = cargo_option_takes_value(argument).ok_or_else(|| {
837                RustTestRunnerError::UnsupportedCommand(format!(
838                    "the pinned Cargo test contract does not recognize option {argument}"
839                ))
840            })?;
841            if takes_value {
842                index += 1;
843                if index == invocation.arguments.len() {
844                    return Err(RustTestRunnerError::UnsupportedCommand(format!(
845                        "Cargo option {argument} has no value"
846                    )));
847                }
848            }
849        }
850        index += 1;
851    }
852    if doc && other_target {
853        return Err(RustTestRunnerError::UnsupportedCommand(
854            "Cargo --doc cannot be combined with another explicit target selection".into(),
855        ));
856    }
857    let run_doctests = doc || !other_target;
858    let run_libtests = !doc;
859    let mut doctest_arguments = invocation.arguments.clone();
860    if run_doctests && !doc {
861        doctest_arguments.insert(test + 1, "--doc".into());
862    }
863    if !invocation.runner_arguments.is_empty() {
864        doctest_arguments.push("--".into());
865        doctest_arguments.extend(invocation.runner_arguments.iter().cloned());
866    }
867    Ok(RustCargoExecutionSelection {
868        run_libtests,
869        run_doctests,
870        doctest_arguments,
871    })
872}
873
874pub(crate) fn relative_source(root: &Path, path: &Path) -> Result<String, RustTestRunnerError> {
875    let relative = path
876        .strip_prefix(root)
877        .map_err(|_| RustTestRunnerError::UnsafeArtifact(path.display().to_string()))?;
878    if relative.as_os_str().is_empty()
879        || relative
880            .components()
881            .any(|part| !matches!(part, Component::Normal(_)))
882    {
883        return Err(RustTestRunnerError::UnsafeArtifact(
884            path.display().to_string(),
885        ));
886    }
887    Ok(relative.to_string_lossy().replace('\\', "/"))
888}
889
890fn build_test_artifacts(
891    project: &PreparedRustProject,
892    command: &[String],
893) -> Result<Vec<TestArtifact>, RustTestRunnerError> {
894    let mut invocation = cargo_invocation(&project.workspace_root, command)?;
895    invocation
896        .arguments
897        .extend(["--no-run".into(), "--message-format=json".into()]);
898    // The instrumented workspace is ephemeral and its sources are generated,
899    // so the HOST crate's lint policy must not reject them: serde builds with
900    // `#![deny(warnings)]`, and http's `if ({ frame ... })` decision wrapping
901    // trips `unused_parens` into a hard error under it. Capping lints to warn
902    // changes nothing about the user's own `cargo test` runs. The user's
903    // RUSTFLAGS are preserved ahead of the cap.
904    let output = Command::new(&invocation.program)
905        .args(invocation.arguments)
906        .current_dir(&project.workspace_root)
907        .env("CARGO_TARGET_DIR", &project.target_directory)
908        .env("RUSTFLAGS", capped_rustflags())
909        .output()
910        .map_err(|error| RustTestRunnerError::Launch(error.to_string()))?;
911    if !output.status.success() {
912        // With --message-format=json the compiler's diagnostics travel on
913        // stdout as JSON and Cargo's own summary on stderr; show both, or a
914        // failed build says only which crate failed.
915        let rendered = output
916            .stdout
917            .split(|byte| *byte == b'\n')
918            .filter_map(|line| serde_json::from_slice::<serde_json::Value>(line).ok())
919            .filter(|message| {
920                message["reason"] == "compiler-message" && message["message"]["level"] == "error"
921            })
922            .filter_map(|message| message["message"]["rendered"].as_str().map(str::to_owned))
923            .collect::<String>();
924        return Err(RustTestRunnerError::CargoFailed(format!(
925            "{rendered}{}",
926            String::from_utf8_lossy(&output.stderr).trim()
927        )));
928    }
929    let canonical_target = fs::canonicalize(&project.target_directory)
930        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
931    let mut artifacts = Vec::new();
932    for line in output
933        .stdout
934        .split(|byte| *byte == b'\n')
935        .filter(|line| !line.is_empty())
936    {
937        let message: CargoMessage = serde_json::from_slice(line)
938            .map_err(|error| RustTestRunnerError::CargoJson(error.to_string()))?;
939        if message.reason != "compiler-artifact"
940            || !message.profile.as_ref().is_some_and(|profile| profile.test)
941        {
942            continue;
943        }
944        let (Some(executable), Some(target)) = (message.executable, message.target) else {
945            continue;
946        };
947        let executable = fs::canonicalize(&executable)
948            .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
949        if !executable.starts_with(&canonical_target)
950            || !fs::metadata(&executable).is_ok_and(|metadata| metadata.is_file())
951        {
952            return Err(RustTestRunnerError::UnsafeArtifact(
953                executable.display().to_string(),
954            ));
955        }
956        let source = fs::canonicalize(target.src_path)
957            .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
958        let package_directory = message
959            .manifest_path
960            .as_deref()
961            .and_then(Path::parent)
962            .map_or_else(|| project.workspace_root.clone(), Path::to_path_buf);
963        artifacts.push(TestArtifact {
964            executable,
965            package_directory,
966            name: target.name,
967            kind: if target.kind.iter().any(|kind| kind == "test") {
968                "integration".into()
969            } else {
970                "unit".into()
971            },
972            source: relative_source(&project.workspace_root, &source)?,
973        });
974    }
975    artifacts.sort_by(|left, right| left.executable.cmp(&right.executable));
976    artifacts.dedup_by(|left, right| left.executable == right.executable);
977    if artifacts.is_empty() {
978        return Err(RustTestRunnerError::CargoJson(
979            "Cargo emitted no libtest artifacts".into(),
980        ));
981    }
982    Ok(artifacts)
983}
984
985fn list_tests(
986    executable: &Path,
987    environment: &[(&'static str, OsString)],
988) -> Result<Vec<String>, RustTestRunnerError> {
989    let output = Command::new(executable)
990        .args(["--list", "--format", "terse"])
991        .envs(environment.iter().map(|(key, value)| (key, value)))
992        .output()
993        .map_err(|error| RustTestRunnerError::Launch(error.to_string()))?;
994    if !output.status.success() {
995        // A libtest binary that dies on a signal writes nothing to stderr, so
996        // reporting stderr alone produces an empty, undiagnosable message.
997        // Name the artifact and how it ended, and fall back to stdout.
998        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned();
999        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned();
1000        let detail = if !stderr.is_empty() {
1001            stderr
1002        } else if !stdout.is_empty() {
1003            format!("no stderr; stdout was {stdout}")
1004        } else {
1005            "no output on either stream".to_owned()
1006        };
1007        return Err(RustTestRunnerError::ListFailed(format!(
1008            "{} exited with {} when asked to --list: {detail}",
1009            executable.display(),
1010            output.status
1011        )));
1012    }
1013    let mut tests = String::from_utf8_lossy(&output.stdout)
1014        .lines()
1015        .filter_map(|line| line.strip_suffix(": test"))
1016        .map(str::to_owned)
1017        .collect::<Vec<_>>();
1018    tests.sort();
1019    tests.dedup();
1020    Ok(tests)
1021}
1022
1023pub(crate) fn snapshot(
1024    manifest: &CoverageManifest,
1025    directory: &Path,
1026) -> Result<RuntimeSnapshot, RustTestRunnerError> {
1027    let points = manifest
1028        .points
1029        .iter()
1030        .map(|point| point.id.as_str())
1031        .collect::<BTreeSet<_>>();
1032    let alternatives = manifest
1033        .branches
1034        .iter()
1035        .flat_map(|branch| {
1036            branch
1037                .alternatives
1038                .iter()
1039                .map(|alternative| alternative.id.as_str())
1040        })
1041        .collect::<BTreeSet<_>>();
1042    let decisions = manifest
1043        .decisions
1044        .iter()
1045        .map(|decision| (decision.id.as_str(), decision))
1046        .collect::<BTreeMap<_, _>>();
1047    let mut hits = BTreeSet::new();
1048    let mut vectors = BTreeMap::<String, BTreeSet<(Vec<Option<bool>>, bool)>>::new();
1049    // Evidence files are named by the instrumentation that wrote them. A
1050    // test may build and run a program instrumented on its own -- a fixture
1051    // prepared inside the instrumented workspace -- and that program
1052    // inherits the evidence directory; its obligations are not this run's.
1053    let token = crate::rust_project::manifest_token(manifest);
1054    for (name, observations) in read_rust_probe_directory(directory)
1055        .map_err(|error| RustTestRunnerError::Probe(error.to_string()))?
1056    {
1057        if !name.starts_with(&token) {
1058            continue;
1059        }
1060        for observation in observations {
1061            match observation {
1062                RustProbeObservation::Hit { id } => {
1063                    if !points.contains(id.as_str()) && !alternatives.contains(id.as_str()) {
1064                        return Err(RustTestRunnerError::UnknownProbe(id));
1065                    }
1066                    hits.insert(id);
1067                }
1068                RustProbeObservation::Decision {
1069                    id,
1070                    values,
1071                    outcome,
1072                } => {
1073                    let Some(meta) = decisions.get(id.as_str()) else {
1074                        return Err(RustTestRunnerError::UnknownProbe(id));
1075                    };
1076                    if values.len() != meta.conditions.len() {
1077                        return Err(RustTestRunnerError::InvalidVector {
1078                            id,
1079                            expected: meta.conditions.len(),
1080                            actual: values.len(),
1081                        });
1082                    }
1083                    hits.insert(format!(
1084                        "{}:outcome:{}",
1085                        meta.id,
1086                        if outcome { "true" } else { "false" }
1087                    ));
1088                    vectors
1089                        .entry(meta.id.clone())
1090                        .or_default()
1091                        .insert((values, outcome));
1092                }
1093            }
1094        }
1095    }
1096    let mut decision_snapshots = Vec::new();
1097    for (id, observed) in vectors {
1098        let meta: DecisionMeta = (*decisions[id.as_str()]).clone();
1099        decision_snapshots.push(DecisionSnapshot {
1100            meta,
1101            vectors: observed
1102                .into_iter()
1103                .map(|(values, outcome)| McdcVector { values, outcome })
1104                .collect(),
1105        });
1106    }
1107    Ok(RuntimeSnapshot {
1108        decisions: decision_snapshots,
1109        hits: hits.into_iter().collect(),
1110        events: Vec::new(),
1111    })
1112}
1113
1114fn rust_coverage_model() -> CoverageModelDeclaration {
1115    CoverageModelDeclaration {
1116        language: "rust".into(),
1117        variant: "rust-owned-probes-v1".into(),
1118        name: "supercov-rust-owned-v1".into(),
1119        completeness_meaning: "Every semantics-proven Rust obligation in the owned source denominator was observed; explicit manifest limitations identify unmeasured Rust surfaces.".into(),
1120        measured: vec![
1121            "owned Rust statements and function entries".into(),
1122            "owned atomic condition vectors and decision outcomes".into(),
1123            "exact process-per-libtest attribution".into(),
1124            "exact process-per-doctest attribution".into(),
1125        ],
1126        not_measured: vec![
1127            "macro-expanded and generated Rust code".into(),
1128            "const-evaluated code and unsupported structural branch probes".into(),
1129            "causal linkage to individual actions or passing assertions".into(),
1130            "all input values, semantic partitions, paths, or concurrency interleavings".into(),
1131            "mutation score or assertion fault-detection strength".into(),
1132        ],
1133    }
1134}
1135
1136/// The user's RUSTFLAGS, then a cap so the HOST crate's lint policy cannot
1137/// reject generated sources: serde builds with `#![deny(warnings)]`, and
1138/// http's `if ({ frame ... })` decision wrapping trips `unused_parens` into a
1139/// hard error under it. The user's own `cargo test` runs are unaffected.
1140/// The stack an instrumented test thread gets. Probes add stack to every
1141/// frame -- a decision frame, a `?` operand copied through its probe, a
1142/// wrapper's temporaries -- and a test that recurses to a depth chosen against
1143/// the default 2 MiB (serde_json's recursion-limit test) overflows under
1144/// instrumentation. libtest sizes test threads from `RUST_MIN_STACK`, so an
1145/// instrumented process gets 16 MiB unless the user chose a size. The
1146/// reservation is virtual; only touched pages cost memory.
1147pub(crate) fn instrumented_stack_environment() -> Vec<(&'static str, &'static str)> {
1148    if std::env::var_os("RUST_MIN_STACK").is_some() {
1149        Vec::new()
1150    } else {
1151        vec![("RUST_MIN_STACK", "16777216")]
1152    }
1153}
1154
1155/// One path the selected toolchain reports through `rustc --print`.
1156fn rustc_print_path(request: &str) -> Result<PathBuf, RustTestRunnerError> {
1157    let output = Command::new("rustc")
1158        .args(["--print", request])
1159        .output()
1160        .map_err(|error| {
1161            RustTestRunnerError::Launch(format!("rustc --print {request}: {error}"))
1162        })?;
1163    if !output.status.success() {
1164        return Err(RustTestRunnerError::Launch(format!(
1165            "rustc --print {request} exited with {}",
1166            output.status
1167        )));
1168    }
1169    Ok(PathBuf::from(
1170        String::from_utf8_lossy(&output.stdout).trim(),
1171    ))
1172}
1173
1174/// The selected toolchain's sysroot, as `rustc --print sysroot` reports it.
1175pub(crate) fn rustc_sysroot() -> Result<PathBuf, RustTestRunnerError> {
1176    rustc_print_path("sysroot")
1177}
1178
1179/// The directory holding the toolchain's own dynamic libraries
1180/// (`<sysroot>/lib/rustlib/<host>/lib`), where libstd's dylib lives; the
1181/// plain `<sysroot>/lib` no longer holds it.
1182pub(crate) fn rustc_target_libdir() -> Result<PathBuf, RustTestRunnerError> {
1183    rustc_print_path("target-libdir")
1184}
1185
1186/// The dynamic library search path Cargo gives a test binary it runs: the
1187/// artifact's own directories and the toolchain's target library directory.
1188/// A proc-macro crate's test harness links libstd dynamically -- async-trait's
1189/// and serde_derive's cannot even list their tests without this -- and any
1190/// dylib dependency is found the same way. The user's own path follows.
1191pub(crate) fn dynamic_library_environment(
1192    target_libdir: &Path,
1193    executable: &Path,
1194) -> Vec<(&'static str, OsString)> {
1195    let variable = if cfg!(target_os = "macos") {
1196        "DYLD_FALLBACK_LIBRARY_PATH"
1197    } else if cfg!(windows) {
1198        "PATH"
1199    } else {
1200        "LD_LIBRARY_PATH"
1201    };
1202    let mut entries = Vec::new();
1203    if let Some(deps) = executable.parent() {
1204        entries.push(deps.to_path_buf());
1205        if let Some(profile) = deps.parent() {
1206            entries.push(profile.to_path_buf());
1207        }
1208    }
1209    entries.push(target_libdir.to_path_buf());
1210    if let Some(existing) = std::env::var_os(variable) {
1211        entries.extend(std::env::split_paths(&existing));
1212    }
1213    match std::env::join_paths(entries) {
1214        Ok(value) => vec![(variable, value)],
1215        Err(_) => Vec::new(),
1216    }
1217}
1218
1219pub(crate) fn capped_rustflags() -> String {
1220    let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
1221    if !rustflags.is_empty() {
1222        rustflags.push(' ');
1223    }
1224    rustflags.push_str("--cap-lints=warn");
1225    rustflags
1226}
1227
1228pub(crate) fn io_error(error: impl std::fmt::Display) -> RustTestRunnerError {
1229    RustTestRunnerError::Io(error.to_string())
1230}
1231
1232/// A libtest case that exited cleanly but ran nothing, or ran its one test as
1233/// ignored, was skipped rather than passed.
1234pub(crate) fn libtest_skipped(exit: i32, stdout: &str) -> bool {
1235    exit == 0 && (stdout.contains("running 0 tests") || stdout.contains("; 1 ignored;"))
1236}
1237
1238/// Limitation IDs are unique across a declaration, so each runner names its
1239/// own: the libtest runner keeps the original IDs, rustdoc's carry its name.
1240fn rust_runner_limitations(runner: &str) -> Vec<FrontendLimitation> {
1241    let prefix = if runner == "rust-libtest" {
1242        "rust".to_owned()
1243    } else {
1244        runner.to_owned()
1245    };
1246    vec![
1247        FrontendLimitation {
1248            id: format!("{prefix}-action-linkage-unavailable"),
1249            scopes: vec![FrontendLimitationScope::Action],
1250            reason: "Rust test frameworks expose no general action lifecycle".into(),
1251        },
1252        FrontendLimitation {
1253            id: format!("{prefix}-assertion-linkage-unavailable"),
1254            scopes: vec![FrontendLimitationScope::Assertion],
1255            reason: "assertion macros do not expose a stable per-assertion success lifecycle"
1256                .into(),
1257        },
1258    ]
1259}
1260
1261fn rust_runner_declaration(runner: &str) -> FrontendRunnerDeclaration {
1262    FrontendRunnerDeclaration {
1263        runner: runner.into(),
1264        execution_model: ExecutionModel::ProcessPerTest,
1265        attribution: FrontendAttribution {
1266            run: AttributionPrecision::Exact,
1267            worker: AttributionPrecision::Exact,
1268            test: AttributionPrecision::Exact,
1269            retry: AttributionPrecision::Exact,
1270            phase: AttributionPrecision::Exact,
1271            action: AttributionPrecision::Unavailable,
1272            assertion: AttributionPrecision::Unavailable,
1273        },
1274        limitations: rust_runner_limitations(runner),
1275    }
1276}
1277
1278/// The manifest's structural limitation IDs, as the declaration references them.
1279fn structural_limitations(project: &PreparedRustProject) -> Vec<String> {
1280    project
1281        .manifest
1282        .limitations
1283        .iter()
1284        .filter_map(|item| {
1285            item.get("id")
1286                .and_then(|value| value.as_str())
1287                .map(str::to_owned)
1288        })
1289        .collect()
1290}
1291
1292pub fn run_prepared_rust_tests(
1293    project: &PreparedRustProject,
1294    command: &[String],
1295    run_id: &str,
1296    generated_at: &str,
1297    diagnostics: &mut dyn Write,
1298) -> Result<RustFrontendRun, RustTestRunnerError> {
1299    let invocation = cargo_invocation(&project.workspace_root, command)?;
1300    let selection = rust_cargo_execution_selection(&invocation)?;
1301    let build_started = Instant::now();
1302    // `cargo test --doc` alone builds nothing here: Cargo refuses `--no-run`
1303    // with `--doc`, and the doctest phase below builds what it runs.
1304    // nextest builds for itself; Cargo builds the libtest artifacts here.
1305    let artifacts = if selection.run_libtests && invocation.kind == RustCargoCommandKind::CargoTest
1306    {
1307        build_test_artifacts(project, command)?
1308    } else {
1309        Vec::new()
1310    };
1311    let build_ms = build_started.elapsed().as_secs_f64() * 1000.0;
1312    let evidence_root = project
1313        .workspace_root
1314        .join(".supercov/rust-evidence")
1315        .join(run_id);
1316    fs::create_dir_all(&evidence_root)
1317        .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1318    let mut results = Vec::new();
1319    let mut overall_exit = 0;
1320    let execution_started = Instant::now();
1321    if invocation.kind == RustCargoCommandKind::NextestRun {
1322        // nextest builds, schedules and retries on its own; this program is
1323        // its target runner and records every attempt it launches.
1324        let outcome = crate::rust_owned_nextest::run_nextest(
1325            project,
1326            &invocation,
1327            &evidence_root.join("nextest"),
1328            run_id,
1329            diagnostics,
1330        )?;
1331        let artifact_count = outcome.artifact_files.len();
1332        return Ok(RustFrontendRun {
1333            declaration: FrontendRunDeclaration {
1334                protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
1335                frontend_id: "rust".into(),
1336                frontend_version: "rust-owned-v1".into(),
1337                language: "rust".into(),
1338                structural_source: StructuralSource::OwnedProbes,
1339                runners: vec![rust_runner_declaration("nextest")],
1340                structural_limitations: structural_limitations(project),
1341            },
1342            request: CoverageReportRequest {
1343                run_id: run_id.into(),
1344                manifest: project.manifest.clone(),
1345                raw_results: outcome.results,
1346                generated_at: generated_at.into(),
1347                coverage_model: Some(rust_coverage_model()),
1348                integrity: None,
1349                test_exit_code: ExitCodeInput::Present(Some(outcome.exit_code)),
1350            },
1351            exit_code: outcome.exit_code,
1352            artifacts: artifact_count,
1353            artifact_files: outcome.artifact_files,
1354            build_ms,
1355            execution_ms: execution_started.elapsed().as_secs_f64() * 1000.0,
1356        });
1357    }
1358    let mut tasks = Vec::new();
1359    let target_libdir = if artifacts.is_empty() {
1360        PathBuf::new()
1361    } else {
1362        rustc_target_libdir()?
1363    };
1364    for (artifact_index, artifact) in artifacts.iter().enumerate() {
1365        let tests = list_tests(
1366            &artifact.executable,
1367            &dynamic_library_environment(&target_libdir, &artifact.executable),
1368        )?;
1369        let contexts = preflight_rust_test_contexts(tests.clone())
1370            .map_err(|error| RustTestRunnerError::Context(error.to_string()))?;
1371        for (test_index, test) in tests.into_iter().enumerate() {
1372            let directory = evidence_root.join(format!("{artifact_index:04}-{test_index:08}"));
1373            fs::create_dir(&directory)
1374                .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1375            tasks.push(ProcessTask {
1376                ordinal: tasks.len(),
1377                artifact_index,
1378                test_index,
1379                artifact: artifact.clone(),
1380                context_id: contexts[&test],
1381                test,
1382                directory,
1383            });
1384        }
1385    }
1386    let workers = std::thread::available_parallelism()
1387        .map(usize::from)
1388        .unwrap_or(1)
1389        .min(tasks.len().max(1));
1390    let next = AtomicUsize::new(0);
1391    let outcomes = Mutex::new(Vec::<Result<ProcessOutcome, String>>::with_capacity(
1392        tasks.len(),
1393    ));
1394    std::thread::scope(|scope| {
1395        for _ in 0..workers {
1396            scope.spawn(|| {
1397                loop {
1398                    let index = next.fetch_add(1, Ordering::Relaxed);
1399                    let Some(task) = tasks.get(index) else { break };
1400                    let result = Command::new(&task.artifact.executable)
1401                        // No --nocapture: libtest's in-memory capture is what
1402                        // plain `cargo test` gives users, and it re-emits a
1403                        // failing test's output, which `.output()` still
1404                        // receives. Streaming instead turns every print in a
1405                        // hot loop into an unbuffered stderr syscall: bytes'
1406                        // advance_bytes_mut_remaining_capacity prints per
1407                        // iteration, and --nocapture alone cost 6.9s of its
1408                        // 14.4s (baseline 8.0s streamed vs 1.0s captured).
1409                        .args(["--exact", &task.test])
1410                        .current_dir(&task.artifact.package_directory)
1411                        .envs(instrumented_stack_environment())
1412                        .envs(dynamic_library_environment(
1413                            &target_libdir,
1414                            &task.artifact.executable,
1415                        ))
1416                        .env("SUPERCOV_RUST_EVIDENCE_DIR", &task.directory)
1417                        .env(
1418                            crate::rust_probe_transport::RUST_CONTEXT_ENV,
1419                            format!("{:016x}", task.context_id),
1420                        )
1421                        .output()
1422                        .map(|output| ProcessOutcome {
1423                            task: ProcessTask {
1424                                ordinal: task.ordinal,
1425                                artifact_index: task.artifact_index,
1426                                test_index: task.test_index,
1427                                artifact: task.artifact.clone(),
1428                                test: task.test.clone(),
1429                                context_id: task.context_id,
1430                                directory: task.directory.clone(),
1431                            },
1432                            output,
1433                        })
1434                        .map_err(|error| error.to_string());
1435                    outcomes
1436                        .lock()
1437                        .expect("Rust test result lock poisoned")
1438                        .push(result);
1439                }
1440            });
1441        }
1442    });
1443    let mut outcomes = outcomes
1444        .into_inner()
1445        .map_err(|_| RustTestRunnerError::Io("Rust test result lock poisoned".into()))?
1446        .into_iter()
1447        .map(|result| result.map_err(RustTestRunnerError::Launch))
1448        .collect::<Result<Vec<_>, _>>()?;
1449    outcomes.sort_by_key(|outcome| outcome.task.ordinal);
1450    for outcome in outcomes {
1451        let ProcessTask {
1452            artifact_index,
1453            test_index,
1454            artifact,
1455            test,
1456            directory,
1457            ..
1458        } = outcome.task;
1459        // Target names are not workspace-unique: two packages may both
1460        // expose `lib` or the same integration-test target. Source path +
1461        // libtest name is stable and unique within the frozen workspace.
1462        let test_id = format!("{}::{test}", artifact.source);
1463        let worker_id = format!("artifact-{artifact_index:04}");
1464        let attempt_id = format!("{run_id}:{artifact_index:04}:{test_index:08}");
1465        let output = outcome.output;
1466        let exit = output.status.code().unwrap_or(1);
1467        let stdout = String::from_utf8_lossy(&output.stdout);
1468        let skipped = libtest_skipped(exit, &stdout);
1469        if exit != 0 {
1470            writeln!(diagnostics, "[supercov] Rust test failed: {test_id}")
1471                .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1472            diagnostics
1473                .write_all(&output.stdout)
1474                .and_then(|_| diagnostics.write_all(&output.stderr))
1475                .map_err(|error| RustTestRunnerError::Io(error.to_string()))?;
1476        }
1477        if exit != 0 {
1478            overall_exit = exit;
1479        }
1480        results.push(RawTestResult {
1481            test_id: Some(test_id.clone()),
1482            scope: Some(ExecutionScope {
1483                version: 1,
1484                run_id: run_id.into(),
1485                worker_id,
1486                test_id: test_id.clone(),
1487                test_key: format!("{}::{test}", artifact.source),
1488                retry: 0,
1489                attempt_id,
1490            }),
1491            test: test_id,
1492            test_file: Some(artifact.source.clone()),
1493            title: Some(test),
1494            retry: Some(0),
1495            status: Some(
1496                if exit != 0 {
1497                    "failed"
1498                } else if skipped {
1499                    "skipped"
1500                } else {
1501                    "passed"
1502                }
1503                .into(),
1504            ),
1505            expected_status: Some("passed".into()),
1506            flaky: false,
1507            provenance: TestProvenance {
1508                runner: "rust-libtest".into(),
1509                kind: artifact.kind,
1510                project: Some(artifact.name),
1511                source: "supercov-owned-process-per-test".into(),
1512            },
1513            role: "test".into(),
1514            phases: Vec::new(),
1515            runtime: vec![snapshot(&project.manifest, &directory)?],
1516            browser: Vec::new(),
1517            server: Vec::new(),
1518        });
1519    }
1520    let doctest_results =
1521        if selection.run_doctests && invocation.kind == RustCargoCommandKind::CargoTest {
1522            crate::rust_owned_doctests::run_doctests(
1523                project,
1524                &invocation,
1525                &selection,
1526                &evidence_root.join("doctests"),
1527                run_id,
1528                diagnostics,
1529                &mut overall_exit,
1530            )?
1531        } else {
1532            Vec::new()
1533        };
1534    // Only observed runners may be declared. A run with no tests at all keeps
1535    // the libtest declaration, as it always has.
1536    let ran_libtests = !results.is_empty();
1537    let ran_doctests = !doctest_results.is_empty();
1538    results.extend(doctest_results);
1539    let mut runners = Vec::new();
1540    if ran_libtests || !ran_doctests {
1541        runners.push(rust_runner_declaration("rust-libtest"));
1542    }
1543    if ran_doctests {
1544        runners.push(rust_runner_declaration("rustdoc"));
1545    }
1546    let structural_limitations = structural_limitations(project);
1547    Ok(RustFrontendRun {
1548        declaration: FrontendRunDeclaration {
1549            protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
1550            frontend_id: "rust".into(),
1551            frontend_version: "rust-owned-v1".into(),
1552            language: "rust".into(),
1553            structural_source: StructuralSource::OwnedProbes,
1554            runners,
1555            structural_limitations,
1556        },
1557        request: CoverageReportRequest {
1558            run_id: run_id.into(),
1559            manifest: project.manifest.clone(),
1560            raw_results: results,
1561            generated_at: generated_at.into(),
1562            coverage_model: Some(rust_coverage_model()),
1563            integrity: None,
1564            test_exit_code: ExitCodeInput::Present(Some(overall_exit)),
1565        },
1566        exit_code: overall_exit,
1567        artifacts: artifacts.len(),
1568        artifact_files: artifacts
1569            .iter()
1570            .map(|artifact| artifact.executable.clone())
1571            .collect(),
1572        build_ms,
1573        execution_ms: execution_started.elapsed().as_secs_f64() * 1000.0,
1574    })
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579    use std::time::{SystemTime, UNIX_EPOCH};
1580
1581    use super::*;
1582    use crate::{
1583        coverage_report::{ArchiveReportRequest, analyze_coverage_archive},
1584        evidence_archive::write_archive,
1585        frontend_protocol::validate_frontend_report_request,
1586        rust_project::prepare_rust_project,
1587    };
1588
1589    #[test]
1590    fn cargo_and_libtest_selection_is_preserved_without_presentation_guessing() {
1591        let root = Path::new(".");
1592        let invocation = cargo_invocation(
1593            root,
1594            &[
1595                "cargo".into(),
1596                "test".into(),
1597                "-p".into(),
1598                "fixture".into(),
1599                "authored".into(),
1600                "--".into(),
1601                "generated".into(),
1602                "--skip".into(),
1603                "slow".into(),
1604                "--include-ignored".into(),
1605            ],
1606        )
1607        .unwrap();
1608        assert_eq!(invocation.arguments, ["test", "-p", "fixture", "authored"]);
1609        assert_eq!(
1610            invocation.runner_arguments,
1611            ["generated", "--skip", "slow", "--include-ignored"]
1612        );
1613        let selection = rust_libtest_selection(&invocation).unwrap();
1614        assert_eq!(
1615            selection.list_arguments,
1616            [
1617                "authored",
1618                "generated",
1619                "--skip",
1620                "slow",
1621                "--include-ignored"
1622            ]
1623        );
1624    }
1625
1626    #[test]
1627    fn direct_cargo_argv_preserves_toml_quotes_inside_config_values() {
1628        let config = "target.host.runner=[\"runner with spaces\",\"--fixed\"]";
1629        let invocation = cargo_invocation(
1630            Path::new("."),
1631            &[
1632                "cargo".into(),
1633                "test".into(),
1634                "--config".into(),
1635                config.into(),
1636            ],
1637        )
1638        .unwrap();
1639        assert_eq!(invocation.arguments, ["test", "--config", config]);
1640    }
1641
1642    #[test]
1643    fn nextest_run_is_detected_without_reclassifying_its_filters_or_retries() {
1644        let invocation = cargo_invocation(
1645            Path::new("."),
1646            &[
1647                "cargo".into(),
1648                "+1.95.0".into(),
1649                "nextest".into(),
1650                "run".into(),
1651                "--retries".into(),
1652                "2".into(),
1653                "-E".into(),
1654                "test(/flaky/)".into(),
1655                "--".into(),
1656                "--nocapture".into(),
1657            ],
1658        )
1659        .unwrap();
1660        assert_eq!(invocation.kind, RustCargoCommandKind::NextestRun);
1661        assert_eq!(
1662            invocation.arguments,
1663            [
1664                "+1.95.0",
1665                "nextest",
1666                "run",
1667                "--retries",
1668                "2",
1669                "-E",
1670                "test(/flaky/)",
1671            ]
1672        );
1673        assert_eq!(invocation.runner_arguments, ["--nocapture"]);
1674        let execution = rust_cargo_execution_selection(&invocation).unwrap();
1675        assert!(execution.run_libtests);
1676        assert!(!execution.run_doctests);
1677        assert!(execution.doctest_arguments.is_empty());
1678        assert!(rust_libtest_selection(&invocation).is_err());
1679        assert_eq!(
1680            nextest_list_invocation(&invocation).unwrap(),
1681            NextestListInvocation {
1682                arguments: vec![
1683                    "+1.95.0".into(),
1684                    "nextest".into(),
1685                    "list".into(),
1686                    "-E".into(),
1687                    "test(/flaky/)".into(),
1688                    "--message-format".into(),
1689                    "json".into(),
1690                ],
1691                runner_arguments: vec!["--nocapture".into()],
1692            }
1693        );
1694    }
1695
1696    #[test]
1697    fn nextest_list_projection_preserves_selection_and_rejects_external_state() {
1698        let invocation = CargoTestInvocation {
1699            program: "cargo".into(),
1700            kind: RustCargoCommandKind::NextestRun,
1701            arguments: vec![
1702                "nextest".into(),
1703                "run".into(),
1704                "--package=fixture".into(),
1705                "--partition".into(),
1706                "hash:1/2".into(),
1707                "--test-threads=8".into(),
1708                "--failure-output".into(),
1709                "final".into(),
1710                "name".into(),
1711            ],
1712            runner_arguments: vec!["--exact".into(), "full::name".into()],
1713        };
1714        assert_eq!(
1715            nextest_list_invocation(&invocation).unwrap(),
1716            NextestListInvocation {
1717                arguments: vec![
1718                    "nextest".into(),
1719                    "list".into(),
1720                    "--package=fixture".into(),
1721                    "--partition".into(),
1722                    "hash:1/2".into(),
1723                    "name".into(),
1724                    "--message-format".into(),
1725                    "json".into(),
1726                ],
1727                runner_arguments: vec!["--exact".into(), "full::name".into()],
1728            }
1729        );
1730
1731        let mut rerun = invocation;
1732        rerun.arguments.extend(["--rerun".into(), "latest".into()]);
1733        assert!(
1734            nextest_list_invocation(&rerun)
1735                .unwrap_err()
1736                .to_string()
1737                .contains("cannot yet be assigned exact selected-test identity")
1738        );
1739    }
1740
1741    #[test]
1742    fn nextest_list_projection_preserves_post_separator_libtest_selection() {
1743        let invocation = cargo_invocation(
1744            Path::new("."),
1745            &[
1746                "cargo".into(),
1747                "nextest".into(),
1748                "run".into(),
1749                "--timings".into(),
1750                "-vv".into(),
1751                "--".into(),
1752                "--include-ignored".into(),
1753                "--skip".into(),
1754                "slow".into(),
1755                "--exact".into(),
1756                "tests::selected".into(),
1757            ],
1758        )
1759        .unwrap();
1760        assert_eq!(
1761            nextest_list_invocation(&invocation).unwrap(),
1762            NextestListInvocation {
1763                arguments: vec![
1764                    "nextest".to_owned(),
1765                    "list".to_owned(),
1766                    "--timings".to_owned(),
1767                    "-vv".to_owned(),
1768                    "--message-format".to_owned(),
1769                    "json".to_owned(),
1770                ],
1771                runner_arguments: vec![
1772                    "--include-ignored".to_owned(),
1773                    "--skip".to_owned(),
1774                    "slow".to_owned(),
1775                    "--exact".to_owned(),
1776                    "tests::selected".to_owned(),
1777                ],
1778            }
1779        );
1780    }
1781
1782    #[test]
1783    fn nextest_version_handshake_preserves_the_cargo_toolchain_selector() {
1784        let invocation = CargoTestInvocation {
1785            program: "cargo".into(),
1786            kind: RustCargoCommandKind::NextestRun,
1787            arguments: vec![
1788                "+1.95.0".into(),
1789                "nextest".into(),
1790                "run".into(),
1791                "-p".into(),
1792                "fixture".into(),
1793            ],
1794            runner_arguments: Vec::new(),
1795        };
1796        assert_eq!(
1797            nextest_version_arguments(&invocation).unwrap(),
1798            ["+1.95.0", "nextest", "--version"]
1799        );
1800    }
1801
1802    #[test]
1803    fn stock_libtest_presentation_and_scheduling_options_do_not_change_discovery() {
1804        let invocation = CargoTestInvocation {
1805            program: "cargo".into(),
1806            kind: RustCargoCommandKind::CargoTest,
1807            arguments: vec!["test".into(), "cargo-filter".into()],
1808            runner_arguments: [
1809                "runner-filter",
1810                "--nocapture",
1811                "--show-output",
1812                "--format=json",
1813                "--color",
1814                "never",
1815                "--test-threads=4",
1816                "--fail-fast",
1817                "--shuffle-seed",
1818                "17",
1819                "-Zunstable-options",
1820                "--exclude-should-panic",
1821            ]
1822            .into_iter()
1823            .map(str::to_owned)
1824            .collect(),
1825        };
1826        let selection = rust_libtest_selection(&invocation).unwrap();
1827        assert_eq!(
1828            selection.list_arguments,
1829            [
1830                "cargo-filter",
1831                "runner-filter",
1832                "-Zunstable-options",
1833                "--exclude-should-panic"
1834            ]
1835        );
1836    }
1837
1838    #[test]
1839    fn cargo_test_options_are_not_mistaken_for_the_test_name_filter() {
1840        let invocation = CargoTestInvocation {
1841            program: "cargo".into(),
1842            kind: RustCargoCommandKind::CargoTest,
1843            arguments: vec![
1844                "test".into(),
1845                "--manifest-path".into(),
1846                "nested/Cargo.toml".into(),
1847                "--features=one,two".into(),
1848                "needle".into(),
1849            ],
1850            runner_arguments: vec!["--ignored".into(), "other".into()],
1851        };
1852        let selection = rust_libtest_selection(&invocation).unwrap();
1853        assert_eq!(selection.list_arguments, ["needle", "--ignored", "other"]);
1854    }
1855
1856    #[test]
1857    fn libtest_thread_count_is_preserved_as_runner_scheduling() {
1858        for arguments in [vec!["--test-threads", "1"], vec!["--test-threads=8"]] {
1859            let invocation = CargoTestInvocation {
1860                program: "cargo".into(),
1861                kind: RustCargoCommandKind::CargoTest,
1862                arguments: vec!["test".into()],
1863                runner_arguments: arguments.into_iter().map(str::to_owned).collect(),
1864            };
1865            let selection = rust_libtest_selection(&invocation).unwrap();
1866            assert!(selection.list_arguments.is_empty());
1867        }
1868    }
1869
1870    #[test]
1871    fn invalid_or_duplicate_libtest_thread_counts_fail_closed() {
1872        for arguments in [
1873            vec!["--test-threads"],
1874            vec!["--test-threads=0"],
1875            vec!["--test-threads=abc"],
1876            vec!["--test-threads", "1", "--test-threads=2"],
1877        ] {
1878            let invocation = CargoTestInvocation {
1879                program: "cargo".into(),
1880                kind: RustCargoCommandKind::CargoTest,
1881                arguments: vec!["test".into()],
1882                runner_arguments: arguments.into_iter().map(str::to_owned).collect(),
1883            };
1884            assert!(rust_libtest_selection(&invocation).is_err());
1885        }
1886    }
1887
1888    #[test]
1889    fn cargo_target_selection_reproduces_when_cargo_runs_doctests() {
1890        let invocation = CargoTestInvocation {
1891            program: "cargo".into(),
1892            kind: RustCargoCommandKind::CargoTest,
1893            arguments: vec![
1894                "test".into(),
1895                "-p".into(),
1896                "fixture".into(),
1897                "needle".into(),
1898            ],
1899            runner_arguments: vec!["--include-ignored".into()],
1900        };
1901        let selection = rust_cargo_execution_selection(&invocation).unwrap();
1902        assert!(selection.run_libtests);
1903        assert!(selection.run_doctests);
1904        assert_eq!(
1905            selection.doctest_arguments,
1906            [
1907                "test",
1908                "--doc",
1909                "-p",
1910                "fixture",
1911                "needle",
1912                "--",
1913                "--include-ignored"
1914            ]
1915        );
1916
1917        let mut explicit_doc = invocation.clone();
1918        explicit_doc.arguments.insert(1, "--doc".into());
1919        let selection = rust_cargo_execution_selection(&explicit_doc).unwrap();
1920        assert!(!selection.run_libtests);
1921        assert!(selection.run_doctests);
1922
1923        for target in ["--lib", "--tests", "--all-targets", "--example=demo"] {
1924            let mut selected = invocation.clone();
1925            selected.arguments.insert(1, target.into());
1926            let selection = rust_cargo_execution_selection(&selected).unwrap();
1927            assert!(selection.run_libtests);
1928            assert!(!selection.run_doctests);
1929        }
1930    }
1931
1932    #[test]
1933    fn libtest_processes_run_in_their_package_directory() {
1934        // Cargo runs a test binary in its package's directory, and tokio's
1935        // `basic_fs` reads `Cargo.toml` from there; the workspace root has a
1936        // different manifest.
1937        let nonce = SystemTime::now()
1938            .duration_since(UNIX_EPOCH)
1939            .unwrap()
1940            .as_nanos();
1941        let root = std::env::temp_dir().join(format!(
1942            "supercov-rust-runner-cwd-{}-{nonce}",
1943            std::process::id()
1944        ));
1945        fs::create_dir_all(root.join("member/src")).unwrap();
1946        fs::write(
1947            root.join("Cargo.toml"),
1948            "[workspace]\nmembers = ['member']\nresolver = '2'\n",
1949        )
1950        .unwrap();
1951        fs::write(
1952            root.join("member/Cargo.toml"),
1953            "[package]\nname='member'\nversion='0.0.0'\nedition='2024'\n",
1954        )
1955        .unwrap();
1956        fs::write(
1957            root.join("member/src/lib.rs"),
1958            r#"
1959pub fn manifest() -> String {
1960    std::fs::read_to_string("Cargo.toml").unwrap()
1961}
1962#[cfg(test)]
1963mod tests {
1964    #[test] fn reads_own_manifest() { assert!(super::manifest().contains("name='member'")); }
1965}
1966"#,
1967        )
1968        .unwrap();
1969        let project = prepare_rust_project(&root).unwrap();
1970        let run = run_prepared_rust_tests(
1971            &project,
1972            &["cargo".into(), "test".into(), "--lib".into()],
1973            "rust-fixture-cwd",
1974            "2026-08-26T00:00:00.000Z",
1975            &mut Vec::new(),
1976        )
1977        .unwrap();
1978        assert_eq!(run.exit_code, 0, "{:?}", run.request.raw_results);
1979        assert_eq!(run.request.raw_results.len(), 1);
1980        assert_eq!(run.request.raw_results[0].status.as_deref(), Some("passed"));
1981        fs::remove_dir_all(&root).ok();
1982    }
1983
1984    #[test]
1985    fn cargo_libtest_runs_produce_queryable_owned_evidence() {
1986        let nonce = SystemTime::now()
1987            .duration_since(UNIX_EPOCH)
1988            .unwrap()
1989            .as_nanos();
1990        let root = std::env::temp_dir().join(format!(
1991            "supercov-rust-runner-{}-{nonce}",
1992            std::process::id()
1993        ));
1994        fs::create_dir_all(root.join("src")).unwrap();
1995        fs::write(
1996            root.join("Cargo.toml"),
1997            "[package]\nname='fixture'\nversion='0.0.0'\nedition='2024'\n",
1998        )
1999        .unwrap();
2000        fs::write(
2001            root.join("src/lib.rs"),
2002            r#"
2003pub fn choose(left: bool, right: bool) -> i32 {
2004    if left && right { 1 } else { 0 }
2005}
2006pub fn pick(value: i32) -> &'static str {
2007    match value {
2008        0 => "zero",
2009        1 => "one",
2010        _ => "many",
2011    }
2012}
2013pub fn total(values: &[i32]) -> i32 {
2014    let mut sum = 0;
2015    for value in values {
2016        sum += value;
2017    }
2018    sum
2019}
2020pub fn first_even(values: &[i32]) -> Option<i32> {
2021    let mut index = 0;
2022    while index < values.len() {
2023        if values[index] % 2 == 0 {
2024            return Some(values[index]);
2025        }
2026        index += 1;
2027    }
2028    None
2029}
2030pub fn parse_twice(text: &str) -> Option<i32> {
2031    let value: i32 = text.parse().ok()?;
2032    Some(value * 2)
2033}
2034pub fn describe(value: Option<i32>, flag: bool) -> &'static str {
2035    if let Some(inner) = value && inner > 0 && flag {
2036        "positive"
2037    } else {
2038        "other"
2039    }
2040}
2041pub fn depth(n: u32) -> Result<u32, String> {
2042    if n == 0 {
2043        Ok(0)
2044    } else {
2045        let below = depth(n - 1)?;
2046        Ok(below + 1)
2047    }
2048}
2049#[cfg(test)]
2050mod tests {
2051    #[test] fn false_path() { assert_eq!(super::choose(false, true), 0); }
2052    #[test] fn true_path() { assert_eq!(super::choose(true, true), 1); }
2053    #[test] #[ignore] fn ignored_path() { unreachable!(); }
2054    #[test] fn pick_zero() { assert_eq!(super::pick(0), "zero"); }
2055    #[test] fn pick_many() { assert_eq!(super::pick(7), "many"); }
2056    #[test] fn total_empty() { assert_eq!(super::total(&[]), 0); }
2057    #[test] fn total_some() { assert_eq!(super::total(&[1, 2]), 3); }
2058    #[test] fn first_even_empty() { assert_eq!(super::first_even(&[]), None); }
2059    #[test] fn first_even_found() { assert_eq!(super::first_even(&[1, 4]), Some(4)); }
2060    #[test] fn parse_ok() { assert_eq!(super::parse_twice("4"), Some(8)); }
2061    #[test] fn parse_bad() { assert_eq!(super::parse_twice("x"), None); }
2062    #[test] fn chain_taken() { assert_eq!(super::describe(Some(1), true), "positive"); }
2063    #[test] fn chain_pattern_fails() { assert_eq!(super::describe(None, true), "other"); }
2064    #[test] fn chain_negative() { assert_eq!(super::describe(Some(-1), true), "other"); }
2065    #[test] fn chain_flag_fails() { assert_eq!(super::describe(Some(1), false), "other"); }
2066    #[test] fn deep_recursion() { assert_eq!(super::depth(5_000), Ok(5_000)); }
2067}
2068"#,
2069        )
2070        .unwrap();
2071        let project = prepare_rust_project(&root).unwrap();
2072        // Doctests need the CLI binary standing in for rustdoc, which this
2073        // test binary cannot do; scripts/rust-public-cargo-integration.mjs
2074        // covers them end to end. `--lib` keeps this run to the libtests.
2075        let run = run_prepared_rust_tests(
2076            &project,
2077            &["cargo".into(), "test".into(), "--lib".into()],
2078            "rust-fixture",
2079            "2026-08-26T00:00:00.000Z",
2080            &mut Vec::new(),
2081        )
2082        .unwrap();
2083        assert_eq!(run.exit_code, 0);
2084        assert_eq!(run.request.raw_results.len(), 16);
2085        let statuses = run
2086            .request
2087            .raw_results
2088            .iter()
2089            .filter_map(|result| result.status.as_deref())
2090            .collect::<Vec<_>>();
2091        assert_eq!(
2092            statuses
2093                .iter()
2094                .filter(|status| **status == "skipped")
2095                .count(),
2096            1
2097        );
2098        assert_eq!(
2099            statuses
2100                .iter()
2101                .filter(|status| **status == "passed")
2102                .count(),
2103            15
2104        );
2105
2106        // The let chain's condition vectors, with the pattern's outcome
2107        // derived: [let Some(inner) = value, inner > 0, flag].
2108        let chain_vectors = |test: &str| {
2109            let result = run
2110                .request
2111                .raw_results
2112                .iter()
2113                .find(|result| result.test.ends_with(test))
2114                .unwrap_or_else(|| panic!("no test {test}"));
2115            let snapshot = result
2116                .runtime
2117                .iter()
2118                .flat_map(|snapshot| &snapshot.decisions)
2119                .find(|decision| decision.meta.source.starts_with("let Some(inner) = value"))
2120                .unwrap_or_else(|| panic!("{test} recorded no chain decision"));
2121            snapshot
2122                .vectors
2123                .iter()
2124                .map(|vector| (vector.values.clone(), vector.outcome))
2125                .collect::<Vec<_>>()
2126        };
2127        assert_eq!(
2128            chain_vectors("chain_taken"),
2129            [(vec![Some(true), Some(true), Some(true)], true)]
2130        );
2131        assert_eq!(
2132            chain_vectors("chain_pattern_fails"),
2133            [(vec![Some(false), None, None], false)]
2134        );
2135        assert_eq!(
2136            chain_vectors("chain_negative"),
2137            [(vec![Some(true), Some(false), None], false)]
2138        );
2139        assert_eq!(
2140            chain_vectors("chain_flag_fails"),
2141            [(vec![Some(true), Some(true), Some(false)], false)]
2142        );
2143        validate_frontend_report_request(&run.declaration, &run.request).unwrap();
2144        let archive = root.join("evidence.raw.gz");
2145        write_archive(run.archive_entries().unwrap(), &archive).unwrap();
2146        let report = analyze_coverage_archive(&ArchiveReportRequest {
2147            archive_path: archive,
2148            run_id: "rust-fixture".into(),
2149            generated_at: "2026-08-26T00:00:00.000Z".into(),
2150            integrity: None,
2151            test_exit_code: ExitCodeInput::Present(Some(0)),
2152        })
2153        .unwrap();
2154        assert_eq!(report.view.tests.len(), 16);
2155        assert!(report.view.summary.lines.covered > 0);
2156        assert!(report.view.summary.decisions > 0);
2157
2158        // Loops, the try operator and the logical operator, each with both
2159        // outcomes attributed to the test that produced it.
2160        let single = |kind: &str| {
2161            let mut found = report
2162                .view
2163                .branches
2164                .iter()
2165                .filter(|branch| branch.meta.kind == kind);
2166            let branch = found.next().unwrap_or_else(|| panic!("no {kind} branch"));
2167            assert!(found.next().is_none(), "more than one {kind} branch");
2168            branch
2169        };
2170        let tests_of = |branch: &crate::coverage_report::BranchResult, label: &str| {
2171            branch
2172                .alternatives
2173                .iter()
2174                .find(|alternative| alternative.label == label)
2175                .unwrap_or_else(|| panic!("{} has no alternative {label}", branch.meta.kind))
2176                .tests
2177                .clone()
2178        };
2179        let for_loop = single("for-loop");
2180        assert_eq!(
2181            tests_of(for_loop, "zero iterations"),
2182            ["src/lib.rs::tests::total_empty"]
2183        );
2184        assert_eq!(
2185            tests_of(for_loop, "entered"),
2186            ["src/lib.rs::tests::total_some"]
2187        );
2188        let while_loop = single("while-loop");
2189        assert_eq!(
2190            tests_of(while_loop, "zero iterations"),
2191            ["src/lib.rs::tests::first_even_empty"]
2192        );
2193        assert_eq!(
2194            tests_of(while_loop, "entered"),
2195            ["src/lib.rs::tests::first_even_found"]
2196        );
2197        // `depth` adds a second `?`; the one in `parse_twice` is the one
2198        // exercised both ways.
2199        let try_operator = report
2200            .view
2201            .branches
2202            .iter()
2203            .find(|branch| {
2204                branch.meta.kind == "try-operator" && branch.meta.source.contains("parse().ok()")
2205            })
2206            .expect("parse_twice's try operator");
2207        assert_eq!(
2208            tests_of(try_operator, "continued"),
2209            ["src/lib.rs::tests::parse_ok"]
2210        );
2211        assert_eq!(
2212            tests_of(try_operator, "early return"),
2213            ["src/lib.rs::tests::parse_bad"]
2214        );
2215        let mut logical = report
2216            .view
2217            .branches
2218            .iter()
2219            .filter(|branch| branch.meta.kind == "logical-and")
2220            .collect::<Vec<_>>();
2221        logical.sort_by_key(|branch| (branch.meta.line, branch.meta.column));
2222        // `left && right` in choose, then the chain's two operators.
2223        assert_eq!(logical.len(), 3);
2224        assert_eq!(
2225            tests_of(logical[0], "short-circuited"),
2226            ["src/lib.rs::tests::false_path"]
2227        );
2228        assert_eq!(
2229            tests_of(logical[0], "right operand evaluated"),
2230            ["src/lib.rs::tests::true_path"]
2231        );
2232        assert_eq!(
2233            tests_of(logical[1], "short-circuited"),
2234            ["src/lib.rs::tests::chain_pattern_fails"]
2235        );
2236        assert_eq!(
2237            tests_of(logical[1], "right operand evaluated"),
2238            [
2239                "src/lib.rs::tests::chain_flag_fails",
2240                "src/lib.rs::tests::chain_negative",
2241                "src/lib.rs::tests::chain_taken",
2242            ]
2243        );
2244        assert_eq!(
2245            tests_of(logical[2], "short-circuited"),
2246            [
2247                "src/lib.rs::tests::chain_negative",
2248                "src/lib.rs::tests::chain_pattern_fails",
2249            ]
2250        );
2251        assert_eq!(
2252            tests_of(logical[2], "right operand evaluated"),
2253            [
2254                "src/lib.rs::tests::chain_flag_fails",
2255                "src/lib.rs::tests::chain_taken",
2256            ]
2257        );
2258        assert!(for_loop.covered && while_loop.covered && try_operator.covered);
2259        assert!(logical.iter().all(|branch| branch.covered));
2260
2261        // The match in `pick`: pick(0) selects the first arm; pick(7) passes
2262        // the first two over and selects the last. Nothing selects `1`.
2263        let mut arms = report
2264            .view
2265            .branches
2266            .iter()
2267            .filter(|branch| branch.meta.kind == "match-arm")
2268            .collect::<Vec<_>>();
2269        arms.sort_by_key(|branch| branch.meta.line);
2270        assert_eq!(arms.len(), 3);
2271        let alternative = |arm: usize, label: &str| {
2272            arms[arm]
2273                .alternatives
2274                .iter()
2275                .find(|alternative| alternative.label == label)
2276                .unwrap_or_else(|| panic!("arm {arm} has no alternative {label}"))
2277        };
2278        assert_eq!(
2279            alternative(0, "selected").tests,
2280            ["src/lib.rs::tests::pick_zero"]
2281        );
2282        assert_eq!(
2283            alternative(0, "not selected").tests,
2284            ["src/lib.rs::tests::pick_many"]
2285        );
2286        assert!(!alternative(1, "selected").covered);
2287        assert_eq!(
2288            alternative(1, "not selected").tests,
2289            ["src/lib.rs::tests::pick_many"]
2290        );
2291        assert_eq!(arms[2].alternatives.len(), 1);
2292        assert_eq!(
2293            alternative(2, "selected").tests,
2294            ["src/lib.rs::tests::pick_many"]
2295        );
2296        assert!(arms[0].covered && !arms[1].covered && arms[2].covered);
2297
2298        fs::remove_dir_all(root).unwrap();
2299    }
2300}