refactor(extract): report extraction progress through the sink

The extraction spinner drew straight to stderr from inside the
library, so every consumer got terminal UI it never asked for.
extract_tarball and its helpers now count extracted files themselves
and emit Progress events; rendering belongs to the caller.
This commit is contained in:
2026-10-02 23:51:42 +02:00
parent d481bd9028
commit 82d2ed3382
3 changed files with 106 additions and 111 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ fn main() -> Result<()> {
arch: args.arch.clone().unwrap_or_default(), arch: args.arch.clone().unwrap_or_default(),
no_cache: args.no_cache, no_cache: args.no_cache,
}; };
let rootfs = cache.prepare(&request)?; let rootfs = cache.prepare(&request, &ecr::NoProgress)?;
// Branch based on --kernel flag // Branch based on --kernel flag
// Option<Option<PathBuf>> (require_equals: the value must use =PATH syntax // Option<Option<PathBuf>> (require_equals: the value must use =PATH syntax
+86 -99
View File
@@ -1,13 +1,13 @@
use crate::progress::{Progress, Unit};
use crate::veprintln; use crate::veprintln;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use indicatif::{ProgressBar, ProgressStyle};
use std::fs::File; use std::fs::File;
use std::io::{BufReader, Read}; use std::io::{BufReader, Read};
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
/// Extract a tarball to the specified directory /// Extract a tarball to the specified directory
pub fn extract_tarball(tarball: &Path, dest: &Path) -> Result<()> { pub fn extract_tarball(tarball: &Path, dest: &Path, progress: &dyn Progress) -> Result<()> {
let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or(""); let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or("");
// OCI bundle files are always named "oci-…" by generate_cache_filename. // OCI bundle files are always named "oci-…" by generate_cache_filename.
@@ -16,15 +16,15 @@ pub fn extract_tarball(tarball: &Path, dest: &Path) -> Result<()> {
// reading the entire compressed archive twice for large OCI images). // reading the entire compressed archive twice for large OCI images).
if filename.starts_with("oci-") { if filename.starts_with("oci-") {
veprintln!("Detected multi-layer OCI image, extracting layers..."); veprintln!("Detected multi-layer OCI image, extracting layers...");
return extract_multi_layer_oci(tarball, dest); return extract_multi_layer_oci(tarball, dest, progress);
} }
extract_single_archive(tarball, dest) extract_single_archive(tarball, dest, progress)
} }
/// Extract a plain rootfs tarball, detecting the compression format from /// Extract a plain rootfs tarball, detecting the compression format from
/// the filename with a magic-byte fallback. /// the filename with a magic-byte fallback.
fn extract_single_archive(tarball: &Path, dest: &Path) -> Result<()> { fn extract_single_archive(tarball: &Path, dest: &Path, progress: &dyn Progress) -> Result<()> {
let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or(""); let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or("");
let file = File::open(tarball) let file = File::open(tarball)
@@ -35,13 +35,13 @@ fn extract_single_archive(tarball: &Path, dest: &Path) -> Result<()> {
// Detect compression format from filename // Detect compression format from filename
if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") { if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") {
extract_gz(reader, dest, &mut fixups)?; extract_gz(reader, dest, &mut fixups, progress)?;
} else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") { } else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") {
extract_xz(reader, dest, &mut fixups)?; extract_xz(reader, dest, &mut fixups, progress)?;
} else if filename.ends_with(".tar.zst") || filename.ends_with(".tar.zstd") { } else if filename.ends_with(".tar.zst") || filename.ends_with(".tar.zstd") {
extract_zst(reader, dest, &mut fixups)?; extract_zst(reader, dest, &mut fixups, progress)?;
} else if filename.ends_with(".tar") { } else if filename.ends_with(".tar") {
extract_tar(reader, dest, &mut fixups)?; extract_tar(reader, dest, &mut fixups, progress)?;
} else { } else {
// Try to detect from magic bytes // Try to detect from magic bytes
let mut magic = [0u8; 6]; let mut magic = [0u8; 6];
@@ -53,25 +53,25 @@ fn extract_single_archive(tarball: &Path, dest: &Path) -> Result<()> {
// gzip magic // gzip magic
drop(peek_reader); drop(peek_reader);
let file = File::open(tarball)?; let file = File::open(tarball)?;
extract_gz(BufReader::new(file), dest, &mut fixups)?; extract_gz(BufReader::new(file), dest, &mut fixups, progress)?;
} }
[0xfd, b'7', b'z', b'X', b'Z', 0x00] => { [0xfd, b'7', b'z', b'X', b'Z', 0x00] => {
// xz magic // xz magic
drop(peek_reader); drop(peek_reader);
let file = File::open(tarball)?; let file = File::open(tarball)?;
extract_xz(BufReader::new(file), dest, &mut fixups)?; extract_xz(BufReader::new(file), dest, &mut fixups, progress)?;
} }
[0x28, 0xb5, 0x2f, 0xfd, ..] => { [0x28, 0xb5, 0x2f, 0xfd, ..] => {
// zstd magic // zstd magic
drop(peek_reader); drop(peek_reader);
let file = File::open(tarball)?; let file = File::open(tarball)?;
extract_zst(BufReader::new(file), dest, &mut fixups)?; extract_zst(BufReader::new(file), dest, &mut fixups, progress)?;
} }
_ => { _ => {
// Assume uncompressed tar // Assume uncompressed tar
drop(peek_reader); drop(peek_reader);
let file = File::open(tarball)?; let file = File::open(tarball)?;
extract_tar(BufReader::new(file), dest, &mut fixups)?; extract_tar(BufReader::new(file), dest, &mut fixups, progress)?;
} }
} }
} }
@@ -80,7 +80,7 @@ fn extract_single_archive(tarball: &Path, dest: &Path) -> Result<()> {
} }
/// Extract a multi-layer OCI image /// Extract a multi-layer OCI image
fn extract_multi_layer_oci(tarball: &Path, dest: &Path) -> Result<()> { fn extract_multi_layer_oci(tarball: &Path, dest: &Path, progress: &dyn Progress) -> Result<()> {
let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or(""); let filename = tarball.file_name().and_then(|n| n.to_str()).unwrap_or("");
// Extract the outer OCI bundle (layers.manifest + layer_N.tar.gz files) // Extract the outer OCI bundle (layers.manifest + layer_N.tar.gz files)
@@ -116,7 +116,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path) -> Result<()> {
// an OCI cache entry (the dispatch above keys on the "oci-" filename // an OCI cache entry (the dispatch above keys on the "oci-" filename
// prefix). The outer archive is a plain rootfs tarball. // prefix). The outer archive is a plain rootfs tarball.
veprintln!("No layers.manifest in bundle; extracting as plain rootfs tarball"); veprintln!("No layers.manifest in bundle; extracting as plain rootfs tarball");
return extract_single_archive(tarball, dest); return extract_single_archive(tarball, dest, progress);
} }
// Read the layers manifest // Read the layers manifest
@@ -135,7 +135,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path) -> Result<()> {
continue; continue;
} }
veprintln!("Extracting layer: {}", layer_name); veprintln!("Extracting layer: {}", layer_name);
extract_oci_layer(&layer_path, dest, &mut fixups)?; extract_oci_layer(&layer_path, dest, &mut fixups, progress)?;
} }
apply_fixups(dest, &mut fixups) apply_fixups(dest, &mut fixups)
@@ -144,7 +144,12 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path) -> Result<()> {
/// Decompress and extract one OCI layer tarball into `dest`, honouring /// Decompress and extract one OCI layer tarball into `dest`, honouring
/// whiteout markers. Compression is inferred from the filename then from /// whiteout markers. Compression is inferred from the filename then from
/// magic bytes. /// magic bytes.
fn extract_oci_layer(layer_path: &Path, dest: &Path, fixups: &mut DeferredFixups) -> Result<()> { fn extract_oci_layer(
layer_path: &Path,
dest: &Path,
fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> {
use std::io::Read; use std::io::Read;
let layer_name = layer_path let layer_name = layer_path
.file_name() .file_name()
@@ -154,82 +159,63 @@ fn extract_oci_layer(layer_path: &Path, dest: &Path, fixups: &mut DeferredFixups
.with_context(|| format!("Failed to open layer: {}", layer_path.display()))?; .with_context(|| format!("Failed to open layer: {}", layer_path.display()))?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
if layer_name.ends_with(".tar.gz") || layer_name.ends_with(".tgz") { // The decompressor type differs per format; erase it behind a trait
extract_with_progress( // object so a single archive-extraction call site serves every branch.
tar::Archive::new(flate2::read::GzDecoder::new(reader)), let archive: tar::Archive<Box<dyn Read>> =
dest, if layer_name.ends_with(".tar.gz") || layer_name.ends_with(".tgz") {
"Extracting OCI layer", tar::Archive::new(Box::new(flate2::read::GzDecoder::new(reader)))
fixups, } else if layer_name.ends_with(".tar.xz") || layer_name.ends_with(".txz") {
) tar::Archive::new(Box::new(xz2::read::XzDecoder::new(reader)))
} else if layer_name.ends_with(".tar.xz") || layer_name.ends_with(".txz") { } else if layer_name.ends_with(".tar.zst") || layer_name.ends_with(".tar.zstd") {
extract_with_progress( tar::Archive::new(Box::new(zstd::stream::read::Decoder::new(reader)?))
tar::Archive::new(xz2::read::XzDecoder::new(reader)), } else {
dest, // Fall back to magic-byte detection
"Extracting OCI layer", let mut magic = [0u8; 6];
fixups, let mut peek = BufReader::new(File::open(layer_path)?);
) let _ = peek.read_exact(&mut magic); // short reads are fine for detection
} else if layer_name.ends_with(".tar.zst") || layer_name.ends_with(".tar.zstd") { drop(peek);
extract_with_progress( let file = File::open(layer_path)?;
tar::Archive::new(zstd::stream::read::Decoder::new(reader)?), match magic {
dest, [0x1f, 0x8b, ..] => {
"Extracting OCI layer", tar::Archive::new(Box::new(flate2::read::GzDecoder::new(BufReader::new(file))))
fixups, }
) [0xfd, b'7', b'z', b'X', b'Z', 0x00] => {
} else { tar::Archive::new(Box::new(xz2::read::XzDecoder::new(BufReader::new(file))))
// Fall back to magic-byte detection }
let mut magic = [0u8; 6]; [0x28, 0xb5, 0x2f, 0xfd, ..] => tar::Archive::new(Box::new(
let mut peek = BufReader::new(File::open(layer_path)?); zstd::stream::read::Decoder::new(BufReader::new(file))?,
let _ = peek.read_exact(&mut magic); // short reads are fine for detection )),
drop(peek); _ => tar::Archive::new(Box::new(BufReader::new(file))),
match magic { }
[0x1f, 0x8b, ..] => extract_with_progress( };
tar::Archive::new(flate2::read::GzDecoder::new(BufReader::new(File::open(
layer_path, extract_archive(archive, dest, "Extracting OCI layer", fixups, progress)
)?))),
dest,
"Extracting OCI layer",
fixups,
),
[0xfd, b'7', b'z', b'X', b'Z', 0x00] => extract_with_progress(
tar::Archive::new(xz2::read::XzDecoder::new(BufReader::new(File::open(
layer_path,
)?))),
dest,
"Extracting OCI layer",
fixups,
),
[0x28, 0xb5, 0x2f, 0xfd, ..] => extract_with_progress(
tar::Archive::new(zstd::stream::read::Decoder::new(BufReader::new(
File::open(layer_path)?,
))?),
dest,
"Extracting OCI layer",
fixups,
),
_ => extract_with_progress(
tar::Archive::new(BufReader::new(File::open(layer_path)?)),
dest,
"Extracting OCI layer",
fixups,
),
}
}
} }
fn extract_gz<R: std::io::Read>(reader: R, dest: &Path, fixups: &mut DeferredFixups) -> Result<()> { fn extract_gz<R: std::io::Read>(
reader: R,
dest: &Path,
fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> {
let gz_decoder = flate2::read::GzDecoder::new(reader); let gz_decoder = flate2::read::GzDecoder::new(reader);
let archive = tar::Archive::new(gz_decoder); let archive = tar::Archive::new(gz_decoder);
extract_with_progress(archive, dest, "Extracting gzip archive", fixups)?; extract_archive(archive, dest, "Extracting gzip archive", fixups, progress)?;
Ok(()) Ok(())
} }
fn extract_xz<R: std::io::Read>(reader: R, dest: &Path, fixups: &mut DeferredFixups) -> Result<()> { fn extract_xz<R: std::io::Read>(
reader: R,
dest: &Path,
fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> {
let xz_decoder = xz2::read::XzDecoder::new(reader); let xz_decoder = xz2::read::XzDecoder::new(reader);
let archive = tar::Archive::new(xz_decoder); let archive = tar::Archive::new(xz_decoder);
extract_with_progress(archive, dest, "Extracting xz archive", fixups)?; extract_archive(archive, dest, "Extracting xz archive", fixups, progress)?;
Ok(()) Ok(())
} }
@@ -238,11 +224,12 @@ fn extract_zst<R: std::io::Read>(
reader: R, reader: R,
dest: &Path, dest: &Path,
fixups: &mut DeferredFixups, fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> { ) -> Result<()> {
let zst_decoder = zstd::Decoder::new(reader)?; let zst_decoder = zstd::Decoder::new(reader)?;
let archive = tar::Archive::new(zst_decoder); let archive = tar::Archive::new(zst_decoder);
extract_with_progress(archive, dest, "Extracting zstd archive", fixups)?; extract_archive(archive, dest, "Extracting zstd archive", fixups, progress)?;
Ok(()) Ok(())
} }
@@ -251,10 +238,11 @@ fn extract_tar<R: std::io::Read>(
reader: R, reader: R,
dest: &Path, dest: &Path,
fixups: &mut DeferredFixups, fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> { ) -> Result<()> {
let archive = tar::Archive::new(reader); let archive = tar::Archive::new(reader);
extract_with_progress(archive, dest, "Extracting tar archive", fixups)?; extract_archive(archive, dest, "Extracting tar archive", fixups, progress)?;
Ok(()) Ok(())
} }
@@ -344,20 +332,16 @@ fn apply_hard_link(dest: &Path, dst: &Path, target: &Path) -> Result<()> {
Ok(()) Ok(())
} }
/// Extract tar archive with progress bar, handling whiteout files for OCI layers /// Extract tar archive entries, reporting a per-file count through
fn extract_with_progress<R: std::io::Read>( /// `progress` and handling whiteout files for OCI layers
fn extract_archive<R: std::io::Read>(
mut archive: tar::Archive<R>, mut archive: tar::Archive<R>,
dest: &Path, dest: &Path,
msg: &str, label: &str,
fixups: &mut DeferredFixups, fixups: &mut DeferredFixups,
progress: &dyn Progress,
) -> Result<()> { ) -> Result<()> {
let pb = ProgressBar::new_spinner(); progress.begin(label, Unit::Files, None);
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg} ({pos} files)")
.unwrap(),
);
pb.set_message(msg.to_string());
archive.set_preserve_permissions(true); archive.set_preserve_permissions(true);
archive.set_preserve_ownerships(false); archive.set_preserve_ownerships(false);
@@ -367,6 +351,7 @@ fn extract_with_progress<R: std::io::Read>(
.entries() .entries()
.context("Failed to read archive entries")?; .context("Failed to read archive entries")?;
let mut count: u64 = 0;
for entry in entries { for entry in entries {
let mut entry = entry.context("Failed to read archive entry")?; let mut entry = entry.context("Failed to read archive entry")?;
@@ -431,11 +416,12 @@ fn extract_with_progress<R: std::io::Read>(
.with_context(|| format!("Failed to extract {}", path.display()))?; .with_context(|| format!("Failed to extract {}", path.display()))?;
} }
pb.inc(1); count += 1;
progress.advance(1);
} }
pb.finish_and_clear(); progress.finish();
veprintln!("Extracted {} files", pb.position()); veprintln!("Extracted {} files", count);
Ok(()) Ok(())
} }
@@ -455,6 +441,7 @@ fn remove_path(path: &Path) -> std::io::Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::progress::NoProgress;
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::{MetadataExt, PermissionsExt};
use tar::{Builder, EntryType, Header}; use tar::{Builder, EntryType, Header};
@@ -533,7 +520,7 @@ mod tests {
], ],
); );
extract_tarball(&tarball, out.path()).unwrap(); extract_tarball(&tarball, out.path(), &NoProgress).unwrap();
let base = out let base = out
.path() .path()
@@ -575,7 +562,7 @@ mod tests {
], ],
); );
extract_tarball(&tarball, out.path()).unwrap(); extract_tarball(&tarball, out.path(), &NoProgress).unwrap();
assert_eq!( assert_eq!(
std::fs::read(out.path().join("etc/dir/link.0")).unwrap(), std::fs::read(out.path().join("etc/dir/link.0")).unwrap(),
b"payload" b"payload"
@@ -618,7 +605,7 @@ mod tests {
], ],
); );
extract_tarball(&tarball, out.path()).unwrap(); extract_tarball(&tarball, out.path(), &NoProgress).unwrap();
assert_eq!( assert_eq!(
std::fs::read(out.path().join("etc/dir/keep.txt")).unwrap(), std::fs::read(out.path().join("etc/dir/keep.txt")).unwrap(),
b"kept", b"kept",
+19 -11
View File
@@ -8,13 +8,13 @@
//! entry and skips both the download and the provisioning step: //! entry and skips both the download and the provisioning step:
//! //!
//! ```no_run //! ```no_run
//! use ecr::{chroot, ExecOptions, PrepareRequest, RootfsCache}; //! use ecr::{chroot, ExecOptions, NoProgress, PrepareRequest, RootfsCache};
//! //!
//! # fn main() -> anyhow::Result<()> { //! # fn main() -> anyhow::Result<()> {
//! let cache = RootfsCache::new(RootfsCache::default_dir()?); //! let cache = RootfsCache::new(RootfsCache::default_dir()?);
//! let req = PrepareRequest::new("alpine:3.23"); //! let req = PrepareRequest::new("alpine:3.23");
//! //!
//! let rootfs = cache.prepare_provisioned(&req, |rootfs| { //! let rootfs = cache.prepare_provisioned(&req, &NoProgress, |rootfs| {
//! let code = rootfs.exec(&ExecOptions { //! let code = rootfs.exec(&ExecOptions {
//! command: vec!["apk".into(), "add".into(), "curl".into()], //! command: vec!["apk".into(), "add".into(), "curl".into()],
//! env: chroot::default_env(rootfs.dir()), //! env: chroot::default_env(rootfs.dir()),
@@ -47,6 +47,7 @@ use crate::distro::{
use crate::download::{digest_sidecar, download_image, fetch_oci_digest}; use crate::download::{digest_sidecar, download_image, fetch_oci_digest};
use crate::exec::ExecOptions; use crate::exec::ExecOptions;
use crate::extract::extract_tarball; use crate::extract::extract_tarball;
use crate::progress::Progress;
use crate::veprintln; use crate::veprintln;
/// Sidecar suffix marking a cache entry that holds a provisioned rootfs. /// Sidecar suffix marking a cache entry that holds a provisioned rootfs.
@@ -89,8 +90,9 @@ impl RootfsCache {
/// Resolve `req`, download through the cache when needed, and extract /// Resolve `req`, download through the cache when needed, and extract
/// into a fresh scratch directory (removed when the returned /// into a fresh scratch directory (removed when the returned
/// [`PreparedRootfs`] is dropped). /// [`PreparedRootfs`] is dropped). Download and extraction progress is
pub fn prepare(&self, req: &PrepareRequest) -> Result<PreparedRootfs> { /// reported through `progress`.
pub fn prepare(&self, req: &PrepareRequest, progress: &dyn Progress) -> Result<PreparedRootfs> {
let arch = req.resolved_arch(); let arch = req.resolved_arch();
let source = resolve_source(&req.image, &arch)?; let source = resolve_source(&req.image, &arch)?;
let cache_path = self.tarball_path(&source, &arch); let cache_path = self.tarball_path(&source, &arch);
@@ -143,7 +145,7 @@ impl RootfsCache {
veprintln!("Using cached tarball: {}", cache_path.display()); veprintln!("Using cached tarball: {}", cache_path.display());
} }
extract_prepared(cache_path, arch) extract_prepared(cache_path, arch, progress)
} }
/// Prepare a provisioned rootfs with cache amortization: when the cache /// Prepare a provisioned rootfs with cache amortization: when the cache
@@ -161,6 +163,7 @@ impl RootfsCache {
pub fn prepare_provisioned<F>( pub fn prepare_provisioned<F>(
&self, &self,
req: &PrepareRequest, req: &PrepareRequest,
progress: &dyn Progress,
provision: F, provision: F,
) -> Result<PreparedRootfs> ) -> Result<PreparedRootfs>
where where
@@ -173,10 +176,10 @@ impl RootfsCache {
if !req.no_cache && cache_path.exists() && marker.exists() { if !req.no_cache && cache_path.exists() && marker.exists() {
veprintln!("Using provisioned cache entry: {}", cache_path.display()); veprintln!("Using provisioned cache entry: {}", cache_path.display());
return extract_prepared(cache_path, arch); return extract_prepared(cache_path, arch, progress);
} }
let rootfs = self.prepare(req)?; let rootfs = self.prepare(req, progress)?;
provision(&rootfs)?; provision(&rootfs)?;
rootfs.persist().with_context(|| { rootfs.persist().with_context(|| {
format!( format!(
@@ -298,12 +301,16 @@ fn resolve_source(image: &str, arch: &str) -> Result<ImageSource> {
} }
} }
fn extract_prepared(cache_path: PathBuf, arch: String) -> Result<PreparedRootfs> { fn extract_prepared(
cache_path: PathBuf,
arch: String,
progress: &dyn Progress,
) -> Result<PreparedRootfs> {
let temp = tempfile::tempdir().context("Failed to create temporary directory")?; let temp = tempfile::tempdir().context("Failed to create temporary directory")?;
let rootfs = temp.path().to_path_buf(); let rootfs = temp.path().to_path_buf();
veprintln!("Extracting to: {}", rootfs.display()); veprintln!("Extracting to: {}", rootfs.display());
extract_tarball(&cache_path, &rootfs)?; extract_tarball(&cache_path, &rootfs, progress)?;
Ok(PreparedRootfs { Ok(PreparedRootfs {
rootfs: temp, rootfs: temp,
@@ -461,6 +468,7 @@ fn get_tarball_extension(url: &str) -> &str {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::progress::NoProgress;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
fn direct_tarball(distro: Distro, version: &str) -> ImageSource { fn direct_tarball(distro: Distro, version: &str) -> ImageSource {
@@ -539,7 +547,7 @@ mod tests {
pack_rootfs(src.path(), &dest).unwrap(); pack_rootfs(src.path(), &dest).unwrap();
let out = tempfile::tempdir().unwrap(); let out = tempfile::tempdir().unwrap();
crate::extract::extract_tarball(&dest, out.path()).unwrap(); crate::extract::extract_tarball(&dest, out.path(), &NoProgress).unwrap();
assert_eq!( assert_eq!(
std::fs::read(out.path().join("hello.txt")).unwrap(), std::fs::read(out.path().join("hello.txt")).unwrap(),
@@ -596,7 +604,7 @@ mod tests {
// The persisted entry extracts back to the original contents // The persisted entry extracts back to the original contents
let out = tempfile::tempdir().unwrap(); let out = tempfile::tempdir().unwrap();
crate::extract::extract_tarball(&cache_path, out.path()).unwrap(); crate::extract::extract_tarball(&cache_path, out.path(), &NoProgress).unwrap();
assert_eq!(std::fs::read(out.path().join("file.txt")).unwrap(), b"data"); assert_eq!(std::fs::read(out.path().join("file.txt")).unwrap(), b"data");
} }