chore: improve message after moving file
[ouch.git] / tests / integration.rs
blobed2b4b18862bdbfb9b728699bf8f7c1c99d789a7
1 #[macro_use]
2 mod utils;
4 use std::{iter::once, path::PathBuf};
6 use fs_err as fs;
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 {
19     #[display("7z")]
20     SevenZ,
21     Tar,
22     Tbz,
23     Tbz2,
24     Tbz3,
25     Tgz,
26     Tlz4,
27     Tlzma,
28     Tsz,
29     Txz,
30     Tzst,
31     Zip,
34 /// Extensions of single file compression formats
35 #[derive(Arbitrary, Debug, Display)]
36 #[display(style = "lowercase")]
37 enum FileExtension {
38     Bz,
39     Bz2,
40     Bz3,
41     Gz,
42     Lz4,
43     Lzma,
44     Sz,
45     Xz,
46     Zst,
49 #[derive(Arbitrary, Debug, Display)]
50 #[display("{0}")]
51 enum Extension {
52     Directory(DirectoryExtension),
53     File(FileExtension),
56 /// Converts a list of extension structs to string
57 fn merge_extensions(ext: impl ToString, exts: Vec<FileExtension>) -> String {
58     once(ext.to_string())
59         .chain(exts.into_iter().map(|x| x.to_string()))
60         .collect::<Vec<_>>()
61         .join(".")
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) {
66     if depth == 0 {
67         return;
68     }
70     let dir = &dir.into();
72     // create 0 to 4 random files
73     for _ in 0..rng.gen_range(0..=4u32) {
74         write_random_content(
75             &mut tempfile::Builder::new().tempfile_in(dir).unwrap().keep().unwrap().0,
76             rng,
77         );
78     }
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);
83     }
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();
90     let dir = dir.path();
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 = 250)]
104 fn single_file(
105     ext: Extension,
106     #[any(size_range(0..8).lift())] exts: Vec<FileExtension>,
107     #[cfg_attr(not(target_arch = "arm"), strategy(proptest::option::of(0i16..12)))]
108     // Decrease the value of --level flag for `arm` systems, because our GitHub
109     // Actions CI runs QEMU which makes the memory consumption higher.
110     #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..8)))]
111     level: Option<i16>,
112 ) {
113     let dir = tempdir().unwrap();
114     let dir = dir.path();
115     let before = &dir.join("before");
116     fs::create_dir(before).unwrap();
117     let before_file = &before.join("file");
118     let archive = &dir.join(format!("file.{}", merge_extensions(ext, exts)));
119     let after = &dir.join("after");
120     write_random_content(
121         &mut fs::File::create(before_file).unwrap(),
122         &mut SmallRng::from_entropy(),
123     );
124     if let Some(level) = level {
125         ouch!("-A", "c", "-l", level.to_string(), before_file, archive);
126     } else {
127         ouch!("-A", "c", before_file, archive);
128     }
129     ouch!("-A", "d", archive, "-d", after);
130     assert_same_directory(before, after, false);
133 /// Compress and decompress a single file over stdin.
134 #[proptest(cases = 200)]
135 fn single_file_stdin(
136     ext: Extension,
137     #[any(size_range(0..8).lift())] exts: Vec<FileExtension>,
138     #[cfg_attr(not(target_arch = "arm"), strategy(proptest::option::of(0i16..12)))]
139     // Decrease the value of --level flag for `arm` systems, because our GitHub
140     // Actions CI runs QEMU which makes the memory consumption higher.
141     #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..8)))]
142     level: Option<i16>,
143 ) {
144     let dir = tempdir().unwrap();
145     let dir = dir.path();
146     let before = &dir.join("before");
147     fs::create_dir(before).unwrap();
148     let before_file = &before.join("file");
149     let format = merge_extensions(&ext, exts);
150     let archive = &dir.join(format!("file.{}", format));
151     let after = &dir.join("after");
152     write_random_content(
153         &mut fs::File::create(before_file).unwrap(),
154         &mut SmallRng::from_entropy(),
155     );
156     if let Some(level) = level {
157         ouch!("-A", "c", "-l", level.to_string(), before_file, archive);
158     } else {
159         ouch!("-A", "c", before_file, archive);
160     }
161     crate::utils::cargo_bin()
162         .args(["-A", "-y", "d", "-", "-d", after.to_str().unwrap(), "--format", &format])
163         .pipe_stdin(archive)
164         .unwrap()
165         .assert()
166         .success();
168     match ext {
169         Extension::Directory(_) => {}
170         // We don't know the original filename, so we create a file named stdin-output
171         // Change the top-level "before" directory to match
172         Extension::File(_) => fs::rename(before_file, before_file.with_file_name("stdin-output")).unwrap(),
173     };
175     assert_same_directory(before, after, false);
178 /// Compress and decompress a directory with random content generated with create_random_files
180 /// This one runs only 50 times because there are only `.zip` and `.tar` to be tested, and
181 /// single-file formats testing is done in the other test
182 #[proptest(cases = 50)]
183 fn multiple_files(
184     ext: DirectoryExtension,
185     #[any(size_range(0..5).lift())] exts: Vec<FileExtension>,
186     #[strategy(0u8..4)] depth: u8,
187 ) {
188     let dir = tempdir().unwrap();
189     let dir = dir.path();
190     let before = &dir.join("before");
191     let before_dir = &before.join("dir");
192     fs::create_dir_all(before_dir).unwrap();
193     let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, exts)));
194     let after = &dir.join("after");
195     create_random_files(before_dir, depth, &mut SmallRng::from_entropy());
196     ouch!("-A", "c", before_dir, archive);
197     ouch!("-A", "d", archive, "-d", after);
198     assert_same_directory(before, after, !matches!(ext, DirectoryExtension::Zip));
201 #[cfg(feature = "unrar")]
202 #[test]
203 fn unpack_rar() -> Result<(), Box<dyn std::error::Error>> {
204     fn test_unpack_rar_single(input: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
205         let dir = tempdir()?;
206         let dirpath = dir.path();
207         let unpacked_path = &dirpath.join("testfile.txt");
208         ouch!("-A", "d", input, "-d", dirpath);
209         let content = fs::read_to_string(unpacked_path)?;
210         assert_eq!(content, "Testing 123\n");
212         Ok(())
213     }
215     let mut datadir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
216     datadir.push("tests/data");
217     ["testfile.rar3.rar.gz", "testfile.rar5.rar"]
218         .iter()
219         .try_for_each(|path| test_unpack_rar_single(&datadir.join(path)))?;
221     Ok(())
224 #[cfg(feature = "unrar")]
225 #[test]
226 fn unpack_rar_stdin() -> Result<(), Box<dyn std::error::Error>> {
227     fn test_unpack_rar_single(input: &std::path::Path, format: &str) -> Result<(), Box<dyn std::error::Error>> {
228         let dir = tempdir()?;
229         let dirpath = dir.path();
230         let unpacked_path = &dirpath.join("testfile.txt");
231         crate::utils::cargo_bin()
232             .args([
233                 "-A",
234                 "-y",
235                 "d",
236                 "-",
237                 "-d",
238                 dirpath.to_str().unwrap(),
239                 "--format",
240                 format,
241             ])
242             .pipe_stdin(input)
243             .unwrap()
244             .assert()
245             .success();
246         let content = fs::read_to_string(unpacked_path)?;
247         assert_eq!(content, "Testing 123\n");
249         Ok(())
250     }
252     let mut datadir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
253     datadir.push("tests/data");
254     [("testfile.rar3.rar.gz", "rar.gz"), ("testfile.rar5.rar", "rar")]
255         .iter()
256         .try_for_each(|(path, format)| test_unpack_rar_single(&datadir.join(path), format))?;
258     Ok(())