1use super::{
13 format::{
14 BUILD_SCOPE_JSON_PATH, CARGO_METADATA_JSON_PATH, OutputDict, RECORD_OPTS_JSON_PATH,
15 RUN_LOG_FILE_NAME, STDERR_DICT_PATH, STDOUT_DICT_PATH, STORE_ZIP_FILE_NAME,
16 TEST_LIST_JSON_PATH,
17 },
18 summary::{RecordOpts, TestEventSummary},
19};
20use crate::{
21 errors::RecordReadError,
22 output_spec::RecordingSpec,
23 record::format::{RERUN_INFO_JSON_PATH, RecordedBuildScope, RerunInfo},
24 user_config::elements::MAX_MAX_OUTPUT_SIZE,
25};
26use camino::{Utf8Path, Utf8PathBuf};
27use debug_ignore::DebugIgnore;
28use eazip::Archive;
29use nextest_metadata::TestListSummary;
30use std::{
31 fs::File,
32 io::{self, BufRead, BufReader, Read},
33};
34
35pub trait StoreReader {
41 fn read_cargo_metadata(&mut self) -> Result<String, RecordReadError>;
43
44 fn read_test_list(&mut self) -> Result<TestListSummary, RecordReadError>;
46
47 fn read_record_opts(&mut self) -> Result<RecordOpts, RecordReadError>;
49
50 fn read_build_scope(&mut self) -> Result<RecordedBuildScope, RecordReadError>;
52
53 fn read_rerun_info(&mut self) -> Result<Option<RerunInfo>, RecordReadError>;
57
58 fn load_dictionaries(&mut self) -> Result<(), RecordReadError>;
62
63 fn read_output(&mut self, file_name: &str) -> Result<Vec<u8>, RecordReadError>;
72
73 fn extract_file_to_path(
78 &mut self,
79 store_path: &str,
80 output_path: &Utf8Path,
81 ) -> Result<u64, RecordReadError>;
82}
83
84#[derive(Debug)]
89pub struct RecordReader {
90 run_dir: Utf8PathBuf,
91 archive: Option<DebugIgnore<Archive<BufReader<File>>>>,
92 stdout_dict: Option<Vec<u8>>,
94 stderr_dict: Option<Vec<u8>>,
96}
97
98impl RecordReader {
99 pub fn open(run_dir: &Utf8Path) -> Result<Self, RecordReadError> {
103 if !run_dir.exists() {
104 return Err(RecordReadError::RunNotFound {
105 path: run_dir.to_owned(),
106 });
107 }
108
109 Ok(Self {
110 run_dir: run_dir.to_owned(),
111 archive: None,
112 stdout_dict: None,
113 stderr_dict: None,
114 })
115 }
116
117 pub fn run_dir(&self) -> &Utf8Path {
119 &self.run_dir
120 }
121
122 fn ensure_archive(&mut self) -> Result<&mut Archive<BufReader<File>>, RecordReadError> {
124 if self.archive.is_none() {
125 let store_path = self.run_dir.join(STORE_ZIP_FILE_NAME);
126 let file = File::open(&store_path).map_err(|error| RecordReadError::OpenArchive {
127 path: store_path.clone(),
128 error,
129 })?;
130 let archive = Archive::new(BufReader::new(file)).map_err(|error| {
131 RecordReadError::ParseArchive {
132 path: store_path,
133 error,
134 }
135 })?;
136 self.archive = Some(DebugIgnore(archive));
137 }
138 Ok(self.archive.as_mut().expect("archive was just set"))
139 }
140
141 fn read_archive_file(&mut self, file_name: &str) -> Result<Vec<u8>, RecordReadError> {
151 let limit = MAX_MAX_OUTPUT_SIZE.as_u64();
152 let archive = self.ensure_archive()?;
153 let mut file =
154 archive
155 .get_by_name(file_name)
156 .ok_or_else(|| RecordReadError::FileNotFound {
157 file_name: file_name.to_string(),
158 })?;
159
160 let claimed_size = file.metadata().uncompressed_size;
161 if claimed_size > limit {
162 return Err(RecordReadError::FileTooLarge {
163 file_name: file_name.to_string(),
164 size: claimed_size,
165 limit,
166 });
167 }
168
169 let capacity = usize::try_from(claimed_size).unwrap_or(usize::MAX);
170 let mut contents = Vec::with_capacity(capacity);
171
172 file.read()
175 .and_then(|reader| reader.take(limit).read_to_end(&mut contents))
176 .map_err(|error| RecordReadError::Decompress {
177 file_name: file_name.to_string(),
178 error,
179 })?;
180
181 let actual_size = contents.len() as u64;
182 if actual_size != claimed_size {
183 return Err(RecordReadError::SizeMismatch {
184 file_name: file_name.to_string(),
185 claimed_size,
186 actual_size,
187 });
188 }
189
190 Ok(contents)
191 }
192
193 pub fn read_cargo_metadata(&mut self) -> Result<String, RecordReadError> {
195 let bytes = self.read_archive_file(CARGO_METADATA_JSON_PATH)?;
196 String::from_utf8(bytes).map_err(|e| RecordReadError::Decompress {
197 file_name: CARGO_METADATA_JSON_PATH.to_string(),
198 error: std::io::Error::new(std::io::ErrorKind::InvalidData, e),
199 })
200 }
201
202 pub fn read_test_list(&mut self) -> Result<TestListSummary, RecordReadError> {
204 let bytes = self.read_archive_file(TEST_LIST_JSON_PATH)?;
205 serde_json::from_slice(&bytes).map_err(|error| RecordReadError::DeserializeMetadata {
206 file_name: TEST_LIST_JSON_PATH.to_string(),
207 error,
208 })
209 }
210
211 pub fn read_record_opts(&mut self) -> Result<RecordOpts, RecordReadError> {
213 let bytes = self.read_archive_file(RECORD_OPTS_JSON_PATH)?;
214 serde_json::from_slice(&bytes).map_err(|error| RecordReadError::DeserializeMetadata {
215 file_name: RECORD_OPTS_JSON_PATH.to_string(),
216 error,
217 })
218 }
219
220 pub fn read_build_scope(&mut self) -> Result<RecordedBuildScope, RecordReadError> {
222 let bytes = self.read_archive_file(BUILD_SCOPE_JSON_PATH)?;
223 serde_json::from_slice(&bytes).map_err(|error| RecordReadError::DeserializeMetadata {
224 file_name: BUILD_SCOPE_JSON_PATH.to_string(),
225 error,
226 })
227 }
228
229 pub fn read_rerun_info(&mut self) -> Result<Option<RerunInfo>, RecordReadError> {
234 match self.read_archive_file(RERUN_INFO_JSON_PATH) {
235 Ok(bytes) => {
236 let info = serde_json::from_slice(&bytes).map_err(|error| {
237 RecordReadError::DeserializeMetadata {
238 file_name: RERUN_INFO_JSON_PATH.to_string(),
239 error,
240 }
241 })?;
242 Ok(Some(info))
243 }
244 Err(RecordReadError::FileNotFound { .. }) => {
245 Ok(None)
247 }
248 Err(e) => Err(e),
249 }
250 }
251
252 pub fn load_dictionaries(&mut self) -> Result<(), RecordReadError> {
261 self.stdout_dict = Some(self.read_archive_file(STDOUT_DICT_PATH)?);
262 self.stderr_dict = Some(self.read_archive_file(STDERR_DICT_PATH)?);
263 Ok(())
264 }
265
266 pub fn events(&self) -> Result<RecordEventIter, RecordReadError> {
270 let log_path = self.run_dir.join(RUN_LOG_FILE_NAME);
271 let file = File::open(&log_path).map_err(|error| RecordReadError::OpenRunLog {
272 path: log_path.clone(),
273 error,
274 })?;
275 let decoder =
276 zstd::stream::Decoder::new(file).map_err(|error| RecordReadError::OpenRunLog {
277 path: log_path,
278 error,
279 })?;
280 Ok(RecordEventIter {
281 reader: DebugIgnore(BufReader::new(decoder)),
282 line_buf: String::new(),
283 line_number: 0,
284 })
285 }
286
287 pub fn read_output(&mut self, file_name: &str) -> Result<Vec<u8>, RecordReadError> {
299 let path = format!("out/{file_name}");
300 let compressed = self.read_archive_file(&path)?;
301 let limit = MAX_MAX_OUTPUT_SIZE.as_u64();
302
303 let dict_bytes = self.get_dict_for_output(file_name).ok_or_else(|| {
307 RecordReadError::UnknownOutputType {
308 file_name: file_name.to_owned(),
309 }
310 })?;
311
312 decompress_with_dict(&compressed, dict_bytes, limit).map_err(|error| {
313 RecordReadError::Decompress {
314 file_name: path,
315 error,
316 }
317 })
318 }
319
320 fn get_dict_for_output(&self, file_name: &str) -> Option<&[u8]> {
329 match OutputDict::for_output_file_name(file_name) {
330 OutputDict::Stdout => Some(
331 self.stdout_dict
332 .as_ref()
333 .expect("load_dictionaries must be called first"),
334 ),
335 OutputDict::Stderr => Some(
336 self.stderr_dict
337 .as_ref()
338 .expect("load_dictionaries must be called first"),
339 ),
340 OutputDict::None => None,
341 }
342 }
343}
344
345impl StoreReader for RecordReader {
346 fn read_cargo_metadata(&mut self) -> Result<String, RecordReadError> {
347 RecordReader::read_cargo_metadata(self)
348 }
349
350 fn read_test_list(&mut self) -> Result<TestListSummary, RecordReadError> {
351 RecordReader::read_test_list(self)
352 }
353
354 fn read_record_opts(&mut self) -> Result<RecordOpts, RecordReadError> {
355 RecordReader::read_record_opts(self)
356 }
357
358 fn read_build_scope(&mut self) -> Result<RecordedBuildScope, RecordReadError> {
359 RecordReader::read_build_scope(self)
360 }
361
362 fn read_rerun_info(&mut self) -> Result<Option<RerunInfo>, RecordReadError> {
363 RecordReader::read_rerun_info(self)
364 }
365
366 fn load_dictionaries(&mut self) -> Result<(), RecordReadError> {
367 RecordReader::load_dictionaries(self)
368 }
369
370 fn read_output(&mut self, file_name: &str) -> Result<Vec<u8>, RecordReadError> {
371 RecordReader::read_output(self, file_name)
372 }
373
374 fn extract_file_to_path(
375 &mut self,
376 store_path: &str,
377 output_path: &Utf8Path,
378 ) -> Result<u64, RecordReadError> {
379 let archive = self.ensure_archive()?;
380 let mut file =
381 archive
382 .get_by_name(store_path)
383 .ok_or_else(|| RecordReadError::FileNotFound {
384 file_name: store_path.to_owned(),
385 })?;
386
387 let mut output_file =
388 File::create(output_path).map_err(|error| RecordReadError::ExtractFile {
389 store_path: store_path.to_owned(),
390 output_path: output_path.to_owned(),
391 error,
392 })?;
393
394 let mut reader = file
396 .read()
397 .map_err(|error| RecordReadError::ReadArchiveFile {
398 file_name: store_path.to_owned(),
399 error,
400 })?;
401
402 io::copy(&mut reader, &mut output_file).map_err(|error| RecordReadError::ExtractFile {
403 store_path: store_path.to_owned(),
404 output_path: output_path.to_owned(),
405 error,
406 })
407 }
408}
409
410pub(super) fn decompress_with_dict(
415 compressed: &[u8],
416 dict_bytes: &[u8],
417 limit: u64,
418) -> std::io::Result<Vec<u8>> {
419 let dict = zstd::dict::DecoderDictionary::copy(dict_bytes);
420 let decoder = zstd::stream::Decoder::with_prepared_dictionary(compressed, &dict)?;
421 let mut decompressed = Vec::new();
422 decoder.take(limit).read_to_end(&mut decompressed)?;
423 Ok(decompressed)
424}
425
426type LogDecoder = zstd::stream::Decoder<'static, BufReader<File>>;
428
429#[derive(Debug)]
433pub struct RecordEventIter {
434 reader: DebugIgnore<BufReader<LogDecoder>>,
435 line_buf: String,
436 line_number: usize,
437}
438
439impl Iterator for RecordEventIter {
440 type Item = Result<TestEventSummary<RecordingSpec>, RecordReadError>;
441
442 fn next(&mut self) -> Option<Self::Item> {
443 loop {
444 self.line_buf.clear();
445 self.line_number += 1;
446
447 match self.reader.read_line(&mut self.line_buf) {
448 Ok(0) => return None,
449 Ok(_) => {
450 let trimmed = self.line_buf.trim();
451 if trimmed.is_empty() {
452 continue;
453 }
454 return Some(serde_json::from_str(trimmed).map_err(|error| {
455 RecordReadError::ParseEvent {
456 line_number: self.line_number,
457 error,
458 }
459 }));
460 }
461 Err(error) => {
462 return Some(Err(RecordReadError::ReadRunLog {
463 line_number: self.line_number,
464 error,
465 }));
466 }
467 }
468 }
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn test_record_reader_nonexistent_dir() {
478 let result = RecordReader::open(Utf8Path::new("/nonexistent/path"));
479 assert!(matches!(result, Err(RecordReadError::RunNotFound { .. })));
480 }
481}