4 use std::{iter::once, path::PathBuf};
7 use parse_display::Display;
8 use proptest::sample::size_range;
9 use rand::{rngs::SmallRng, Rng, SeedableRng};
10 use tempfile::tempdir;
11 use test_strategy::{proptest, Arbitrary};
13 use crate::utils::{assert_same_directory, write_random_content};
15 /// tar and zip extensions
16 #[derive(Arbitrary, Debug, Display)]
17 #[display(style = "lowercase")]
18 enum DirectoryExtension {
34 /// Extensions of single file compression formats
35 #[derive(Arbitrary, Debug, Display)]
36 #[display(style = "lowercase")]
49 #[derive(Arbitrary, Debug, Display)]
52 Directory(DirectoryExtension),
56 /// Converts a list of extension structs to string
57 fn merge_extensions(ext: impl ToString, exts: Vec<FileExtension>) -> String {
59 .chain(exts.into_iter().map(|x| x.to_string()))
64 /// Create random nested directories and files under the specified directory
65 fn create_random_files(dir: impl Into<PathBuf>, depth: u8, rng: &mut SmallRng) {
70 let dir = &dir.into();
72 // create 0 to 4 random files
73 for _ in 0..rng.gen_range(0..=4u32) {
75 &mut tempfile::Builder::new().tempfile_in(dir).unwrap().keep().unwrap().0,
80 // create more random files in 0 to 2 new directories
81 for _ in 0..rng.gen_range(0..=2u32) {
82 create_random_files(tempfile::tempdir_in(dir).unwrap().into_path(), depth - 1, rng);
86 /// Compress and decompress a single empty file
87 #[proptest(cases = 200)]
88 fn single_empty_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec<FileExtension>) {
89 let dir = tempdir().unwrap();
91 let before = &dir.join("before");
92 fs::create_dir(before).unwrap();
93 let before_file = &before.join("file");
94 let archive = &dir.join(format!("file.{}", merge_extensions(ext, exts)));
95 let after = &dir.join("after");
96 fs::write(before_file, []).unwrap();
97 ouch!("-A", "c", before_file, archive);
98 ouch!("-A", "d", archive, "-d", after);
99 assert_same_directory(before, after, false);
102 /// Compress and decompress a single file
103 #[proptest(cases = 150)]
106 #[any(size_range(0..6).lift())] exts: Vec<FileExtension>,
107 // Use faster --level for slower CI targets
108 #[cfg_attr(not(any(target_arch = "arm", target_abi = "eabihf")), strategy(proptest::option::of(0i16..12)))]
109 #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..6)))]
112 let dir = tempdir().unwrap();
113 let dir = dir.path();
114 let before = &dir.join("before");
115 fs::create_dir(before).unwrap();
116 let before_file = &before.join("file");
117 let archive = &dir.join(format!("file.{}", merge_extensions(ext, exts)));
118 let after = &dir.join("after");
119 write_random_content(
120 &mut fs::File::create(before_file).unwrap(),
121 &mut SmallRng::from_entropy(),
123 if let Some(level) = level {
124 ouch!("-A", "c", "-l", level.to_string(), before_file, archive);
126 ouch!("-A", "c", before_file, archive);
128 ouch!("-A", "d", archive, "-d", after);
129 assert_same_directory(before, after, false);
132 /// Compress and decompress a single file over stdin.
133 #[proptest(cases = 200)]
134 fn single_file_stdin(
136 #[any(size_range(0..8).lift())] exts: Vec<FileExtension>,
137 // Use faster --level for slower CI targets
138 #[cfg_attr(not(any(target_arch = "arm", target_abi = "eabihf")), strategy(proptest::option::of(0i16..12)))]
139 #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..6)))]
142 let dir = tempdir().unwrap();
143 let dir = dir.path();
144 let before = &dir.join("before");
145 fs::create_dir(before).unwrap();
146 let before_file = &before.join("file");
147 let format = merge_extensions(&ext, exts);
148 let archive = &dir.join(format!("file.{}", format));
149 let after = &dir.join("after");
150 write_random_content(
151 &mut fs::File::create(before_file).unwrap(),
152 &mut SmallRng::from_entropy(),
154 if let Some(level) = level {
155 ouch!("-A", "c", "-l", level.to_string(), before_file, archive);
157 ouch!("-A", "c", before_file, archive);
159 crate::utils::cargo_bin()
160 .args(["-A", "-y", "d", "-", "-d", after.to_str().unwrap(), "--format", &format])
167 Extension::Directory(_) => {}
168 // We don't know the original filename, so we create a file named stdin-output
169 // Change the top-level "before" directory to match
170 Extension::File(_) => fs::rename(before_file, before_file.with_file_name("stdin-output")).unwrap(),
173 assert_same_directory(before, after, false);
176 /// Compress and decompress a directory with random content generated with `create_random_files`
177 #[proptest(cases = 25)]
179 ext: DirectoryExtension,
180 #[any(size_range(0..1).lift())] extra_extensions: Vec<FileExtension>,
181 #[strategy(0u8..3)] depth: u8,
183 let dir = tempdir().unwrap();
184 let dir = dir.path();
185 let before = &dir.join("before");
186 let before_dir = &before.join("dir");
187 fs::create_dir_all(before_dir).unwrap();
188 let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions)));
189 let after = &dir.join("after");
190 create_random_files(before_dir, depth, &mut SmallRng::from_entropy());
191 ouch!("-A", "c", before_dir, archive);
192 ouch!("-A", "d", archive, "-d", after);
193 assert_same_directory(before, after, !matches!(ext, DirectoryExtension::Zip));
196 #[cfg(feature = "unrar")]
198 fn unpack_rar() -> Result<(), Box<dyn std::error::Error>> {
199 fn test_unpack_rar_single(input: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
200 let dir = tempdir()?;
201 let dirpath = dir.path();
202 let unpacked_path = &dirpath.join("testfile.txt");
203 ouch!("-A", "d", input, "-d", dirpath);
204 let content = fs::read_to_string(unpacked_path)?;
205 assert_eq!(content, "Testing 123\n");
210 let mut datadir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
211 datadir.push("tests/data");
212 ["testfile.rar3.rar.gz", "testfile.rar5.rar"]
214 .try_for_each(|path| test_unpack_rar_single(&datadir.join(path)))?;
219 #[cfg(feature = "unrar")]
221 fn unpack_rar_stdin() -> Result<(), Box<dyn std::error::Error>> {
222 fn test_unpack_rar_single(input: &std::path::Path, format: &str) -> Result<(), Box<dyn std::error::Error>> {
223 let dir = tempdir()?;
224 let dirpath = dir.path();
225 let unpacked_path = &dirpath.join("testfile.txt");
226 crate::utils::cargo_bin()
233 dirpath.to_str().unwrap(),
241 let content = fs::read_to_string(unpacked_path)?;
242 assert_eq!(content, "Testing 123\n");
247 let mut datadir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
248 datadir.push("tests/data");
249 [("testfile.rar3.rar.gz", "rar.gz"), ("testfile.rar5.rar", "rar")]
251 .try_for_each(|(path, format)| test_unpack_rar_single(&datadir.join(path), format))?;