integration_tests/
seed.rs1use crate::{env::set_env_vars_for_script, nextest_cli::CargoNextestCli};
5use camino::{Utf8Path, Utf8PathBuf};
6use color_eyre::eyre::{Context, eyre};
7use fs_err as fs;
8use sha2::{Digest, Sha256};
9use std::{collections::BTreeMap, path::PathBuf, process::Command, time::SystemTime};
10
11pub fn fixture_project_dir(workspace_root: &Utf8Path) -> Utf8PathBuf {
12 workspace_root.join("fixtures/fixture-project")
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18pub struct Sha256Hash([u8; 32]);
19
20impl std::fmt::Display for Sha256Hash {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 hex::encode(self.0).fmt(f)
23 }
24}
25
26pub fn compute_dir_hash(dir: impl AsRef<Utf8Path>) -> color_eyre::Result<Sha256Hash> {
70 let files = collect_all_files(dir.as_ref(), true)?;
71 let mut hasher = Sha256::new();
72
73 hasher.update(b"nextest:rustc-version-verbose\0");
76 hasher.update(&rustc_version_verbose()?);
77 hasher.update([0, 0]);
78
79 for (file_name, metadata) in files {
80 hasher.update(file_name.as_str());
81 hasher.update([0]);
82 let timestamp = metadata
84 .mtime
85 .duration_since(SystemTime::UNIX_EPOCH)
86 .expect("file's mtime after 1970-01-01");
87 hasher.update(timestamp.as_nanos().to_le_bytes());
88 hasher.update(metadata.hash.0);
89 hasher.update([0]);
90 }
91 Ok(Sha256Hash(hasher.finalize().into()))
92}
93
94fn rustc_version_verbose() -> color_eyre::Result<Vec<u8>> {
95 let rustc_path = match std::env::var_os("RUSTC") {
96 Some(path) => PathBuf::from(path),
97 None => PathBuf::from("rustc"),
98 };
99 let output = Command::new(&rustc_path)
100 .args(["--version", "--verbose"])
101 .output()
102 .wrap_err_with(|| {
103 format!(
104 "failed to spawn `{} --version --verbose`",
105 rustc_path.display()
106 )
107 })?;
108 if !output.status.success() {
109 return Err(eyre!(
110 "`{} --version --verbose` failed with {}\nstderr:\n{}",
111 rustc_path.display(),
112 output.status,
113 String::from_utf8_lossy(&output.stderr),
114 ));
115 }
116 Ok(output.stdout)
117}
118
119fn collect_all_files(
123 dir: &Utf8Path,
124 root: bool,
125) -> color_eyre::Result<BTreeMap<Utf8PathBuf, FileMetadata>> {
126 let mut stack = vec![dir.to_path_buf()];
127 let mut hashes = BTreeMap::new();
128
129 while let Some(dir) = stack.pop() {
131 for entry in dir.read_dir_utf8()? {
132 let entry =
133 entry.wrap_err_with(|| format!("failed to read entry from directory {dir}"))?;
134 let ty = entry
135 .file_type()
136 .wrap_err_with(|| format!("failed to get file type for entry {}", entry.path()))?;
137
138 if root && entry.path().file_name() == Some("target") {
140 continue;
141 }
142
143 if ty.is_dir() {
144 stack.push(entry.into_path());
145 } else if ty.is_file() {
146 let metadata = entry.metadata().wrap_err_with(|| {
147 format!("failed to get metadata for file {}", entry.path())
148 })?;
149
150 let mtime = metadata.modified().wrap_err_with(|| {
154 format!("failed to get modified time for file {}", entry.path())
155 })?;
156 let path = entry.into_path();
157 let contents = fs::read(&path)?;
158 let hash = Sha256Hash(Sha256::digest(&contents).into());
159 hashes.insert(path, FileMetadata { mtime, hash });
160 }
161 }
162 }
163
164 Ok(hashes)
165}
166
167#[derive(Clone, Debug)]
168struct FileMetadata {
169 mtime: SystemTime,
170 hash: Sha256Hash,
171}
172
173pub fn get_seed_archive_name(hash: Sha256Hash) -> Utf8PathBuf {
174 let temp_dir = Utf8PathBuf::try_from(std::env::temp_dir()).expect("temp dir is utf-8");
176 let username = whoami::username().expect("obtained username");
177 let user_dir = temp_dir.join(format!("fixture-project-seed-{username}"));
178 user_dir.join(format!("seed-{hash}.tar.zst"))
179}
180
181pub fn make_seed_archive(workspace_dir: &Utf8Path, file_name: &Utf8Path) -> color_eyre::Result<()> {
182 fs::create_dir_all(file_name.parent().unwrap())?;
184
185 let temp_dir = camino_tempfile::Builder::new()
187 .prefix("nextest-seed-build-")
188 .tempdir()
189 .wrap_err("failed to create temporary directory")?;
190 let target_dir = temp_dir.path().join("target");
191 fs::create_dir_all(&target_dir)?;
192
193 let mut cli = CargoNextestCli::for_script()?;
195
196 set_env_vars_for_script();
202
203 let output = cli
204 .args([
205 "--manifest-path",
206 workspace_dir.join("Cargo.toml").as_str(),
207 "archive",
208 "--archive-file",
209 file_name.as_str(),
210 "--workspace",
211 "--all-targets",
212 "--target-dir",
213 target_dir.as_str(),
214 "--profile",
216 "archive-all",
217 ])
218 .output();
219
220 if std::env::var("INTEGRATION_TESTS_DEBUG").as_deref() == Ok("1") {
221 eprintln!("make_seed_archive output: {output}");
222 }
223
224 Ok(())
225}