mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 12:05:46 +00:00
add two failing tests and make them pass
This commit is contained in:
parent
e15cb83bd4
commit
f04c916f7f
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -519,6 +519,12 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "difflib"
|
name = "difflib"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -818,6 +824,15 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -1081,17 +1096,20 @@ dependencies = [
|
|||||||
"filetime_creation",
|
"filetime_creation",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
|
"glob",
|
||||||
"gzp",
|
"gzp",
|
||||||
"ignore",
|
"ignore",
|
||||||
"infer",
|
"infer",
|
||||||
"insta",
|
"insta",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
"lz4_flex",
|
"lz4_flex",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parse-display",
|
"parse-display",
|
||||||
|
"pretty_assertions",
|
||||||
"proptest",
|
"proptest",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rayon",
|
"rayon",
|
||||||
@ -1246,6 +1264,16 @@ dependencies = [
|
|||||||
"termtree",
|
"termtree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.93"
|
||||||
@ -2047,6 +2075,12 @@ dependencies = [
|
|||||||
"lzma-sys",
|
"lzma-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
|
@ -58,9 +58,12 @@ clap_mangen = "0.2.24"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0.14"
|
assert_cmd = "2.0.14"
|
||||||
|
glob = "0.3.2"
|
||||||
infer = "0.16.0"
|
infer = "0.16.0"
|
||||||
insta = { version = "1.40.0", features = ["filters"] }
|
insta = { version = "1.40.0", features = ["filters"] }
|
||||||
|
itertools = "0.14.0"
|
||||||
parse-display = "0.9.1"
|
parse-display = "0.9.1"
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
proptest = "1.5.0"
|
proptest = "1.5.0"
|
||||||
rand = { version = "0.8.5", default-features = false, features = [
|
rand = { version = "0.8.5", default-features = false, features = [
|
||||||
"small_rng",
|
"small_rng",
|
||||||
@ -88,4 +91,5 @@ inherits = "release"
|
|||||||
lto = false
|
lto = false
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
incremental = true
|
incremental = true
|
||||||
codegen-units = 16
|
codegen-units = 32
|
||||||
|
strip = false
|
||||||
|
@ -132,7 +132,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
||||||
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
||||||
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
||||||
Tar | Zip | Rar | SevenZip => unreachable!(),
|
Tar | Zip | Rar | SevenZip => decoder,
|
||||||
};
|
};
|
||||||
Ok(decoder)
|
Ok(decoder)
|
||||||
};
|
};
|
||||||
|
@ -36,7 +36,6 @@ pub fn list_archive_contents(
|
|||||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||||
let files = crate::archive::zip::list_archive(zip_archive, password);
|
let files = crate::archive::zip::list_archive(zip_archive, password);
|
||||||
list::list_files(archive_path, files, list_options)?;
|
list::list_files(archive_path, files, list_options)?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ pub fn list_archive_contents(
|
|||||||
|
|
||||||
// Grab previous decoder and wrap it inside of a new one
|
// Grab previous decoder and wrap it inside of a new one
|
||||||
let chain_reader_decoder =
|
let chain_reader_decoder =
|
||||||
|format: &CompressionFormat, decoder: Box<dyn Read + Send>| -> crate::Result<Box<dyn Read + Send>> {
|
|format: CompressionFormat, decoder: Box<dyn Read + Send>| -> crate::Result<Box<dyn Read + Send>> {
|
||||||
let decoder: Box<dyn Read + Send> = match format {
|
let decoder: Box<dyn Read + Send> = match format {
|
||||||
Gzip => Box::new(flate2::read::GzDecoder::new(decoder)),
|
Gzip => Box::new(flate2::read::GzDecoder::new(decoder)),
|
||||||
Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)),
|
Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)),
|
||||||
@ -62,16 +61,22 @@ pub fn list_archive_contents(
|
|||||||
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
|
||||||
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
|
||||||
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
|
||||||
Tar | Zip | Rar | SevenZip => unreachable!(),
|
Tar | Zip | Rar | SevenZip => unreachable!("should be treated by caller"),
|
||||||
};
|
};
|
||||||
Ok(decoder)
|
Ok(decoder)
|
||||||
};
|
};
|
||||||
|
|
||||||
for format in formats.iter().skip(1).rev() {
|
let mut misplaced_archive_format = None;
|
||||||
|
for &format in formats.iter().skip(1).rev() {
|
||||||
|
if format.archive_format() {
|
||||||
|
misplaced_archive_format = Some(format);
|
||||||
|
break;
|
||||||
|
}
|
||||||
reader = chain_reader_decoder(format, reader)?;
|
reader = chain_reader_decoder(format, reader)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match formats[0] {
|
let archive_format = misplaced_archive_format.unwrap_or(formats[0]);
|
||||||
|
let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match archive_format {
|
||||||
Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))),
|
Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))),
|
||||||
Zip => {
|
Zip => {
|
||||||
if formats.len() > 1 {
|
if formats.len() > 1 {
|
||||||
@ -120,7 +125,7 @@ pub fn list_archive_contents(
|
|||||||
Box::new(archive::sevenz::list_archive(archive_path, password)?)
|
Box::new(archive::sevenz::list_archive(archive_path, password)?)
|
||||||
}
|
}
|
||||||
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => {
|
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => {
|
||||||
panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!");
|
unreachable!("Not an archive, should be validated before calling this function.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,8 +60,8 @@ impl Extension {
|
|||||||
|
|
||||||
/// Checks if the first format in `compression_formats` is an archive
|
/// Checks if the first format in `compression_formats` is an archive
|
||||||
pub fn is_archive(&self) -> bool {
|
pub fn is_archive(&self) -> bool {
|
||||||
// Safety: we check that `compression_formats` is not empty in `Self::new`
|
// Index Safety: we check that `compression_formats` is not empty in `Self::new`
|
||||||
self.compression_formats[0].is_archive_format()
|
self.compression_formats[0].archive_format()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ pub enum CompressionFormat {
|
|||||||
|
|
||||||
impl CompressionFormat {
|
impl CompressionFormat {
|
||||||
/// Currently supported archive formats are .tar (and aliases to it) and .zip
|
/// Currently supported archive formats are .tar (and aliases to it) and .zip
|
||||||
fn is_archive_format(&self) -> bool {
|
pub fn archive_format(&self) -> bool {
|
||||||
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
||||||
match self {
|
match self {
|
||||||
Tar | Zip | Rar | SevenZip => true,
|
Tar | Zip | Rar | SevenZip => true,
|
||||||
@ -147,14 +147,13 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_extension(name: &mut &[u8]) -> Option<Extension> {
|
fn split_extension_at_end(name: &[u8]) -> Option<(&[u8], Extension)> {
|
||||||
let (new_name, ext) = name.rsplit_once_str(b".")?;
|
let (new_name, ext) = name.rsplit_once_str(b".")?;
|
||||||
if matches!(new_name, b"" | b"." | b"..") {
|
if matches!(new_name, b"" | b"." | b"..") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let ext = to_extension(ext)?;
|
let ext = to_extension(ext)?;
|
||||||
*name = new_name;
|
Some((new_name, ext))
|
||||||
Some(ext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_format_flag(input: &OsStr) -> crate::Result<Vec<Extension>> {
|
pub fn parse_format_flag(input: &OsStr) -> crate::Result<Vec<Extension>> {
|
||||||
@ -188,7 +187,7 @@ pub fn parse_format_flag(input: &OsStr) -> crate::Result<Vec<Extension>> {
|
|||||||
|
|
||||||
/// Extracts extensions from a path.
|
/// Extracts extensions from a path.
|
||||||
///
|
///
|
||||||
/// Returns both the remaining path and the list of extension objects
|
/// Returns both the remaining path and the list of extension objects.
|
||||||
pub fn separate_known_extensions_from_name(path: &Path) -> (&Path, Vec<Extension>) {
|
pub fn separate_known_extensions_from_name(path: &Path) -> (&Path, Vec<Extension>) {
|
||||||
let mut extensions = vec![];
|
let mut extensions = vec![];
|
||||||
|
|
||||||
@ -196,9 +195,12 @@ pub fn separate_known_extensions_from_name(path: &Path) -> (&Path, Vec<Extension
|
|||||||
return (path, extensions);
|
return (path, extensions);
|
||||||
};
|
};
|
||||||
|
|
||||||
// While there is known extensions at the tail, grab them
|
while let Some((new_name, extension)) = split_extension_at_end(&name) {
|
||||||
while let Some(extension) = split_extension(&mut name) {
|
name = new_name;
|
||||||
extensions.insert(0, extension);
|
extensions.insert(0, extension);
|
||||||
|
if extensions[0].is_archive() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(name) = name.to_str() {
|
if let Ok(name) = name.to_str() {
|
||||||
@ -365,4 +367,19 @@ mod tests {
|
|||||||
"linux.pkg.info.tar.zst"
|
"linux.pkg.info.tar.zst"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_parsing_with_multiple_archive_formats() {
|
||||||
|
assert_eq!(
|
||||||
|
separate_known_extensions_from_name("file.tar.zip".as_ref()),
|
||||||
|
("file.tar".as_ref(), vec![Extension::new(&[Zip], "zip")])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
separate_known_extensions_from_name("file.7z.zst.zip.lz4".as_ref()),
|
||||||
|
(
|
||||||
|
"file.7z.zst".as_ref(),
|
||||||
|
vec![Extension::new(&[Zip], "zip"), Extension::new(&[Lz4], "lz4")]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
use itertools::Itertools;
|
||||||
use parse_display::Display;
|
use parse_display::Display;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use proptest::sample::size_range;
|
use proptest::sample::size_range;
|
||||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
@ -17,7 +19,7 @@ use test_strategy::{proptest, Arbitrary};
|
|||||||
use crate::utils::{assert_same_directory, write_random_content};
|
use crate::utils::{assert_same_directory, write_random_content};
|
||||||
|
|
||||||
/// tar and zip extensions
|
/// tar and zip extensions
|
||||||
#[derive(Arbitrary, Debug, Display)]
|
#[derive(Arbitrary, Clone, Copy, Debug, Display)]
|
||||||
#[display(style = "lowercase")]
|
#[display(style = "lowercase")]
|
||||||
enum DirectoryExtension {
|
enum DirectoryExtension {
|
||||||
#[display("7z")]
|
#[display("7z")]
|
||||||
@ -865,3 +867,96 @@ fn unpack_multiple_sources_into_the_same_destination_with_merge(
|
|||||||
|
|
||||||
assert_eq!(5, out_path.as_path().read_dir()?.count());
|
assert_eq!(5, out_path.as_path().read_dir()?.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reading_nested_archives_with_two_archive_extensions_adjacent() {
|
||||||
|
let archive_formats = ["tar", "zip", "7z"].into_iter();
|
||||||
|
|
||||||
|
for (first_archive, second_archive) in archive_formats.clone().cartesian_product(archive_formats.rev()) {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let in_dir = |path: &str| format!("{}/{}", temp_dir.path().display(), path);
|
||||||
|
|
||||||
|
fs::write(in_dir("a.txt"), "contents").unwrap();
|
||||||
|
|
||||||
|
let files = [
|
||||||
|
"a.txt",
|
||||||
|
&format!("b.{first_archive}"),
|
||||||
|
&format!("c.{first_archive}.{second_archive}"),
|
||||||
|
];
|
||||||
|
let transformations = [first_archive, second_archive];
|
||||||
|
let compressed_path = in_dir(files.last().unwrap());
|
||||||
|
|
||||||
|
for (window, format) in files.windows(2).zip(transformations.iter()) {
|
||||||
|
let [a, b] = [window[0], window[1]].map(in_dir);
|
||||||
|
crate::utils::cargo_bin()
|
||||||
|
.args(["compress", &a, &b, "--format", format])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::utils::cargo_bin()
|
||||||
|
.args(["list", &compressed_path, "--yes"])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
|
crate::utils::cargo_bin()
|
||||||
|
.args(["decompress", &compressed_path, "--dir", &in_dir("out"), "--yes"])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
|
let decompressed_files = glob::glob(&format!("{}/*", in_dir("out")))
|
||||||
|
.unwrap()
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
let decompression_expected = format!("{}/out/{}", temp_dir.path().display(), files[1]);
|
||||||
|
assert_eq!(decompressed_files, [Path::new(&decompression_expected)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reading_nested_archives_with_two_archive_extensions_interleaved() {
|
||||||
|
let archive_formats = ["tar", "zip", "7z"].into_iter();
|
||||||
|
|
||||||
|
for (first_archive, second_archive) in archive_formats.clone().cartesian_product(archive_formats.rev()) {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let in_dir = |path: &str| format!("{}/{}", temp_dir.path().display(), path);
|
||||||
|
|
||||||
|
fs::write(in_dir("a.txt"), "contents").unwrap();
|
||||||
|
|
||||||
|
let files = [
|
||||||
|
"a.txt",
|
||||||
|
&format!("c.{first_archive}"),
|
||||||
|
&format!("d.{first_archive}.zst"),
|
||||||
|
&format!("e.{first_archive}.zst.{second_archive}"),
|
||||||
|
&format!("f.{first_archive}.zst.{second_archive}.lz4"),
|
||||||
|
];
|
||||||
|
let transformations = [first_archive, "zst", second_archive, "lz4"];
|
||||||
|
let compressed_path = in_dir(files.last().unwrap());
|
||||||
|
|
||||||
|
for (window, format) in files.windows(2).zip(transformations.iter()) {
|
||||||
|
let [a, b] = [window[0], window[1]].map(in_dir);
|
||||||
|
crate::utils::cargo_bin()
|
||||||
|
.args(["compress", &a, &b, "--format", format])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: uncomment after fixing the 7z BadSignature error [4, 34, 77, 24, 96, 64]
|
||||||
|
// crate::utils::cargo_bin()
|
||||||
|
// .args(["list", &compressed_path, "--yes"])
|
||||||
|
// .assert()
|
||||||
|
// .success();
|
||||||
|
|
||||||
|
crate::utils::cargo_bin()
|
||||||
|
.args(["decompress", &compressed_path, "--dir", &in_dir("out"), "--yes"])
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
|
let decompressed_files = glob::glob(&format!("{}/*", in_dir("out")))
|
||||||
|
.unwrap()
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
let decompression_expected = format!("{}/out/{}", temp_dir.path().display(), files[2]);
|
||||||
|
assert_eq!(decompressed_files, [Path::new(&decompression_expected)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user