Skip to main content

Merge pull request #76 from alexwlchan/better-tests

ID
0be1b44
date
2025-01-06 21:07:42+00:00
author
Alex Chan <alex@alexwlchan.net>
parents
242946a, 29d886f
message
Merge pull request #76 from alexwlchan/better-tests

Rewrite the tests to use my new style of assert_cmd tests
changed files
6 files, 170 additions, 167 deletions

Changed files

Cargo.lock (17587) → Cargo.lock (18076)

diff --git a/Cargo.lock b/Cargo.lock
index 695c147..6bb1824 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -221,7 +221,7 @@ dependencies = [
  "image-webp",
  "kmeans_colors",
  "palette",
- "regex",
+ "predicates",
 ]
 
 [[package]]
@@ -241,6 +241,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 = "getrandom"
 version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -341,6 +350,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
 name = "num-traits"
 version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -398,7 +413,10 @@ checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
 dependencies = [
  "anstyle",
  "difflib",
+ "float-cmp",
+ "normalize-line-endings",
  "predicates-core",
+ "regex",
 ]
 
 [[package]]

Cargo.toml (500) → Cargo.toml (500)

diff --git a/Cargo.toml b/Cargo.toml
index 818dd26..334ca45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2018"
 assert_cmd = "2.0.16"
 clap = { version = "4.5.23", features = ["derive"] }
 image-webp = "0.2.0"
-regex = "1.11.1"
+predicates = "3"
 
 [dependencies.kmeans_colors]
 version = "0.6.0"

src/main.rs (10964) → src/main.rs (10515)

