Skip to main content

Merge pull request #27 from alexwlchan/better-tests

ID
bcd6183
date
2025-01-06 21:25:21+00:00
author
Alex Chan <alex@alexwlchan.net>
parents
00a111f, 4a303ce
message
Merge pull request #27 from alexwlchan/better-tests

Convert all the tests to use the new `assert_cmd` helpers
changed files
3 files, 109 additions, 91 deletions

Changed files

Cargo.lock (36743) → Cargo.lock (37232)

diff --git a/Cargo.lock b/Cargo.lock
index 0e65442..4e9773f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -306,7 +306,7 @@ dependencies = [
  "assert_cmd",
  "clap",
  "image",
- "regex",
+ "predicates",
  "tempfile",
 ]
 
@@ -417,6 +417,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
 name = "flume"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -667,6 +676,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
 
 [[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
 name = "num-bigint"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -764,7 +779,10 @@ checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
 dependencies = [
  "anstyle",
  "difflib",
+ "float-cmp",
+ "normalize-line-endings",
  "predicates-core",
+ "regex",
 ]
 
 [[package]]

Cargo.toml (206) → Cargo.toml (206)

diff --git a/Cargo.toml b/Cargo.toml
index fbb05cd..fc38671 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,5 +7,5 @@ edition = "2021"
 assert_cmd = "2.0.14"
 clap = { version = "4", features = ["derive"] }
 image = "0.25.5"
-regex = "1.11.1"
+predicates = "3"
 tempfile = "3"

src/main.rs (6911) → src/main.rs (6550)

diff --git a/src/main.rs b/src/main.rs
index 5f39d46..9a71c4c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -58,75 +58,85 @@ fn main() {
 mod test_cli {
     use std::path::PathBuf;
 
-    use regex::Regex;
+    use predicates::prelude::*;
 
-    use crate::test_utils::{get_dimensions, get_failure, get_success};
+    use crate::run_command;
+    use crate::test_utils::get_dimensions;
 
     #[test]
     fn it_creates_a_thumbnail_with_max_width() {
-        let output = get_success(&["src/tests/red.png", "--width=50", "--out-dir=/tmp"]);
+        let result = run_command!("src/tests/red.png", "--width=50", "--out-dir=/tmp");
+
+        result.success().stdout("/tmp/red.png").stderr("");
 
-        assert_eq!(output.exit_code, 0);
-        assert_eq!(output.stdout, "/tmp/red.png");
-        assert_eq!(output.stderr, "");
         assert_eq!(get_dimensions(&PathBuf::from("/tmp/red.png")), (50, 100));
     }
 
     #[test]
     fn it_creates_a_thumbnail_with_max_height() {
-        let output = get_success(&["src/tests/noise.jpg", "--height=128", "--out-dir=/tmp"]);
+        let result = run_command!("src/tests/noise.jpg", "--height=128", "--out-dir=/tmp");
+
+        result.success().stdout("/tmp/noise.jpg").stderr("");
 
-        assert_eq!(output.exit_code, 0);
-        assert_eq!(output.stdout, "/tmp/noise.jpg");
-        assert_eq!(output.stderr, "");
         assert_eq!(get_dimensions(&PathBuf::from("/tmp/noise.jpg")), (64, 128));
     }
 
     #[test]
     fn it_fails_if_you_pass_width_and_height() {
-        let output = get_failure(&[
+        let result = run_command!(
             "src/tests/red.png",
             "--width=100",
             "--height=100",
             "--out-dir=/tmp",
-        ]);
+        );
 
-        let re =
-            Regex::new(r"the argument '--width <WIDTH>' cannot be used with '--height <HEIGHT>'")
-                .unwrap();
-        assert!(re.is_match(&output.stderr));
+        let is_invalid_args_err = predicate::str::is_match(
+            r"the argument '--width <WIDTH>' cannot be used with '--height <HEIGHT>'",
+        )
+        .unwrap();
 
-        assert_eq!(output.exit_code, 2);
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(2)
+            .stdout("")
+            .stderr(is_invalid_args_err);
     }
 
     #[test]
     fn it_fails_if_you_pass_neither_width_nor_height() {
-        let output = get_failure(&["src/tests/red.png", "--out-dir=/tmp"]);
+        let result = run_command!("src/tests/red.png", "--out-dir=/tmp");
 
-        let re = Regex::new(r"the following required arguments were not provided:").unwrap();
-        assert!(re.is_match(&output.stderr));
+        let is_missing_args_err =
+            predicate::str::is_match(r"the following required arguments were not provided:")
+                .unwrap();
 
-        assert_eq!(output.exit_code, 2);
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(2)
+            .stdout("")
+            .stderr(is_missing_args_err);
     }
 
     #[test]
     fn it_fails_if_you_pass_a_non_existent_file() {
-        let output = get_failure(&["doesnotexist.txt", "--width=50", "--out-dir=/tmp"]);
+        let result = run_command!("doesnotexist.txt", "--width=50", "--out-dir=/tmp");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stderr, "No such file or directory (os error 2)\n");
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("No such file or directory (os error 2)\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_a_non_image() {
-        let output = get_failure(&["Cargo.toml", "--width=50", "--out-dir=/tmp"]);
+        let result = run_command!("Cargo.toml", "--width=50", "--out-dir=/tmp");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stderr, "The image format could not be determined\n");
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("The image format could not be determined\n");
     }
 
     // TODO: Improve this error message.
@@ -135,57 +145,52 @@ mod test_cli {
     // we'd return a more meaningful error message in this case.
     #[test]
     fn it_fails_if_out_dir_is_a_file() {
-        let output = get_failure(&["src/images/noise.jpg", "--width=50", "--out-dir=README.md"]);
+        let result = run_command!("src/images/noise.jpg", "--width=50", "--out-dir=README.md");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stderr, "File exists (os error 17)\n");
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("File exists (os error 17)\n");
     }
 
     #[test]
     fn it_fails_if_you_try_to_overwrite_the_original_file() {
-        let output = get_failure(&["src/images/noise.jpg", "--width=50", "--out-dir=src/images"]);
+        let result = run_command!("src/images/noise.jpg", "--width=50", "--out-dir=src/images");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(
-            output.stderr,
-            "Cannot write thumbnail to the same directory as the original image\n"
-        );
-        assert_eq!(output.stdout, "");
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Cannot write thumbnail to the same directory as the original image\n");
     }
 
     #[test]
     fn it_prints_the_version() {
-        let output = get_success(&["--version"]);
-
-        let re = Regex::new(r"^create_thumbnail [0-9]+\.[0-9]+\.[0-9]+\n$").unwrap();
+        let result = run_command!("--version");
 
-        assert!(re.is_match(&output.stdout));
+        // Match strings like `create_thumbnail 1.2.3`
+        let is_version_string =
+            predicate::str::is_match(r"^create_thumbnail [0-9]+\.[0-9]+\.[0-9]+\n$").unwrap();
 
-        assert_eq!(output.exit_code, 0);
-        assert_eq!(output.stderr, "");
+        result.success().stdout(is_version_string).stderr("");
     }
 
     #[test]
     fn it_prints_the_help() {
-        let output = get_success(&["--help"]);
-
-        let re = Regex::new(r"create_thumbnail --out-dir").unwrap();
+        let result = run_command!("--help");
 
-        assert!(re.is_match(&output.stdout));
+        // Match strings like `dominant_colours 1.2.3`
+        let is_help_text = predicate::str::is_match(r"create_thumbnail --out-dir").unwrap();
 
-        assert_eq!(output.exit_code, 0);
-        assert_eq!(output.stderr, "");
+        result.success().stdout(is_help_text).stderr("");
     }
 }
 
 #[cfg(test)]
 pub mod test_utils {
     use std::path::PathBuf;
-    use std::str;
 
-    use assert_cmd::assert::OutputAssertExt;
-    use assert_cmd::Command;
     use image::GenericImageView;
 
     /// Return a path to a temporary directory to use for testing.
@@ -203,38 +208,33 @@ pub mod test_utils {
 
         img.dimensions()
     }
+}
 
-    pub struct DcOutput {
-        pub exit_code: i32,
-        pub stdout: String,
-        pub stderr: String,
-    }
-
-    pub fn get_success(args: &[&str]) -> DcOutput {
-        let mut cmd = Command::cargo_bin("create_thumbnail").unwrap();
-        let output = cmd
-            .args(args)
-            .unwrap()
-            .assert()
-            .success()
-            .get_output()
-            .to_owned();
-
-        DcOutput {
-            exit_code: output.status.code().unwrap(),
-            stdout: str::from_utf8(&output.stdout).unwrap().to_owned(),
-            stderr: str::from_utf8(&output.stderr).unwrap().to_owned(),
-        }
-    }
-
-    pub fn get_failure(args: &[&str]) -> DcOutput {
-        let mut cmd = Command::cargo_bin("create_thumbnail").unwrap();
-        let output = cmd.args(args).unwrap_err().as_output().unwrap().to_owned();
-
-        DcOutput {
-            exit_code: output.status.code().unwrap(),
-            stdout: str::from_utf8(&output.stdout).unwrap().to_owned(),
-            stderr: str::from_utf8(&output.stderr).unwrap().to_owned(),
-        }
+#[cfg(test)]
+#[macro_use]
+mod test_helpers {
+    /// Run this command-line tool with zero or more arguments:
+    ///
+    ///     run_command!();
+    ///     run_command!("shape.png");
+    ///     run_command!("shape.png", "--sides=4", "--colour=red");
+    ///
+    /// This returns an `assert_cmd::assert::Assert` that will allow
+    /// you to make assertions about the output.
+    /// See https://docs.rs/assert_cmd/latest/assert_cmd/assert/struct.Assert.html
+    #[macro_export]
+    macro_rules! run_command {
+        () => {
+            assert_cmd::Command::cargo_bin(env!("CARGO_PKG_NAME"))
+                       .unwrap()
+                       .assert()
+        };
+
+        ($($arg:expr),+ $(,)?) => {{
+            assert_cmd::Command::cargo_bin(env!("CARGO_PKG_NAME"))
+                       .unwrap()
+                       .args(&[$($arg),*])
+                       .assert()
+        }};
     }
 }