Skip to content

Commit b5ed34b

Browse files
authored
Merge pull request #46 from KSXGitHub/integration-tests
Integration tests
2 parents 81ff0f1 + f9ca0df commit b5ed34b

File tree

10 files changed

+1016
-1
lines changed

10 files changed

+1016
-1
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ features = ["derive"]
8282

8383
[dev-dependencies]
8484
build-fs-tree = "^0.3.0"
85+
command-extra = "^1.0.0"
8586
maplit = "^1.0.2"
8687
pretty_assertions = "^0.7.2"
8788
rand = "^0.8.4"

src/data_tree/getters.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ impl<Name, Data: Size> DataTree<Name, Data> {
77
&self.name
88
}
99

10+
/// Get mutable reference to name.
11+
pub fn name_mut(&mut self) -> &mut Name {
12+
&mut self.name
13+
}
14+
1015
/// Extract total disk usage
1116
pub fn data(&self) -> Data {
1217
self.data

src/data_tree/retain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ where
2323

2424
/// Recursively cull all descendants whose data are too small relative to root.
2525
#[cfg(feature = "cli")]
26-
pub(crate) fn par_cull_insignificant_data(&mut self, min_ratio: f32)
26+
pub fn par_cull_insignificant_data(&mut self, min_ratio: f32)
2727
where
2828
Data: Into<u64>,
2929
{

tests/_utils.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use build_fs_tree::{dir, file, Build, MergeableFileSystemTree};
2+
use command_extra::CommandExtra;
23
use derive_more::{AsRef, Deref};
34
use parallel_disk_usage::{
45
data_tree::{DataTree, DataTreeReflection},
@@ -16,6 +17,7 @@ use std::{
1617
fs::{create_dir, metadata, remove_dir_all, Metadata},
1718
io::Error,
1819
path::{Path, PathBuf},
20+
process::{Command, Output},
1921
};
2022

2123
/// Representation of a temporary filesystem item.
@@ -42,6 +44,7 @@ impl Temp {
4244
}
4345

4446
impl Drop for Temp {
47+
/// Delete the created temporary directory.
4548
fn drop(&mut self) {
4649
let path = &self.0;
4750
if let Err(error) = remove_dir_all(path) {
@@ -55,6 +58,7 @@ impl Drop for Temp {
5558
pub struct SampleWorkspace(Temp);
5659

5760
impl Default for SampleWorkspace {
61+
/// Set up a temporary directory for tests.
5862
fn default() -> Self {
5963
let temp = Temp::new_dir().expect("create working directory for sample workspace");
6064

@@ -202,3 +206,116 @@ where
202206
}),
203207
);
204208
}
209+
210+
/// Path to the `pdu` executable
211+
pub const PDU: &str = env!("CARGO_BIN_EXE_pdu");
212+
213+
/// Representation of a `pdu` command.
214+
#[derive(Debug, Default, Clone)]
215+
pub struct CommandRepresentation<'a> {
216+
args: Vec<&'a str>,
217+
}
218+
219+
impl<'a> CommandRepresentation<'a> {
220+
/// Add an argument.
221+
pub fn arg(mut self, arg: &'a str) -> Self {
222+
self.args.push(arg);
223+
self
224+
}
225+
}
226+
227+
/// List of `pdu` commands.
228+
#[derive(Debug, Clone, AsRef, Deref)]
229+
pub struct CommandList<'a>(Vec<CommandRepresentation<'a>>);
230+
231+
impl<'a> Default for CommandList<'a> {
232+
/// Initialize a list with one `pdu` command.
233+
fn default() -> Self {
234+
CommandRepresentation::default()
235+
.pipe(|x| vec![x])
236+
.pipe(CommandList)
237+
}
238+
}
239+
240+
impl<'a> CommandList<'a> {
241+
/// Duplicate the list with a flag argument.
242+
///
243+
/// The resulting list would include the original list with the flag
244+
/// followed by the original list without the flag.
245+
pub fn flag_matrix(self, name: &'a str) -> Self {
246+
Self::assert_flag(name);
247+
let CommandList(list) = self;
248+
list.clone()
249+
.into_iter()
250+
.map(|cmd| cmd.arg(name))
251+
.chain(list)
252+
.collect::<Vec<_>>()
253+
.pipe(CommandList)
254+
}
255+
256+
/// Duplicate the list with one or many option argument(s).
257+
///
258+
/// The resulting list would include the original list with the option(s)
259+
/// followed by the original list without the option(s).
260+
pub fn option_matrix<const LEN: usize>(self, name: &'a str, values: [&'a str; LEN]) -> Self {
261+
Self::assert_flag(name);
262+
let CommandList(tail) = self;
263+
let mut head: Vec<_> = values
264+
.iter()
265+
.copied()
266+
.flat_map(|value| {
267+
tail.clone()
268+
.into_iter()
269+
.map(move |cmd| cmd.arg(name).arg(value))
270+
})
271+
.collect();
272+
head.extend(tail);
273+
CommandList(head)
274+
}
275+
276+
/// Create a list of `pdu` [command](Command).
277+
pub fn commands(&'a self) -> impl Iterator<Item = Command> + 'a {
278+
self.iter()
279+
.map(|cmd| Command::new(PDU).with_args(&cmd.args))
280+
}
281+
282+
/// Make sure a flag name has valid syntax.
283+
fn assert_flag(name: &str) {
284+
match name.len() {
285+
0 | 1 => panic!("{:?} is not a valid flag", name),
286+
2 => assert!(name.starts_with('-'), "{:?} is not a valid flag", name),
287+
_ => assert!(name.starts_with("--"), "{:?} is not a valid flag", name),
288+
}
289+
}
290+
}
291+
292+
/// Make sure that status code is 0, print stderr if it's not empty,
293+
/// and turn stdin into a string.
294+
pub fn stdout_text(
295+
Output {
296+
status,
297+
stdout,
298+
stderr,
299+
}: Output,
300+
) -> String {
301+
inspect_stderr(&stderr);
302+
assert!(
303+
status.success(),
304+
"progress exits with non-zero status: {:?}",
305+
status
306+
);
307+
stdout
308+
.pipe(String::from_utf8)
309+
.expect("parse stdout as UTF-8")
310+
.trim_end()
311+
.to_string()
312+
}
313+
314+
/// Print stderr if it's not empty.
315+
pub fn inspect_stderr(stderr: &[u8]) {
316+
let text = String::from_utf8_lossy(stderr);
317+
let text = text.trim();
318+
if !text.is_empty() {
319+
eprintln!("STDERR:\n{}\n", text);
320+
}
321+
}

tests/args_fraction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![cfg(feature = "cli")]
2+
23
use parallel_disk_usage::args::fraction::{ConversionError::*, Fraction, FromStrError::*};
34
use pretty_assertions::assert_eq;
45

tests/cli_errors.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![cfg(feature = "cli")]
2+
3+
pub mod _utils;
4+
pub use _utils::*;
5+
6+
use command_extra::CommandExtra;
7+
use pipe_trait::Pipe;
8+
use pretty_assertions::assert_eq;
9+
use std::process::{Command, Output, Stdio};
10+
11+
fn stdio(command: Command) -> Command {
12+
command
13+
.with_stdin(Stdio::null())
14+
.with_stdout(Stdio::piped())
15+
.with_stderr(Stdio::piped())
16+
}
17+
18+
#[test]
19+
fn min_ratio_1() {
20+
let workspace = SampleWorkspace::default();
21+
let Output {
22+
status,
23+
stdout,
24+
stderr,
25+
} = Command::new(PDU)
26+
.with_current_dir(workspace.as_path())
27+
.with_arg("--min-ratio=1")
28+
.pipe(stdio)
29+
.output()
30+
.expect("spawn command");
31+
let stderr = String::from_utf8(stderr).expect("parse stderr as UTF-8");
32+
let stderr = stderr.trim();
33+
dbg!(&status);
34+
eprintln!("STDERR:\n{}\n", stderr);
35+
assert!(!status.success());
36+
assert_eq!(
37+
stderr,
38+
"error: Invalid value for '--min-ratio <min-ratio>': greater than or equal to 1"
39+
);
40+
assert_eq!(&stdout, &[] as &[u8]);
41+
}
42+
43+
#[test]
44+
fn max_depth_0() {
45+
let workspace = SampleWorkspace::default();
46+
let Output {
47+
status,
48+
stdout,
49+
stderr,
50+
} = Command::new(PDU)
51+
.with_current_dir(workspace.as_path())
52+
.with_arg("--max-depth=0")
53+
.pipe(stdio)
54+
.output()
55+
.expect("spawn command");
56+
let stderr = String::from_utf8(stderr).expect("parse stderr as UTF-8");
57+
let stderr = stderr.trim();
58+
dbg!(&status);
59+
eprintln!("STDERR:\n{}\n", stderr);
60+
assert!(!status.success());
61+
assert_eq!(
62+
stderr,
63+
"error: Invalid value for '--max-depth <max-depth>': number would be zero for non-zero type"
64+
);
65+
assert_eq!(&stdout, &[] as &[u8]);
66+
}

tests/flag_combinations.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![cfg(feature = "cli")]
2+
3+
pub mod _utils;
4+
pub use _utils::*;
5+
6+
use command_extra::CommandExtra;
7+
use std::process::Stdio;
8+
9+
/// There are branches of similar shapes in `/src/app.rs` that call
10+
/// the `sub!` macro. This test suite is to ensure that no combination
11+
/// of variant is left out by programmer's mistake.
12+
#[test]
13+
fn flag_combinations() {
14+
#[cfg(unix)]
15+
let quantity = ["len", "blksize", "blocks"];
16+
#[cfg(windows)]
17+
let quantity = ["len"];
18+
19+
let list = CommandList::default()
20+
.option_matrix("--quantity", quantity)
21+
.flag_matrix("--progress");
22+
23+
for command in list.commands() {
24+
dbg!(&command);
25+
let workspace = SampleWorkspace::default();
26+
dbg!(workspace.as_path());
27+
let output = command
28+
.with_current_dir(workspace.as_path())
29+
.with_stdin(Stdio::null())
30+
.with_stdout(Stdio::null())
31+
.with_stderr(Stdio::piped())
32+
.output()
33+
.expect("execute command");
34+
inspect_stderr(&output.stderr);
35+
let status = output.status;
36+
assert!(status.success(), "status: {:?}", status.code())
37+
}
38+
}

0 commit comments

Comments
 (0)