diff --git a/src/main.rs b/src/main.rs
index 899d235..d42d809 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -81,302 +81,287 @@ fn main() {
 
 #[cfg(test)]
 mod tests {
-    use std::str;
+    use predicates::prelude::*;
 
-    use assert_cmd::assert::OutputAssertExt;
-    use assert_cmd::Command;
-    use regex::Regex;
+    use crate::run_command;
 
     // Note: for the purposes of these tests, I mostly trust the k-means code
     // provided by the external library.
 
     #[test]
     fn it_prints_the_colour() {
-        let output = get_success(&["./src/tests/red.png", "--max-colours=1"]);
-
-        assert_eq!(output.exit_code, 0);
+        let result = run_command!("./src/tests/red.png");
 
-        assert!(
-            output.stdout == "#ff0000\n" || output.stdout == "#fe0000\n",
-            "stdout = {:?}",
-            output.stdout
-        );
-
-        assert_eq!(output.stderr, "");
+        result.success().stdout("#fe0000\n").stderr("");
     }
 
     #[test]
     fn it_can_look_at_png_images() {
-        let output = get_success(&["./src/tests/red.png", "--max-colours=1"]);
-        assert_eq!(output.exit_code, 0);
+        let result = run_command!("./src/tests/red.png");
+
+        result.success().stdout("#fe0000\n").stderr("");
     }
 
     #[test]
     fn it_can_look_at_jpeg_images() {
-        let output = get_success(&["./src/tests/noise.jpg", "--max-colours=1"]);
-        assert_eq!(output.exit_code, 0);
+        let result = run_command!("./src/tests/black.jpg");
+
+        result.success().stdout("#000000\n").stderr("");
     }
 
     #[test]
     fn it_can_look_at_static_gif_images() {
-        let output = get_success(&["./src/tests/yellow.gif", "--max-colours=1"]);
-        assert_eq!(output.exit_code, 0);
+        let result = run_command!("./src/tests/yellow.gif");
+
+        result.success().stdout("#fffb00\n").stderr("");
     }
 
     #[test]
     fn it_can_look_at_tiff_images() {
-        let output = get_success(&["./src/tests/green.tiff", "--max-colours=1"]);
-        assert_eq!(output.exit_code, 0);
+        let result = run_command!("./src/tests/green.tiff");
+
+        result.success().stdout("#04ff02\n").stderr("");
     }
 
     #[test]
     fn it_omits_the_escape_codes_with_no_palette() {
-        let output = get_success(&["./src/tests/red.png", "--max-colours=1", "--no-palette"]);
-
-        assert_eq!(output.exit_code, 0);
-
-        assert!(
-            output.stdout == "#ff0000\n" || output.stdout == "#fe0000\n",
-            "stdout = {:?}",
-            output.stdout
-        );
+        let result = run_command!("./src/tests/red.png", "--max-colours=1");
 
-        assert_eq!(output.stderr, "");
+        result.success().stdout("#fe0000\n").stderr("");
     }
 
     #[test]
     fn it_defaults_to_five_colours() {
-        let output = get_success(&["./src/tests/noise.jpg"]);
+        let result = run_command!("./src/tests/noise.jpg");
 
-        assert_eq!(
-            output.stdout.matches("\n").count(),
-            5,
-            "stdout = {:?}",
-            output.stdout
-        );
+        let has_five_lines = predicate::str::is_match(r"^(#[a-f0-9]{6}\n){5}$").unwrap();
+
+        result.success().stdout(has_five_lines).stderr("");
     }
 
     #[test]
     fn it_lets_you_choose_the_max_colours() {
-        let output = get_success(&["./src/tests/noise.jpg", "--max-colours=8"]);
+        let result = run_command!("./src/tests/noise.jpg", "--max-colours=8");
 
-        assert_eq!(
-            output.stdout.matches("\n").count(),
-            8,
-            "stdout = {:?}",
-            output.stdout
-        );
+        let has_eight_lines = predicate::str::is_match(r"^(#[a-f0-9]{6}\n){8}$").unwrap();
+
+        result.success().stdout(has_eight_lines).stderr("");
     }
 
     // The image created in the next two tests was created with the
     // following command:
     //
-    //      convert -delay 200 -loop 10 -dispose previous red.png blue.png red.png blue.png red.png blue.png red.png blue.png animated_squares.gif
+    //      convert \
+    //        -delay 200 \
+    //        -loop 10 \
+    //        -dispose previous \
+    //        red.png blue.png \
+    //        red.png blue.png \
+    //        red.png blue.png \
+    //        red.png blue.png \
+    //        animated_squares.gif
     //
+    // It creates an animated GIF that has alternating red/blue frames.
 
     #[test]
     fn it_looks_at_multiple_frames_in_an_animated_gif() {
-        let output = get_success(&["./src/tests/animated_squares.gif", "--no-palette"]);
+        let result = run_command!("./src/tests/animated_squares.gif");
 
-        assert_eq!(
-            output.stdout, "#0200ff\n#ff0000\n",
-            "stdout = {:?}",
-            output.stdout
-        );
+        result.success().stdout("#0200ff\n#ff0000\n").stderr("");
     }
 
     #[test]
     fn it_looks_at_multiple_frames_in_an_animated_gif_uppercase() {
-        let output = get_success(&["./src/tests/animated_upper_squares.GIF"]);
+        let result = run_command!("./src/tests/animated_upper_squares.GIF");
 
-        assert_eq!(
-            output.stdout.matches("\n").count(),
-            2,
-            "stdout = {:?}",
-            output.stdout
-        );
+        result.success().stdout("#0200ff\n#ff0000\n").stderr("");
     }
 
+    // This is an animated WebP that has alternating red/blue frames.
+    //
+    // It needs to look at multiple frames to see both colours.
     #[test]
     fn it_looks_at_multiple_frames_in_an_animated_webp() {
-        let output = get_success(&["./src/tests/animated_squares.webp", "--no-palette"]);
+        let result = run_command!("./src/tests/animated_squares.webp");
 
-        assert_eq!(
-            output.stdout, "#0200ff\n#ff0100\n#ff0002\n",
-            "stdout = {:?}",
-            output.stdout
-        );
+        result
+            .success()
+            .stdout("#0200ff\n#ff0100\n#ff0002\n")
+            .stderr("");
     }
 
     #[test]
     fn it_fails_if_you_pass_an_invalid_max_colours() {
-        let output = get_failure(&["./src/tests/red.png", "--max-colours=NaN"]);
+        let result = run_command!("./src/tests/red.png", "--max-colours=NaN");
 
-        assert_eq!(output.exit_code, 2);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "error: invalid value 'NaN' for '--max-colours <MAX_COLOURS>': invalid digit found in string\n\nFor more information, try '--help'.\n"
-        );
+        result
+            .failure()
+            .code(2)
+            .stdout("")
+            .stderr("error: invalid value 'NaN' for '--max-colours <MAX_COLOURS>': invalid digit found in string\n\nFor more information, try '--help'.\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_an_nonexistent_file() {
-        let output = get_failure(&["./doesnotexist.jpg"]);
+        let result = run_command!("./doesnotexist.jpg");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(output.stderr, "No such file or directory (os error 2)\n");
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("No such file or directory (os error 2)\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_an_nonexistent_gif() {
-        let output = get_failure(&["./doesnotexist.gif"]);
+        let result = run_command!("./doesnotexist.gif");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(output.stderr, "No such file or directory (os error 2)\n");
+        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_file() {
-        let output = get_failure(&["./README.md"]);
+        let result = run_command!("./README.md");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Unable to determine image format from file extension\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Unable to determine image format from file extension\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_an_unsupported_image_format() {
-        let output = get_failure(&["./src/tests/orange.heic"]);
+        let result = run_command!("./src/tests/orange.heic");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Unable to determine image format from file extension\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Unable to determine image format from file extension\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_a_malformed_image() {
-        let output = get_failure(&["./src/tests/malformed.txt.png"]);
+        let result = run_command!("./src/tests/malformed.txt.png");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Format error decoding Png: Invalid PNG signature.\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Format error decoding Png: Invalid PNG signature.\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_a_malformed_gif() {
-        let output = get_failure(&["./src/tests/malformed.txt.gif"]);
+        let result = run_command!("./src/tests/malformed.txt.gif");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Format error decoding Gif: malformed GIF header\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Format error decoding Gif: malformed GIF header\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_a_malformed_webp() {
-        let output = get_failure(&["./src/tests/malformed.txt.webp"]);
+        let result = run_command!("./src/tests/malformed.txt.webp");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Format error decoding WebP: Invalid Chunk header: [52, 49, 46, 46]\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Format error decoding WebP: Invalid Chunk header: [52, 49, 46, 46]\n");
     }
 
     #[test]
     fn it_fails_if_you_pass_a_path_without_a_file_extension() {
-        let output = get_failure(&["./src/tests/noextension"]);
+        let result = run_command!("./src/tests/noextension");
 
-        assert_eq!(output.exit_code, 1);
-        assert_eq!(output.stdout, "");
-        assert_eq!(
-            output.stderr,
-            "Path has no file extension, so could not determine image format\n"
-        );
+        result
+            .failure()
+            .code(1)
+            .stdout("")
+            .stderr("Path has no file extension, so could not determine image format\n");
     }
 
     #[test]
     fn it_chooses_the_right_color_for_a_dark_background() {
-        let output = get_success(&[
+        let result = run_command!(
             "src/tests/stripes.png",
             "--max-colours=5",
             "--best-against-bg=#222",
-            "--no-palette",
-        ]);
+        );
 
-        assert_eq!(output.stdout, "#d4fb79\n");
+        result.success().stdout("#d4fb79\n").stderr("");
     }
 
     #[test]
     fn it_chooses_the_right_color_for_a_light_background() {
-        let output = get_success(&[
+        let result = run_command!(
             "src/tests/stripes.png",
             "--max-colours=5",
             "--best-against-bg=#fff",
-            "--no-palette",
-        ]);
+        );
 
-        assert_eq!(output.stdout, "#693900\n");
+        result.success().stdout("#693900\n").stderr("");
     }
 
     #[test]
     fn it_prints_the_version() {
-        let output = get_success(&["--version"]);
-
-        let re = Regex::new(r"^dominant_colours [0-9]+\.[0-9]+\.[0-9]+\n$").unwrap();
+        let result = run_command!("--version");
 
-        assert!(re.is_match(&output.stdout));
+        // Match strings like `dominant_colours 1.2.3`
+        let is_version_string =
+            predicate::str::is_match(r"^dominant_colours [0-9]+\.[0-9]+\.[0-9]+\n$").unwrap();
 
-        assert_eq!(output.exit_code, 0);
-        assert_eq!(output.stderr, "");
-    }
-
-    struct DcOutput {
-        exit_code: i32,
-        stdout: String,
-        stderr: String,
+        result.success().stdout(is_version_string).stderr("");
     }
+}
 
-    fn get_success(args: &[&str]) -> DcOutput {
-        let mut cmd = Command::cargo_bin("dominant_colours").unwrap();
-        let output = cmd
-            .args(args)
-            .unwrap()
-            .assert()
-            .success()
-            .get_output()
-            .to_owned();
+#[cfg(test)]
+#[macro_use]
+mod test_helpers {
+    use assert_cmd::assert::Assert;
+    use assert_cmd::Command;
 
-        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(),
-        }
+    /// Run this command-line tool with zero or more arguments:
+    ///
+    ///     run_command!();
+    ///     run_command!("test");
+    ///     run_command!("test", "--nocapture", "--ignored");
+    ///
+    /// 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 {
+        // Match zero arguments
+        () => {
+            $crate::test_helpers::run_command(&[])
+        };
+
+        // Match one or more arguments
+        ($($arg:expr),+ $(,)?) => {{
+            let args = &[$($arg),*];
+            $crate::test_helpers::run_command(args)
+        }};
     }
 
-    fn get_failure(args: &[&str]) -> DcOutput {
-        let mut cmd = Command::cargo_bin("dominant_colours").unwrap();
-        let output = cmd.args(args).unwrap_err().as_output().unwrap().to_owned();
+    /// Run this command-line tool with the given arguments.
+    ///
+    /// 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
+    pub fn run_command(args: &[&str]) -> Assert {
+        let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
 
-        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(),
-        }
+        let assert = cmd.args(args).assert();
+
+        assert
     }
 }

src/tests/black.jpg (0) → src/tests/black.jpg (843)

diff --git a/src/tests/black.jpg b/src/tests/black.jpg
new file mode 100644
index 0000000..1ef8891
Binary files /dev/null and b/src/tests/black.jpg differ

src/tests/red.png (725) → src/tests/red.png (1026)

diff --git a/src/tests/red.png b/src/tests/red.png
index 80aea6e..6658915 100644
Binary files a/src/tests/red.png and b/src/tests/red.png differ

src/tests/yellow.gif (77) → src/tests/yellow.gif (77)

diff --git a/src/tests/yellow.gif b/src/tests/yellow.gif
index d036bdd..c851c7d 100644
Binary files a/src/tests/yellow.gif and b/src/tests/yellow.gif differ