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:
parent
633f0ccaa6
commit
11c253a213
@ -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: "
|
||||||
|
Available recipes:
|
||||||
foo
|
foo
|
||||||
f # alias for `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: "
|
||||||
|
Available recipes:
|
||||||
foo
|
foo
|
||||||
f # alias for `foo`
|
f # alias for `foo`
|
||||||
fo # 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: "
|
||||||
|
Available recipes:
|
||||||
foo PARAM='foo'
|
foo PARAM='foo'
|
||||||
f PARAM='foo' # alias for `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: "
|
||||||
|
Available recipes:
|
||||||
foo PARAM='foo'
|
foo PARAM='foo'
|
||||||
",
|
",
|
||||||
}
|
}
|
||||||
|
|
||||||
integration_test! {
|
integration_test! {
|
||||||
@ -462,9 +475,9 @@ 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,
|
||||||
@ -2107,60 +2120,57 @@ 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:
|
||||||
|
|
||||||
default:
|
|
||||||
echo {{foo}}
|
echo {{foo}}
|
||||||
|
",
|
||||||
",
|
|
||||||
stdout: "bar\n",
|
stdout: "bar\n",
|
||||||
stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
|
stderr: "
|
||||||
Please see this issue for more details: https://github.com/casey/just/issues/379
|
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'
|
1 | 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:
|
||||||
|
|
||||||
default:
|
|
||||||
echo $FOO
|
echo $FOO
|
||||||
|
",
|
||||||
",
|
|
||||||
stdout: "bar\n",
|
stdout: "bar\n",
|
||||||
stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
|
stderr: "
|
||||||
Please see this issue for more details: https://github.com/casey/just/issues/379
|
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'
|
1 | 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:
|
||||||
|
|
||||||
default:
|
|
||||||
echo default
|
echo default
|
||||||
|
",
|
||||||
",
|
|
||||||
args: ("foo"),
|
args: ("foo"),
|
||||||
stdout: "default\n",
|
stdout: "default\n",
|
||||||
stderr: "warning: `=` in assignments, exports, and aliases is being phased out on favor of `:=`
|
stderr: "
|
||||||
Please see this issue for more details: https://github.com/casey/just/issues/379
|
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
|
1 | alias foo = default
|
||||||
| ^
|
| ^
|
||||||
echo default
|
echo default
|
||||||
",
|
",
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
142
tests/testing/mod.rs
Normal 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"), "");
|
||||||
|
}
|
||||||
|
}
|
@ -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`
|
||||||
|
Loading…
Reference in New Issue
Block a user