From 11c253a213d94e7ce41e7de8a9b73d189236d52f Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Mon, 7 Oct 2019 00:32:51 -0700 Subject: [PATCH] Add `unindent()` for nicer integration test strings (#481) Adds an `unindent()` function that strips common leading indentation from strings, and apply it to integration test case strings, so that they can be written in a more readable style. --- tests/integration.rs | 164 ++++++++++++++++++---------------- tests/interrupts.rs | 4 +- tests/invocation_directory.rs | 4 +- tests/tempdir.rs | 7 -- tests/testing/mod.rs | 142 +++++++++++++++++++++++++++++ tests/working_directory.rs | 4 +- 6 files changed, 235 insertions(+), 90 deletions(-) delete mode 100644 tests/tempdir.rs create mode 100644 tests/testing/mod.rs diff --git a/tests/integration.rs b/tests/integration.rs index 9352dd8..0123e8a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,4 +1,4 @@ -mod tempdir; +mod testing; use std::{ env, fs, @@ -11,13 +11,13 @@ use std::{ use executable_path::executable_path; use libc::{EXIT_FAILURE, EXIT_SUCCESS}; use pretty_assertions::assert_eq; -use tempdir::tempdir; +use testing::{tempdir, unindent}; /// Instantiate an integration test. macro_rules! integration_test { ( name: $name:ident, - justfile: $justfile:tt, + justfile: $justfile:expr, $(args: ($($arg:tt)*),)? $(stdin: $stdin:expr,)? $(stdout: $stdout:expr,)? @@ -52,8 +52,8 @@ impl<'a> Default for Test<'a> { fn default() -> Test<'a> { Test { justfile: "", - stdin: "", args: &[], + stdin: "", stdout: "", stderr: "", status: EXIT_SUCCESS, @@ -65,9 +65,13 @@ impl<'a> Test<'a> { fn run(self) { let tmp = tempdir(); + let justfile = unindent(self.justfile); + let stdout = unindent(self.stdout); + let stderr = unindent(self.stderr); + let mut justfile_path = tmp.path().to_path_buf(); justfile_path.push("justfile"); - fs::write(justfile_path, self.justfile).unwrap(); + fs::write(justfile_path, justfile).unwrap(); let mut dotenv_path = tmp.path().to_path_buf(); dotenv_path.push(".env"); @@ -103,8 +107,8 @@ impl<'a> Test<'a> { let want = Output { status: self.status, - stdout: self.stdout, - stderr: self.stderr, + stdout: &stdout, + stderr: &stderr, }; assert_eq!(have, want, "bad output"); @@ -160,42 +164,51 @@ fn test_round_trip(tmpdir: &Path) { integration_test! { name: alias_listing, - justfile: "foo:\n echo foo\nalias f := foo", + justfile: " + foo: + echo foo + + alias f := foo + ", args: ("--list"), - stdout: "Available recipes: - foo - f # alias for `foo` -", + stdout: " + Available recipes: + foo + f # alias for `foo` + ", } integration_test! { name: alias_listing_multiple_aliases, justfile: "foo:\n echo foo\nalias f := foo\nalias fo := foo", args: ("--list"), - stdout: "Available recipes: - foo - f # alias for `foo` - fo # alias for `foo` -", + stdout: " + Available recipes: + foo + f # alias for `foo` + fo # alias for `foo` + ", } integration_test! { name: alias_listing_parameters, justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias f := foo", args: ("--list"), - stdout: "Available recipes: - foo PARAM='foo' - f PARAM='foo' # alias for `foo` -", + stdout: " + Available recipes: + foo PARAM='foo' + f PARAM='foo' # alias for `foo` + ", } integration_test! { name: alias_listing_private, justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias _f := foo", args: ("--list"), - stdout: "Available recipes: - foo PARAM='foo' -", + stdout: " + Available recipes: + foo PARAM='foo' + ", } integration_test! { @@ -462,10 +475,10 @@ backtick-fail: \techo {{`exit 1`}} ", stdout: "", - stderr: "error: Backtick failed with exit code 1 - | -3 | echo {{`exit 1`}} - | ^^^^^^^^ + stderr: " error: Backtick failed with exit code 1 + | + 3 | echo {{`exit 1`}} + | ^^^^^^^^ ", status: 1, } @@ -2105,62 +2118,59 @@ default a=`read A && echo $A` b=`read B && echo $B`: } integration_test! { - name: equals_deprecated_assignment, - justfile: " + name: equals_deprecated_assignment, + justfile: " + foo = 'bar' -foo = 'bar' - -default: - echo {{foo}} - -", - stdout: "bar\n", - stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` -Please see this issue for more details: https://github.com/casey/just/issues/379 - | -3 | foo = 'bar' - | ^ -echo bar -", + default: + echo {{foo}} + ", + stdout: "bar\n", + stderr: " + warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` + Please see this issue for more details: https://github.com/casey/just/issues/379 + | + 1 | foo = 'bar' + | ^ + echo bar + ", } integration_test! { - name: equals_deprecated_export, - justfile: " + name: equals_deprecated_export, + justfile: " + export FOO = 'bar' -export FOO = 'bar' - -default: - echo $FOO - -", - stdout: "bar\n", - stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` -Please see this issue for more details: https://github.com/casey/just/issues/379 - | -3 | export FOO = 'bar' - | ^ -echo $FOO -", + default: + echo $FOO + ", + stdout: "bar\n", + stderr: " + warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` + Please see this issue for more details: https://github.com/casey/just/issues/379 + | + 1 | export FOO = 'bar' + | ^ + echo $FOO + ", } integration_test! { - name: equals_deprecated_alias, - justfile: " + name: equals_deprecated_alias, + justfile: " + alias foo = default -alias foo = default - -default: - echo default - -", - args: ("foo"), - stdout: "default\n", - stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` -Please see this issue for more details: https://github.com/casey/just/issues/379 - | -3 | alias foo = default - | ^ -echo default -", + default: + echo default + ", + args: ("foo"), + stdout: "default\n", + stderr: " + warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=` + Please see this issue for more details: https://github.com/casey/just/issues/379 + | + 1 | alias foo = default + | ^ + echo default + ", } diff --git a/tests/interrupts.rs b/tests/interrupts.rs index 731f665..8eddb90 100644 --- a/tests/interrupts.rs +++ b/tests/interrupts.rs @@ -1,8 +1,8 @@ -mod tempdir; +mod testing; #[cfg(unix)] mod unix { - use super::tempdir::tempdir; + use super::testing::tempdir; use executable_path::executable_path; use std::{ fs, diff --git a/tests/invocation_directory.rs b/tests/invocation_directory.rs index c544164..f390f0a 100644 --- a/tests/invocation_directory.rs +++ b/tests/invocation_directory.rs @@ -1,10 +1,10 @@ -mod tempdir; +mod testing; use std::{fs, path::Path, process, str}; use executable_path::executable_path; -use tempdir::tempdir; +use testing::tempdir; #[cfg(unix)] fn to_shell_path(path: &Path) -> String { diff --git a/tests/tempdir.rs b/tests/tempdir.rs deleted file mode 100644 index 4fe74c4..0000000 --- a/tests/tempdir.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[allow(dead_code)] -pub(crate) fn tempdir() -> tempfile::TempDir { - tempfile::Builder::new() - .prefix("just-test-tempdir") - .tempdir() - .expect("failed to create temporary directory") -} diff --git a/tests/testing/mod.rs b/tests/testing/mod.rs new file mode 100644 index 0000000..40fd9c9 --- /dev/null +++ b/tests/testing/mod.rs @@ -0,0 +1,142 @@ +pub(crate) fn tempdir() -> tempfile::TempDir { + tempfile::Builder::new() + .prefix("just-test-tempdir") + .tempdir() + .expect("failed to create temporary directory") +} + +#[allow(dead_code)] +pub(crate) fn unindent(text: &str) -> String { + // find line start and end indices + let mut lines = Vec::new(); + let mut start = 0; + for (i, c) in text.char_indices() { + if c == '\n' { + let end = i + 1; + lines.push((start, end)); + start = end; + } + } + + // if the text isn't newline-terminated, add the final line + if text.chars().last() != Some('\n') { + lines.push((start, text.len())); + } + + // find the longest common indentation + let mut common_indentation = None; + for (start, end) in lines.iter().cloned() { + let line = &text[start..end]; + + // skip blank lines + if blank(line) { + continue; + } + + // calculate new common indentation + common_indentation = match common_indentation { + Some(common_indentation) => Some(common(common_indentation, indentation(line))), + None => Some(indentation(line)), + }; + } + + // if common indentation is present, process the text + if let Some(common_indentation) = common_indentation { + if common_indentation != "" { + let mut output = String::new(); + + for (i, (start, end)) in lines.iter().cloned().enumerate() { + let line = &text[start..end]; + + if blank(line) { + // skip intial and final blank line + if i != 0 && i != lines.len() - 1 { + output.push('\n'); + } + } else { + // otherwise push the line without the common indentation + output.push_str(&line[common_indentation.len()..]); + } + } + + return output; + } + } + + // otherwise just return the input string + text.to_owned() +} + +fn indentation(line: &str) -> &str { + for (i, c) in line.char_indices() { + if c != ' ' && c != '\t' { + return &line[0..i]; + } + } + + line +} + +fn blank(line: &str) -> bool { + for (i, c) in line.char_indices() { + if c == ' ' || c == '\t' { + continue; + } + + if c == '\n' && i == line.len() - 1 { + continue; + } + + return false; + } + + true +} + +fn common<'s>(a: &'s str, b: &'s str) -> &'s str { + for ((i, ac), bc) in a.char_indices().zip(b.chars()) { + if ac != bc { + return &a[0..i]; + } + } + + a +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unindents() { + assert_eq!(unindent("foo"), "foo"); + assert_eq!(unindent("foo\nbar\nbaz\n"), "foo\nbar\nbaz\n"); + assert_eq!(unindent(""), ""); + assert_eq!(unindent(" foo\n bar"), "foo\nbar"); + assert_eq!(unindent(" foo\n bar\n\n"), "foo\nbar\n"); + } + + #[test] + fn indentations() { + assert_eq!(indentation(""), ""); + assert_eq!(indentation("foo"), ""); + assert_eq!(indentation(" foo"), " "); + assert_eq!(indentation("\t\tfoo"), "\t\t"); + assert_eq!(indentation("\t \t foo"), "\t \t "); + } + + #[test] + fn blanks() { + assert!(blank(" \n")); + assert!(!blank(" foo\n")); + assert!(blank("\t\t\n")); + } + + #[test] + fn commons() { + assert_eq!(common("foo", "foobar"), "foo"); + assert_eq!(common("foo", "bar"), ""); + assert_eq!(common("", ""), ""); + assert_eq!(common("", "bar"), ""); + } +} diff --git a/tests/working_directory.rs b/tests/working_directory.rs index b6535c1..e900434 100644 --- a/tests/working_directory.rs +++ b/tests/working_directory.rs @@ -1,10 +1,10 @@ -mod tempdir; +mod testing; use std::{error::Error, fs, process::Command}; use executable_path::executable_path; -use tempdir::tempdir; +use testing::tempdir; /// Test that just runs with the correct working directory when invoked with /// `--justfile` but not `--working-directory`