supercov_engine/
source_capture.rs1use crate::{
5 evidence_archive::EvidenceArchiveEntry,
6 source_manifest::{FileFingerprint, Files, InputManifest, Inputs, local_path},
7 workspace::{canonicalize_simplified, simplified},
8};
9use std::{
10 collections::BTreeSet,
11 fs,
12 path::{Path, PathBuf},
13};
14
15pub const ARCHIVE_PATH: &str = "source-inputs.json";
17
18pub fn capture(
20 root: &Path,
21 language: &str,
22 paths: impl IntoIterator<Item = PathBuf>,
23) -> Result<Inputs, String> {
24 let supplied_root = simplified(root.to_owned());
25 let root = canonicalize_simplified(root).map_err(|e| e.to_string())?;
26 let mut inputs = Inputs {
27 language: language.into(),
28 files: Files::new(),
29 };
30 for path in paths.into_iter().map(simplified).collect::<BTreeSet<_>>() {
31 let full = if path.is_absolute() {
32 root.join(path.strip_prefix(&supplied_root).unwrap_or(&path))
33 } else {
34 root.join(&path)
35 };
36 if !full.exists() {
37 continue;
38 }
39 let relative = full
40 .strip_prefix(&root)
41 .map_err(|_| format!("source input outside project: {}", full.display()))?
42 .to_string_lossy()
43 .replace('\\', "/");
44 if !local_path(&relative)
45 || !canonicalize_simplified(&full)
46 .map_err(|e| e.to_string())?
47 .starts_with(&root)
48 {
49 return Err(format!("source input outside project: {relative}"));
50 }
51 if inputs.files.contains_key(&relative) {
52 continue;
53 }
54 let bytes = fs::read(&full).map_err(|e| format!("{relative}: {e}"))?;
55 let Ok(text) = String::from_utf8(bytes) else {
58 continue;
59 };
60 inputs.files.insert(relative, text);
61 }
62 Ok(inputs)
63}
64
65pub fn append(
66 mut entries: Vec<EvidenceArchiveEntry>,
67 inputs: &Inputs,
68) -> Result<Vec<EvidenceArchiveEntry>, String> {
69 if entries.iter().any(|e| e.path == ARCHIVE_PATH) {
70 return Err("duplicate source inputs".into());
71 }
72 entries.push(EvidenceArchiveEntry {
73 path: ARCHIVE_PATH.into(),
74 contents: serde_json::to_vec(&inputs.manifest()).map_err(|e| e.to_string())?,
75 });
76 Ok(entries)
77}
78
79pub fn current_sources(root: &Path, manifest: &InputManifest) -> Result<Inputs, String> {
81 let root = canonicalize_simplified(root).map_err(|e| e.to_string())?;
82 let mut files = Files::new();
83 for (file, expected) in &manifest.files {
84 if !local_path(file) {
85 return Err(format!("Invalid source path: {file}"));
86 }
87 let path = root.join(file);
88 let source = (|| {
89 let canonical = canonicalize_simplified(&path).ok()?;
90 if !canonical.starts_with(&root) || !canonical.is_file() {
91 return None;
92 }
93 let text = fs::read_to_string(canonical).ok()?;
94 FileFingerprint::of(&text)
95 .same_bytes(expected)
96 .then_some(text)
97 })();
98 let Some(source) = source else {
99 return Err(format!(
100 "Current source differs from the run or is unavailable: {file}; rerun tests for the current checkout"
101 ));
102 };
103 files.insert(file.clone(), source);
104 }
105 Ok(manifest.with_sources(files))
106}