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.
This commit is contained in:
Casey Rodarmor 2019-10-07 00:32:51 -07:00 committed by GitHub
parent 633f0ccaa6
commit 11c253a213
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 235 additions and 90 deletions

View File

@ -1,4 +1,4 @@
mod tempdir; mod testing;
use std::{ use std::{
env, fs, env, fs,
@ -11,13 +11,13 @@ use std::{
use executable_path::executable_path; use executable_path::executable_path;
use libc::{EXIT_FAILURE, EXIT_SUCCESS}; use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tempdir::tempdir; use testing::{tempdir, unindent};
/// Instantiate an integration test. /// Instantiate an integration test.
macro_rules! integration_test { macro_rules! integration_test {
( (
name: $name:ident, name: $name:ident,
justfile: $justfile:tt, justfile: $justfile:expr,
$(args: ($($arg:tt)*),)? $(args: ($($arg:tt)*),)?
$(stdin: $stdin:expr,)? $(stdin: $stdin:expr,)?
$(stdout: $stdout:expr,)? $(stdout: $stdout:expr,)?
@ -52,8 +52,8 @@ impl<'a> Default for Test<'a> {
fn default() -> Test<'a> { fn default() -> Test<'a> {
Test { Test {
justfile: "", justfile: "",
stdin: "",
args: &[], args: &[],
stdin: "",
stdout: "", stdout: "",
stderr: "", stderr: "",
status: EXIT_SUCCESS, status: EXIT_SUCCESS,
@ -65,9 +65,13 @@ impl<'a> Test<'a> {
fn run(self) { fn run(self) {
let tmp = tempdir(); 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(); let mut justfile_path = tmp.path().to_path_buf();
justfile_path.push("justfile"); 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(); let mut dotenv_path = tmp.path().to_path_buf();
dotenv_path.push(".env"); dotenv_path.push(".env");
@ -103,8 +107,8 @@ impl<'a> Test<'a> {
let want = Output { let want = Output {
status: self.status, status: self.status,
stdout: self.stdout, stdout: &stdout,
stderr: self.stderr, stderr: &stderr,
}; };
assert_eq!(have, want, "bad output"); assert_eq!(have, want, "bad output");
@ -160,42 +164,51 @@ fn test_round_trip(tmpdir: &Path) {
integration_test! { integration_test! {
name: alias_listing, name: alias_listing,
justfile: "foo:\n echo foo\nalias f := foo", justfile: "
foo:
echo foo
alias f := foo
",
args: ("--list"), args: ("--list"),
stdout: "Available recipes: stdout: "
foo Available recipes:
f # alias for `foo` foo
", f # alias for `foo`
",
} }
integration_test! { integration_test! {
name: alias_listing_multiple_aliases, name: alias_listing_multiple_aliases,
justfile: "foo:\n echo foo\nalias f := foo\nalias fo := foo", justfile: "foo:\n echo foo\nalias f := foo\nalias fo := foo",
args: ("--list"), args: ("--list"),
stdout: "Available recipes: stdout: "
foo Available recipes:
f # alias for `foo` foo
fo # alias for `foo` f # alias for `foo`
", fo # alias for `foo`
",
} }
integration_test! { integration_test! {
name: alias_listing_parameters, name: alias_listing_parameters,
justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias f := foo", justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias f := foo",
args: ("--list"), args: ("--list"),
stdout: "Available recipes: stdout: "
foo PARAM='foo' Available recipes:
f PARAM='foo' # alias for `foo` foo PARAM='foo'
", f PARAM='foo' # alias for `foo`
",
} }
integration_test! { integration_test! {
name: alias_listing_private, name: alias_listing_private,
justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias _f := foo", justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias _f := foo",
args: ("--list"), args: ("--list"),
stdout: "Available recipes: stdout: "
foo PARAM='foo' Available recipes:
", foo PARAM='foo'
",
} }
integration_test! { integration_test! {
@ -462,10 +475,10 @@ backtick-fail:
\techo {{`exit 1`}} \techo {{`exit 1`}}
", ",
stdout: "", stdout: "",
stderr: "error: Backtick failed with exit code 1 stderr: " error: Backtick failed with exit code 1
| |
3 | echo {{`exit 1`}} 3 | echo {{`exit 1`}}
| ^^^^^^^^ | ^^^^^^^^
", ",
status: 1, status: 1,
} }
@ -2105,62 +2118,59 @@ default a=`read A && echo $A` b=`read B && echo $B`:
} }
integration_test! { integration_test! {
name: equals_deprecated_assignment, name: equals_deprecated_assignment,
justfile: " justfile: "
foo = 'bar'
foo = 'bar' default:
echo {{foo}}
default: ",
echo {{foo}} stdout: "bar\n",
stderr: "
", warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
stdout: "bar\n", Please see this issue for more details: https://github.com/casey/just/issues/379
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'
| | ^
3 | foo = 'bar' echo bar
| ^ ",
echo bar
",
} }
integration_test! { integration_test! {
name: equals_deprecated_export, name: equals_deprecated_export,
justfile: " justfile: "
export FOO = 'bar'
export FOO = 'bar' default:
echo $FOO
default: ",
echo $FOO stdout: "bar\n",
stderr: "
", warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
stdout: "bar\n", Please see this issue for more details: https://github.com/casey/just/issues/379
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'
| | ^
3 | export FOO = 'bar' echo $FOO
| ^ ",
echo $FOO
",
} }
integration_test! { integration_test! {
name: equals_deprecated_alias, name: equals_deprecated_alias,
justfile: " justfile: "
alias foo = default
alias foo = default default:
echo default
default: ",
echo default args: ("foo"),
stdout: "default\n",
", stderr: "
args: ("foo"), warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
stdout: "default\n", Please see this issue for more details: https://github.com/casey/just/issues/379
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
| | ^
3 | alias foo = default echo default
| ^ ",
echo default
",
} }

View File

@ -1,8 +1,8 @@
mod tempdir; mod testing;
#[cfg(unix)] #[cfg(unix)]
mod unix { mod unix {
use super::tempdir::tempdir; use super::testing::tempdir;
use executable_path::executable_path; use executable_path::executable_path;
use std::{ use std::{
fs, fs,

View File

@ -1,10 +1,10 @@
mod tempdir; mod testing;
use std::{fs, path::Path, process, str}; use std::{fs, path::Path, process, str};
use executable_path::executable_path; use executable_path::executable_path;
use tempdir::tempdir; use testing::tempdir;
#[cfg(unix)] #[cfg(unix)]
fn to_shell_path(path: &Path) -> String { fn to_shell_path(path: &Path) -> String {

View File

@ -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")
}

142
tests/testing/mod.rs Normal file
View File

@ -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"), "");
}
}

View File

@ -1,10 +1,10 @@
mod tempdir; mod testing;
use std::{error::Error, fs, process::Command}; use std::{error::Error, fs, process::Command};
use executable_path::executable_path; use executable_path::executable_path;
use tempdir::tempdir; use testing::tempdir;
/// Test that just runs with the correct working directory when invoked with /// Test that just runs with the correct working directory when invoked with
/// `--justfile` but not `--working-directory` /// `--justfile` but not `--working-directory`