1use super::{ArchiveCounts, ArchiveEvent, BINARIES_METADATA_FILE_NAME, CARGO_METADATA_FILE_NAME};
5use crate::{
6 config::{
7 core::{EvaluatableProfile, get_num_cpus},
8 elements::{ArchiveConfig, ArchiveIncludeOnMissing, RecursionDepth},
9 },
10 errors::{ArchiveCreateError, FromMessagesError, UnknownArchiveFormat, WriteTestListError},
11 helpers::{convert_rel_path_to_forward_slash, rel_path_join},
12 list::{BinaryList, RustBuildMeta, RustTestArtifact},
13 redact::Redactor,
14 reuse_build::{ArchiveFilterCounts, LIBDIRS_BASE_DIR, PathMapper},
15 test_filter::{BinaryFilter, FilterBinaryMatch, FilterBound},
16};
17use atomicwrites::{AtomicFile, OverwriteBehavior};
18use camino::{Utf8Path, Utf8PathBuf};
19use core::fmt;
20use guppy::{PackageId, graph::PackageGraph};
21use nextest_filtering::EvalContext;
22use std::{
23 collections::{BTreeSet, HashSet},
24 fs,
25 io::{self, BufWriter, Write},
26 sync::Arc,
27 time::{Instant, SystemTime},
28};
29use tracing::{debug, trace};
30use zstd::Encoder;
31
32pub fn apply_archive_filters(
34 graph: &PackageGraph,
35 binary_list: Arc<BinaryList>,
36 filter: &BinaryFilter,
37 ecx: &EvalContext<'_>,
38 path_mapper: &PathMapper,
39) -> Result<(BinaryList, ArchiveFilterCounts), FromMessagesError> {
40 let rust_build_meta = binary_list.rust_build_meta.map_paths(path_mapper);
41 let test_artifacts = RustTestArtifact::from_binary_list(
42 graph,
43 binary_list.clone(),
44 &rust_build_meta,
45 path_mapper,
46 None,
47 )?;
48
49 let test_artifacts: BTreeSet<_> = test_artifacts
51 .iter()
52 .filter(|test_artifact| {
53 let query = test_artifact.to_binary_query();
57 let filter_match = filter.check_match(&query, ecx, FilterBound::All);
58
59 debug_assert!(
60 !matches!(filter_match, FilterBinaryMatch::Possible),
61 "build_filtersets should have errored out on test filters, \
62 Possible should never be returned"
63 );
64 matches!(filter_match, FilterBinaryMatch::Definite)
65 })
66 .map(|test_artifact| &test_artifact.binary_id)
67 .collect();
68
69 let filtered_binaries: Vec<_> = binary_list
70 .rust_binaries
71 .iter()
72 .filter(|binary| test_artifacts.contains(&binary.id))
73 .cloned()
74 .collect();
75
76 let relevant_package_ids: HashSet<&str> = filtered_binaries
79 .iter()
80 .map(|binary| binary.package_id.as_str())
81 .collect();
82 let partitioned_non_test_binaries = binary_list
83 .rust_build_meta
84 .non_test_binaries
85 .partition_for_archive(&relevant_package_ids);
86
87 let mut filtered_build_script_out_dirs =
89 binary_list.rust_build_meta.build_script_out_dirs.clone();
90 filtered_build_script_out_dirs
91 .retain(|package_id, _| relevant_package_ids.contains(package_id.as_str()));
92 let filtered_build_script_info =
93 binary_list
94 .rust_build_meta
95 .build_script_info
96 .as_ref()
97 .map(|info| {
98 info.iter()
99 .filter(|(package_id, _)| relevant_package_ids.contains(package_id.as_str()))
100 .map(|(k, v)| (k.clone(), v.clone()))
101 .collect()
102 });
103
104 let filtered_out_test_binary_count = binary_list
105 .rust_binaries
106 .len()
107 .saturating_sub(filtered_binaries.len());
108 let filtered_out_non_test_binary_count =
109 partitioned_non_test_binaries.filtered_out_binary_count;
110 let filtered_out_build_script_out_dir_count = binary_list
111 .rust_build_meta
112 .build_script_out_dirs
113 .len()
114 .saturating_sub(filtered_build_script_out_dirs.len());
115
116 let filtered_build_meta = RustBuildMeta {
117 non_test_binaries: partitioned_non_test_binaries.retained,
118 build_script_out_dirs: filtered_build_script_out_dirs,
119 build_script_info: filtered_build_script_info,
120 ..binary_list.rust_build_meta.clone()
121 };
122
123 Ok((
124 BinaryList {
125 rust_build_meta: filtered_build_meta,
126 rust_binaries: filtered_binaries,
127 },
128 ArchiveFilterCounts {
129 filtered_out_test_binary_count,
130 filtered_out_non_test_binary_count,
131 filtered_out_build_script_out_dir_count,
132 },
133 ))
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
138#[non_exhaustive]
139pub enum ArchiveFormat {
140 TarZst,
142}
143
144impl ArchiveFormat {
145 pub const SUPPORTED_FORMATS: &'static [(&'static str, Self)] = &[(".tar.zst", Self::TarZst)];
147
148 pub fn autodetect(archive_file: &Utf8Path) -> Result<Self, UnknownArchiveFormat> {
151 let file_name = archive_file.file_name().unwrap_or("");
152 for (extension, format) in Self::SUPPORTED_FORMATS {
153 if file_name.ends_with(extension) {
154 return Ok(*format);
155 }
156 }
157
158 Err(UnknownArchiveFormat {
159 file_name: file_name.to_owned(),
160 })
161 }
162}
163
164#[expect(clippy::too_many_arguments)]
168pub fn archive_to_file<'a, F>(
169 profile: EvaluatableProfile<'a>,
170 binary_list: &'a BinaryList,
171 filter_counts: ArchiveFilterCounts,
172 cargo_metadata: &'a str,
173 graph: &'a PackageGraph,
174 path_mapper: &'a PathMapper,
175 format: ArchiveFormat,
176 zstd_level: i32,
177 zstd_long: bool,
178 output_file: &'a Utf8Path,
179 mut callback: F,
180 redactor: Redactor,
181) -> Result<(), ArchiveCreateError>
182where
183 F: for<'b> FnMut(ArchiveEvent<'b>) -> io::Result<()>,
184{
185 let config = profile.archive_config();
186
187 let start_time = Instant::now();
188
189 let file = AtomicFile::new(output_file, OverwriteBehavior::AllowOverwrite);
190 let file_count = file
191 .write(|file| {
192 let (host_stdlib, host_stdlib_err) = if let Some(libdir) = binary_list
199 .rust_build_meta
200 .build_platforms
201 .host
202 .libdir
203 .as_path()
204 {
205 split_result(find_std(libdir))
206 } else {
207 (None, None)
208 };
209
210 let (target_stdlib, target_stdlib_err) =
211 if let Some(target) = &binary_list.rust_build_meta.build_platforms.target {
212 if let Some(libdir) = target.libdir.as_path() {
213 split_result(find_std(libdir))
214 } else {
215 (None, None)
216 }
217 } else {
218 (None, None)
219 };
220
221 let stdlib_count = host_stdlib.is_some() as usize + target_stdlib.is_some() as usize;
222
223 let archiver = Archiver::new(
224 config,
225 binary_list,
226 cargo_metadata,
227 graph,
228 path_mapper,
229 host_stdlib,
230 target_stdlib,
231 format,
232 zstd_level,
233 zstd_long,
234 file,
235 redactor,
236 )?;
237
238 let test_binary_count = binary_list.rust_binaries.len();
239 let non_test_binary_count =
240 binary_list.rust_build_meta.non_test_binaries.binary_count();
241 let build_script_out_dir_count =
242 binary_list.rust_build_meta.build_script_out_dirs.len();
243 let linked_path_count = binary_list.rust_build_meta.linked_paths.len();
244 let extra_path_count = config.include.len();
245
246 let counts = ArchiveCounts {
247 test_binary_count,
248 filter_counts,
249 non_test_binary_count,
250 build_script_out_dir_count,
251 linked_path_count,
252 extra_path_count,
253 stdlib_count,
254 };
255
256 callback(ArchiveEvent::ArchiveStarted {
257 counts,
258 output_file,
259 })
260 .map_err(ArchiveCreateError::ReporterIo)?;
261
262 if let Some(err) = host_stdlib_err {
264 callback(ArchiveEvent::StdlibPathError {
265 error: &err.to_string(),
266 })
267 .map_err(ArchiveCreateError::ReporterIo)?;
268 }
269 if let Some(err) = target_stdlib_err {
270 callback(ArchiveEvent::StdlibPathError {
271 error: &err.to_string(),
272 })
273 .map_err(ArchiveCreateError::ReporterIo)?;
274 }
275
276 let (_, file_count) = archiver.archive(&mut callback)?;
277 Ok(file_count)
278 })
279 .map_err(|err| match err {
280 atomicwrites::Error::Internal(err) => ArchiveCreateError::OutputArchiveIo(err),
281 atomicwrites::Error::User(err) => err,
282 })?;
283
284 let elapsed = start_time.elapsed();
285
286 callback(ArchiveEvent::Archived {
287 file_count,
288 output_file,
289 elapsed,
290 })
291 .map_err(ArchiveCreateError::ReporterIo)?;
292
293 Ok(())
294}
295
296struct Archiver<'a, W: Write> {
297 binary_list: &'a BinaryList,
298 cargo_metadata: &'a str,
299 graph: &'a PackageGraph,
300 path_mapper: &'a PathMapper,
301 host_stdlib: Option<Utf8PathBuf>,
302 target_stdlib: Option<Utf8PathBuf>,
303 builder: tar::Builder<Encoder<'static, BufWriter<W>>>,
304 unix_timestamp: u64,
305 added_files: HashSet<Utf8PathBuf>,
306 config: &'a ArchiveConfig,
307 redactor: Redactor,
308}
309
310impl<'a, W: Write> Archiver<'a, W> {
311 #[expect(clippy::too_many_arguments)]
312 fn new(
313 config: &'a ArchiveConfig,
314 binary_list: &'a BinaryList,
315 cargo_metadata: &'a str,
316 graph: &'a PackageGraph,
317 path_mapper: &'a PathMapper,
318 host_stdlib: Option<Utf8PathBuf>,
319 target_stdlib: Option<Utf8PathBuf>,
320 format: ArchiveFormat,
321 compression_level: i32,
322 zstd_long: bool,
323 writer: W,
324 redactor: Redactor,
325 ) -> Result<Self, ArchiveCreateError> {
326 let buf_writer = BufWriter::new(writer);
327 let builder = match format {
328 ArchiveFormat::TarZst => {
329 let mut encoder = zstd::Encoder::new(buf_writer, compression_level)
330 .map_err(ArchiveCreateError::OutputArchiveIo)?;
331 encoder
332 .include_checksum(true)
333 .map_err(ArchiveCreateError::OutputArchiveIo)?;
334 if zstd_long {
335 encoder
340 .long_distance_matching(true)
341 .map_err(ArchiveCreateError::OutputArchiveIo)?;
342 }
343 if let Err(err) = encoder.multithread(get_num_cpus() as u32) {
344 tracing::warn!(
345 ?err,
346 "libzstd compiled without multithreading, defaulting to single-thread"
347 );
348 }
349 let mut builder = tar::Builder::new(encoder);
350 builder.follow_symlinks(true);
352 builder
353 }
354 };
355
356 let unix_timestamp = SystemTime::now()
357 .duration_since(SystemTime::UNIX_EPOCH)
358 .expect("current time should be after 1970-01-01")
359 .as_secs();
360
361 Ok(Self {
362 binary_list,
363 cargo_metadata,
364 graph,
365 path_mapper,
366 host_stdlib,
367 target_stdlib,
368 builder,
369 unix_timestamp,
370 added_files: HashSet::new(),
371 config,
372 redactor,
373 })
374 }
375
376 fn archive<F>(mut self, callback: &mut F) -> Result<(W, usize), ArchiveCreateError>
377 where
378 F: for<'b> FnMut(ArchiveEvent<'b>) -> io::Result<()>,
379 {
380 let archive_summary = self.binary_list.to_archive_summary();
384 let binaries_metadata = serde_json::to_string_pretty(&archive_summary)
385 .map_err(|e| ArchiveCreateError::CreateBinaryList(WriteTestListError::Json(e)))?;
386
387 self.append_from_memory(BINARIES_METADATA_FILE_NAME, &binaries_metadata)?;
388
389 self.append_from_memory(CARGO_METADATA_FILE_NAME, self.cargo_metadata)?;
390
391 let target_dir = &self.binary_list.rust_build_meta.target_directory;
392 let build_directory = &self.binary_list.rust_build_meta.build_directory;
393
394 fn filter_map_err<T>(result: io::Result<()>) -> Option<Result<T, ArchiveCreateError>> {
395 match result {
396 Ok(()) => None,
397 Err(err) => Some(Err(ArchiveCreateError::ReporterIo(err))),
398 }
399 }
400
401 let archive_include_paths = self
403 .config
404 .include
405 .iter()
406 .filter_map(|include| {
407 let src_path = include.join_path(target_dir);
408 let src_path = self.path_mapper.map_target_path(src_path);
410
411 match src_path.symlink_metadata() {
412 Ok(metadata) => {
413 if metadata.is_dir() {
414 if include.depth().is_zero() {
415 filter_map_err(callback(ArchiveEvent::DirectoryAtDepthZero {
417 path: &src_path,
418 }))
419 } else {
420 Some(Ok((include, src_path)))
421 }
422 } else if metadata.is_file() || metadata.is_symlink() {
423 Some(Ok((include, src_path)))
424 } else {
425 filter_map_err(callback(ArchiveEvent::UnknownFileType {
426 step: ArchiveStep::ExtraPaths,
427 path: &src_path,
428 }))
429 }
430 }
431 Err(error) => {
432 if error.kind() == io::ErrorKind::NotFound {
433 match include.on_missing() {
434 ArchiveIncludeOnMissing::Error => {
435 Some(Err(ArchiveCreateError::MissingExtraPath {
437 path: src_path.to_owned(),
438 redactor: self.redactor.clone(),
439 }))
440 }
441 ArchiveIncludeOnMissing::Warn => {
442 filter_map_err(callback(ArchiveEvent::ExtraPathMissing {
443 path: &src_path,
444 warn: true,
445 }))
446 }
447 ArchiveIncludeOnMissing::Ignore => {
448 filter_map_err(callback(ArchiveEvent::ExtraPathMissing {
449 path: &src_path,
450 warn: false,
451 }))
452 }
453 }
454 } else {
455 Some(Err(ArchiveCreateError::InputFileRead {
456 step: ArchiveStep::ExtraPaths,
457 path: src_path.to_owned(),
458 is_dir: None,
459 error,
460 }))
461 }
462 }
463 }
464 })
465 .collect::<Result<Vec<_>, ArchiveCreateError>>()?;
466
467 for binary in &self.binary_list.rust_binaries {
470 let rel_path = binary
471 .path
472 .strip_prefix(build_directory)
473 .expect("test binary paths must be within the build directory");
474 let rel_path = Utf8Path::new("target").join(rel_path);
476 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
477
478 self.append_file(ArchiveStep::TestBinaries, &binary.path, &rel_path)?;
479 }
480 for non_test_binary in self.binary_list.rust_build_meta.non_test_binaries.files() {
481 let src_path = self
482 .binary_list
483 .rust_build_meta
484 .target_directory
485 .join(&non_test_binary.path);
486 let src_path = self.path_mapper.map_target_path(src_path);
488
489 let rel_path = Utf8Path::new("target").join(&non_test_binary.path);
490 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
491
492 self.append_file(ArchiveStep::NonTestBinaries, &src_path, &rel_path)?;
493 }
494
495 for build_script_out_dir in self
498 .binary_list
499 .rust_build_meta
500 .build_script_out_dirs
501 .values()
502 {
503 let src_path = build_directory.join(build_script_out_dir);
504 let src_path = self.path_mapper.map_build_path(src_path);
505
506 let rel_path = Utf8Path::new("target").join(build_script_out_dir);
507 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
508
509 self.append_path_recursive(
513 ArchiveStep::BuildScriptOutDirs,
514 &src_path,
515 &rel_path,
516 RecursionDepth::Finite(1),
517 false,
518 callback,
519 )?;
520
521 }
525
526 for (linked_path, requested_by) in &self.binary_list.rust_build_meta.linked_paths {
528 let src_path = build_directory.join(linked_path);
531 let src_path = self.path_mapper.map_build_path(src_path);
532
533 if !src_path.exists() {
535 let mut requested_by: Vec<_> = requested_by
537 .iter()
538 .map(|package_id| {
539 self.graph
540 .metadata(&PackageId::new(package_id.clone()))
541 .map_or_else(
542 |_| {
543 package_id.to_owned()
546 },
547 |metadata| format!("{} v{}", metadata.name(), metadata.version()),
548 )
549 })
550 .collect();
551 requested_by.sort_unstable();
552
553 callback(ArchiveEvent::LinkedPathNotFound {
554 path: &src_path,
555 requested_by: &requested_by,
556 })
557 .map_err(ArchiveCreateError::ReporterIo)?;
558 continue;
559 }
560
561 let rel_path = Utf8Path::new("target").join(linked_path);
562 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
563 self.append_path_recursive(
566 ArchiveStep::LinkedPaths,
567 &src_path,
568 &rel_path,
569 RecursionDepth::Finite(1),
570 false,
571 callback,
572 )?;
573 }
574
575 for (include, src_path) in archive_include_paths {
577 let rel_path = include.join_path(Utf8Path::new("target"));
578 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
579
580 if src_path.exists() {
581 self.append_path_recursive(
582 ArchiveStep::ExtraPaths,
583 &src_path,
584 &rel_path,
585 include.depth(),
586 true,
588 callback,
589 )?;
590 }
591 }
592
593 if let Some(host_stdlib) = self.host_stdlib.clone() {
595 let rel_path = Utf8Path::new(LIBDIRS_BASE_DIR)
596 .join("host")
597 .join(host_stdlib.file_name().unwrap());
598 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
599
600 self.append_file(ArchiveStep::ExtraPaths, &host_stdlib, &rel_path)?;
601 }
602 if let Some(target_stdlib) = self.target_stdlib.clone() {
603 let rel_path = Utf8Path::new(LIBDIRS_BASE_DIR)
606 .join("target/0")
607 .join(target_stdlib.file_name().unwrap());
608 let rel_path = convert_rel_path_to_forward_slash(&rel_path);
609
610 self.append_file(ArchiveStep::ExtraPaths, &target_stdlib, &rel_path)?;
611 }
612
613 let encoder = self
615 .builder
616 .into_inner()
617 .map_err(ArchiveCreateError::OutputArchiveIo)?;
618 let buf_writer = encoder
620 .finish()
621 .map_err(ArchiveCreateError::OutputArchiveIo)?;
622 let writer = buf_writer
623 .into_inner()
624 .map_err(|err| ArchiveCreateError::OutputArchiveIo(err.into_error()))?;
625
626 Ok((writer, self.added_files.len()))
627 }
628
629 fn append_from_memory(&mut self, name: &str, contents: &str) -> Result<(), ArchiveCreateError> {
634 let mut header = tar::Header::new_gnu();
635 header.set_size(contents.len() as u64);
636 header.set_mtime(self.unix_timestamp);
637 header.set_mode(0o664);
638 header.set_cksum();
639
640 self.builder
641 .append_data(&mut header, name, io::Cursor::new(contents))
642 .map_err(ArchiveCreateError::OutputArchiveIo)?;
643 self.added_files.insert(name.into());
646 Ok(())
647 }
648
649 fn append_path_recursive<F>(
650 &mut self,
651 step: ArchiveStep,
652 src_path: &Utf8Path,
653 rel_path: &Utf8Path,
654 limit: RecursionDepth,
655 warn_on_exceed_depth: bool,
656 callback: &mut F,
657 ) -> Result<(), ArchiveCreateError>
658 where
659 F: for<'b> FnMut(ArchiveEvent<'b>) -> io::Result<()>,
660 {
661 let metadata =
663 fs::symlink_metadata(src_path).map_err(|error| ArchiveCreateError::InputFileRead {
664 step,
665 path: src_path.to_owned(),
666 is_dir: None,
667 error,
668 })?;
669
670 let mut stack = vec![(limit, src_path.to_owned(), rel_path.to_owned(), metadata)];
672
673 while let Some((depth, src_path, rel_path, metadata)) = stack.pop() {
674 trace!(
675 target: "nextest-runner",
676 "processing `{src_path}` with metadata {metadata:?} \
677 (depth: {depth})",
678 );
679
680 if metadata.is_dir() {
681 if depth.is_zero() {
683 callback(ArchiveEvent::RecursionDepthExceeded {
684 step,
685 path: &src_path,
686 limit: limit.unwrap_finite(),
687 warn: warn_on_exceed_depth,
688 })
689 .map_err(ArchiveCreateError::ReporterIo)?;
690 continue;
691 }
692
693 debug!(
695 target: "nextest-runner",
696 "recursing into `{}`",
697 src_path
698 );
699 let entries = src_path.read_dir_utf8().map_err(|error| {
700 ArchiveCreateError::InputFileRead {
701 step,
702 path: src_path.to_owned(),
703 is_dir: Some(true),
704 error,
705 }
706 })?;
707 for entry in entries {
708 let entry = entry.map_err(|error| ArchiveCreateError::DirEntryRead {
709 path: src_path.to_owned(),
710 error,
711 })?;
712 let metadata =
713 entry
714 .metadata()
715 .map_err(|error| ArchiveCreateError::InputFileRead {
716 step,
717 path: entry.path().to_owned(),
718 is_dir: None,
719 error,
720 })?;
721 let entry_rel_path = rel_path_join(&rel_path, entry.file_name().as_ref());
722 stack.push((
723 depth.decrement(),
724 entry.into_path(),
725 entry_rel_path,
726 metadata,
727 ));
728 }
729 } else if metadata.is_file() || metadata.is_symlink() {
730 self.append_file(step, &src_path, &rel_path)?;
731 } else {
732 callback(ArchiveEvent::UnknownFileType {
734 step,
735 path: &src_path,
736 })
737 .map_err(ArchiveCreateError::ReporterIo)?;
738 }
739 }
740
741 Ok(())
742 }
743
744 fn append_file(
745 &mut self,
746 step: ArchiveStep,
747 src: &Utf8Path,
748 dest: &Utf8Path,
749 ) -> Result<(), ArchiveCreateError> {
750 if !self.added_files.contains(dest) {
752 debug!(
753 target: "nextest-runner",
754 "adding `{src}` to archive as `{dest}`",
755 );
756 self.builder
757 .append_path_with_name(src, dest)
758 .map_err(|error| ArchiveCreateError::InputFileRead {
759 step,
760 path: src.to_owned(),
761 is_dir: Some(false),
762 error,
763 })?;
764 self.added_files.insert(dest.into());
765 }
766 Ok(())
767 }
768}
769
770fn find_std(libdir: &Utf8Path) -> io::Result<Utf8PathBuf> {
771 for path in libdir.read_dir_utf8()? {
772 let path = path?;
773 let file_name = path.file_name();
779 let is_unix = file_name.starts_with("libstd-")
780 && (file_name.ends_with(".so") || file_name.ends_with(".dylib"));
781 let is_windows = file_name.starts_with("std-") && file_name.ends_with(".dll");
782
783 if is_unix || is_windows {
784 return Ok(path.into_path());
785 }
786 }
787
788 Err(io::Error::other(
789 "could not find the Rust standard library in the libdir",
790 ))
791}
792
793fn split_result<T, E>(result: Result<T, E>) -> (Option<T>, Option<E>) {
794 match result {
795 Ok(v) => (Some(v), None),
796 Err(e) => (None, Some(e)),
797 }
798}
799
800#[derive(Clone, Copy, Debug)]
804pub enum ArchiveStep {
805 TestBinaries,
807
808 NonTestBinaries,
810
811 BuildScriptOutDirs,
813
814 LinkedPaths,
816
817 ExtraPaths,
819
820 Stdlib,
822}
823
824impl fmt::Display for ArchiveStep {
825 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826 match self {
827 Self::TestBinaries => write!(f, "test binaries"),
828 Self::NonTestBinaries => write!(f, "non-test binaries"),
829 Self::BuildScriptOutDirs => write!(f, "build script output directories"),
830 Self::LinkedPaths => write!(f, "linked paths"),
831 Self::ExtraPaths => write!(f, "extra paths"),
832 Self::Stdlib => write!(f, "standard library"),
833 }
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
842 fn test_archive_format_autodetect() {
843 assert_eq!(
844 ArchiveFormat::autodetect("foo.tar.zst".as_ref()).unwrap(),
845 ArchiveFormat::TarZst,
846 );
847 assert_eq!(
848 ArchiveFormat::autodetect("foo/bar.tar.zst".as_ref()).unwrap(),
849 ArchiveFormat::TarZst,
850 );
851 ArchiveFormat::autodetect("foo".as_ref()).unwrap_err();
852 ArchiveFormat::autodetect("/".as_ref()).unwrap_err();
853 }
854}