Refactor and rename test macros (#415)

This commit is contained in:
Casey Rodarmor 2019-04-19 02:17:43 -07:00 committed by GitHub
parent d065d1c54f
commit 415c84ea39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 295 additions and 260 deletions

59
Cargo.lock generated
View File

@ -96,6 +96,25 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "colored-diff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ctor"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "3.1.1" version = "3.1.1"
@ -105,6 +124,11 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "dotenv" name = "dotenv"
version = "0.13.0" version = "0.13.0"
@ -180,6 +204,14 @@ dependencies = [
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "itertools"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.8.0" version = "0.8.0"
@ -197,6 +229,7 @@ dependencies = [
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"brev 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "brev 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"colored-diff 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -206,6 +239,7 @@ dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "target 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -246,6 +280,25 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "output_vt100"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pretty_assertions"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "0.4.27" version = "0.4.27"
@ -499,7 +552,10 @@ dependencies = [
"checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83" "checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83"
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum colored-diff 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fcbd90537dc19162289fbce7e95c882dded30f4ed41044cf1470612528d7163"
"checksum ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3b4c17619643c1252b5f690084b82639dd7fac141c57c8e77a00e0148132092c"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e" "checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
"checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86" "checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86"
"checksum edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" "checksum edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b"
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
@ -510,12 +566,15 @@ dependencies = [
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"

View File

@ -31,3 +31,5 @@ features = ["termination"]
[dev-dependencies] [dev-dependencies]
executable-path = "1.0.0" executable-path = "1.0.0"
pretty_assertions = "0.6.1"
colored-diff = "0.2.1"

View File

@ -165,7 +165,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::testing::parse_success; use crate::testing::parse;
use brev::OutputError; use brev::OutputError;
fn no_cwd_err() -> Result<PathBuf, String> { fn no_cwd_err() -> Result<PathBuf, String> {
@ -174,7 +174,7 @@ mod test {
#[test] #[test]
fn backtick_code() { fn backtick_code() {
match parse_success("a:\n echo {{`f() { return 100; }; f`}}") match parse("a:\n echo {{`f() { return 100; }; f`}}")
.run(&no_cwd_err(), &["a"], &Default::default()) .run(&no_cwd_err(), &["a"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -203,7 +203,7 @@ recipe:
..Default::default() ..Default::default()
}; };
match parse_success(text) match parse(text)
.run(&no_cwd_err(), &["recipe"], &configuration) .run(&no_cwd_err(), &["recipe"], &configuration)
.unwrap_err() .unwrap_err()
{ {

View File

@ -93,7 +93,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
mod test { mod test {
use super::*; use super::*;
compilation_error_test! { error_test! {
name: circular_variable_dependency, name: circular_variable_dependency,
input: "a = b\nb = a", input: "a = b\nb = a",
offset: 0, offset: 0,
@ -103,7 +103,7 @@ mod test {
kind: CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]}, kind: CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]},
} }
compilation_error_test! { error_test! {
name: self_variable_dependency, name: self_variable_dependency,
input: "a = a", input: "a = a",
offset: 0, offset: 0,
@ -113,7 +113,7 @@ mod test {
kind: CircularVariableDependency{variable: "a", circle: vec!["a", "a"]}, kind: CircularVariableDependency{variable: "a", circle: vec!["a", "a"]},
} }
compilation_error_test! { error_test! {
name: unknown_expression_variable, name: unknown_expression_variable,
input: "x = yy", input: "x = yy",
offset: 4, offset: 4,
@ -123,7 +123,7 @@ mod test {
kind: UndefinedVariable{variable: "yy"}, kind: UndefinedVariable{variable: "yy"},
} }
compilation_error_test! { error_test! {
name: unknown_function, name: unknown_function,
input: "a = foo()", input: "a = foo()",
offset: 4, offset: 4,

View File

@ -64,3 +64,6 @@ pub(crate) use crate::command_ext::CommandExt;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use crate::range_ext::RangeExt; pub(crate) use crate::range_ext::RangeExt;
#[allow(unused_imports)]
pub(crate) use crate::ordinal::Ordinal;

View File

@ -118,8 +118,8 @@ impl<'a> Display for CompilationError<'a> {
f, f,
"Alias `{}` defined on `{}` shadows recipe defined on `{}`", "Alias `{}` defined on `{}` shadows recipe defined on `{}`",
alias, alias,
self.line + 1, self.line.ordinal(),
recipe_line + 1, recipe_line.ordinal(),
)?; )?;
} }
CircularRecipeDependency { recipe, ref circle } => { CircularRecipeDependency { recipe, ref circle } => {
@ -181,8 +181,8 @@ impl<'a> Display for CompilationError<'a> {
f, f,
"Alias `{}` first defined on line `{}` is redefined on line `{}`", "Alias `{}` first defined on line `{}` is redefined on line `{}`",
alias, alias,
first + 1, first.ordinal(),
self.line + 1, self.line.ordinal(),
)?; )?;
} }
DuplicateDependency { recipe, dependency } => { DuplicateDependency { recipe, dependency } => {
@ -197,8 +197,8 @@ impl<'a> Display for CompilationError<'a> {
f, f,
"Recipe `{}` first defined on line {} is redefined on line {}", "Recipe `{}` first defined on line {} is redefined on line {}",
recipe, recipe,
first + 1, first.ordinal(),
self.line + 1 self.line.ordinal()
)?; )?;
} }
DependencyHasParameters { recipe, dependency } => { DependencyHasParameters { recipe, dependency } => {

View File

@ -205,7 +205,7 @@ mod test {
use super::*; use super::*;
use crate::runtime_error::RuntimeError::*; use crate::runtime_error::RuntimeError::*;
use crate::testing::parse_success; use crate::testing::parse;
fn no_cwd_err() -> Result<PathBuf, String> { fn no_cwd_err() -> Result<PathBuf, String> {
Err(String::from("no cwd in tests")) Err(String::from("no cwd in tests"))
@ -213,7 +213,7 @@ mod test {
#[test] #[test]
fn unknown_recipes() { fn unknown_recipes() {
match parse_success("a:\nb:\nc:") match parse("a:\nb:\nc:")
.run(&no_cwd_err(), &["a", "x", "y", "z"], &Default::default()) .run(&no_cwd_err(), &["a", "x", "y", "z"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -246,7 +246,7 @@ a:
x x
"; ";
match parse_success(text) match parse(text)
.run(&no_cwd_err(), &["a"], &Default::default()) .run(&no_cwd_err(), &["a"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -265,7 +265,7 @@ a:
#[test] #[test]
fn code_error() { fn code_error() {
match parse_success("fail:\n @exit 100") match parse("fail:\n @exit 100")
.run(&no_cwd_err(), &["fail"], &Default::default()) .run(&no_cwd_err(), &["fail"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -288,7 +288,7 @@ a:
a return code: a return code:
@x() { {{return}} {{code + "0"}}; }; x"#; @x() { {{return}} {{code + "0"}}; }; x"#;
match parse_success(text) match parse(text)
.run(&no_cwd_err(), &["a", "return", "15"], &Default::default()) .run(&no_cwd_err(), &["a", "return", "15"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -307,7 +307,7 @@ a return code:
#[test] #[test]
fn missing_some_arguments() { fn missing_some_arguments() {
match parse_success("a b c d:") match parse("a b c d:")
.run(&no_cwd_err(), &["a", "b", "c"], &Default::default()) .run(&no_cwd_err(), &["a", "b", "c"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -331,7 +331,7 @@ a return code:
#[test] #[test]
fn missing_some_arguments_variadic() { fn missing_some_arguments_variadic() {
match parse_success("a b c +d:") match parse("a b c +d:")
.run(&no_cwd_err(), &["a", "B", "C"], &Default::default()) .run(&no_cwd_err(), &["a", "B", "C"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -355,7 +355,7 @@ a return code:
#[test] #[test]
fn missing_all_arguments() { fn missing_all_arguments() {
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}") match parse("a b c d:\n echo {{b}}{{c}}{{d}}")
.run(&no_cwd_err(), &["a"], &Default::default()) .run(&no_cwd_err(), &["a"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -379,7 +379,7 @@ a return code:
#[test] #[test]
fn missing_some_defaults() { fn missing_some_defaults() {
match parse_success("a b c d='hello':") match parse("a b c d='hello':")
.run(&no_cwd_err(), &["a", "b"], &Default::default()) .run(&no_cwd_err(), &["a", "b"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -403,7 +403,7 @@ a return code:
#[test] #[test]
fn missing_all_defaults() { fn missing_all_defaults() {
match parse_success("a b c='r' d='h':") match parse("a b c='r' d='h':")
.run(&no_cwd_err(), &["a"], &Default::default()) .run(&no_cwd_err(), &["a"], &Default::default())
.unwrap_err() .unwrap_err()
{ {
@ -430,7 +430,7 @@ a return code:
let mut configuration: Configuration = Default::default(); let mut configuration: Configuration = Default::default();
configuration.overrides.insert("foo", "bar"); configuration.overrides.insert("foo", "bar");
configuration.overrides.insert("baz", "bob"); configuration.overrides.insert("baz", "bob");
match parse_success("a:\n echo {{`f() { return 100; }; f`}}") match parse("a:\n echo {{`f() { return 100; }; f`}}")
.run(&no_cwd_err(), &["a"], &configuration) .run(&no_cwd_err(), &["a"], &configuration)
.unwrap_err() .unwrap_err()
{ {
@ -458,7 +458,7 @@ wut:
..Default::default() ..Default::default()
}; };
match parse_success(text) match parse(text)
.run(&no_cwd_err(), &["wut"], &configuration) .run(&no_cwd_err(), &["wut"], &configuration)
.unwrap_err() .unwrap_err()
{ {

View File

@ -626,9 +626,40 @@ impl<'a> Lexer<'a> {
mod tests { mod tests {
use super::*; use super::*;
use crate::testing::token_summary; fn summary(tokens: &[Token]) -> String {
use TokenKind::*;
macro_rules! summary_test { tokens
.iter()
.map(|t| match t.kind {
At => "@",
Backtick => "`",
Colon => ":",
ColonEquals => ":=",
Comma => ",",
Comment => "#",
Dedent => "<",
Eof => ".",
Eol => "$",
Equals => "=",
Indent => ">",
InterpolationEnd => "}",
InterpolationStart => "{",
Line => "^",
Name => "N",
ParenL => "(",
ParenR => ")",
Plus => "+",
StringRaw => "'",
StringCooked => "\"",
Text => "_",
Whitespace => " ",
})
.collect::<Vec<&str>>()
.join("")
}
macro_rules! lex_test {
($name:ident, $input:expr, $expected:expr $(,)*) => { ($name:ident, $input:expr, $expected:expr $(,)*) => {
#[test] #[test]
fn $name() { fn $name() {
@ -640,116 +671,78 @@ mod tests {
.map(Token::lexeme) .map(Token::lexeme)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(""); .join("");
let actual = token_summary(&tokens); let actual = summary(&tokens);
if actual != expected {
panic!( use pretty_assertions::assert_eq;
"token summary mismatch:\nexpected: {}\ngot: {}\n",
expected, actual assert_eq!(actual, expected, "token summary mismatch");
);
} assert_eq!(input, roundtrip, "token round-trip not equal");
assert_eq!(input, roundtrip);
} }
}; };
} }
macro_rules! error_test { lex_test! {
(
name: $name:ident,
input: $input:expr,
offset: $offset:expr,
line: $line:expr,
column: $column:expr,
width: $width:expr,
kind: $kind:expr,
) => {
#[test]
fn $name() {
let input = $input;
let expected = CompilationError {
text: input,
offset: $offset,
line: $line,
column: $column,
width: $width,
kind: $kind,
};
if let Err(error) = Lexer::lex(input) {
assert_eq!(error.text, expected.text);
assert_eq!(error.offset, expected.offset);
assert_eq!(error.line, expected.line);
assert_eq!(error.column, expected.column);
assert_eq!(error.kind, expected.kind);
assert_eq!(error, expected);
} else {
panic!("tokenize succeeded but expected: {}\n{}", expected, input);
}
}
};
}
summary_test! {
name, name,
"foo", "foo",
"N.", "N.",
} }
summary_test! { lex_test! {
comment, comment,
"# hello", "# hello",
"#.", "#.",
} }
summary_test! { lex_test! {
backtick, backtick,
"`echo`", "`echo`",
"`.", "`.",
} }
summary_test! { lex_test! {
raw_string, raw_string,
"'hello'", "'hello'",
"'.", "'.",
} }
summary_test! { lex_test! {
cooked_string, cooked_string,
r#""hello""#, r#""hello""#,
r#""."#, r#""."#,
} }
summary_test! { lex_test! {
export_concatination, export_concatination,
"export foo = 'foo' + 'bar'", "export foo = 'foo' + 'bar'",
"N N = ' + '.", "N N = ' + '.",
} }
summary_test! { lex_test! {
export_complex, export_complex,
"export foo = ('foo' + 'bar') + `baz`", "export foo = ('foo' + 'bar') + `baz`",
"N N = (' + ') + `.", "N N = (' + ') + `.",
} }
summary_test! { lex_test! {
eol_linefeed, eol_linefeed,
"\n", "\n",
"$.", "$.",
} }
summary_test! { lex_test! {
eol_carriage_return_linefeed, eol_carriage_return_linefeed,
"\r\n", "\r\n",
"$.", "$.",
} }
summary_test! { lex_test! {
indented_line, indented_line,
"foo:\n a", "foo:\n a",
"N:$>^_<.", "N:$>^_<.",
} }
summary_test! { lex_test! {
indented_block, indented_block,
r##"foo: r##"foo:
a a
@ -759,7 +752,7 @@ mod tests {
"N:$>^_$ ^_$ ^_$<.", "N:$>^_$ ^_$ ^_$<.",
} }
summary_test! { lex_test! {
indented_block_followed_by_item, indented_block_followed_by_item,
"foo: "foo:
a a
@ -767,7 +760,7 @@ b:",
"N:$>^_$<N:.", "N:$>^_$<N:.",
} }
summary_test! { lex_test! {
indented_block_followed_by_blank, indented_block_followed_by_blank,
"foo: "foo:
a a
@ -776,13 +769,13 @@ b:",
"N:$>^_$^$<N:.", "N:$>^_$^$<N:.",
} }
summary_test! { lex_test! {
indented_line_containing_unpaired_carriage_return, indented_line_containing_unpaired_carriage_return,
"foo:\n \r \n", "foo:\n \r \n",
"N:$>^_$<.", "N:$>^_$<.",
} }
summary_test! { lex_test! {
indented_blocks, indented_blocks,
" "
b: a b: a
@ -800,19 +793,19 @@ c: b
"$N: N$>^_$^$<N:$>^_$ ^_$^$<N: N$>^_$^$<N: N$>^_<.", "$N: N$>^_$^$<N:$>^_$ ^_$^$<N: N$>^_$^$<N: N$>^_<.",
} }
summary_test! { lex_test! {
interpolation_empty, interpolation_empty,
"hello:\n echo {{}}", "hello:\n echo {{}}",
"N:$>^_{}<.", "N:$>^_{}<.",
} }
summary_test! { lex_test! {
interpolation_expression, interpolation_expression,
"hello:\n echo {{`echo hello` + `echo goodbye`}}", "hello:\n echo {{`echo hello` + `echo goodbye`}}",
"N:$>^_{` + `}<.", "N:$>^_{` + `}<.",
} }
summary_test! { lex_test! {
tokenize_names, tokenize_names,
"\ "\
foo foo
@ -822,13 +815,13 @@ test123",
"N$N$N$N.", "N$N$N$N.",
} }
summary_test! { lex_test! {
tokenize_indented_line, tokenize_indented_line,
"foo:\n a", "foo:\n a",
"N:$>^_<.", "N:$>^_<.",
} }
summary_test! { lex_test! {
tokenize_indented_block, tokenize_indented_block,
r##"foo: r##"foo:
a a
@ -838,13 +831,13 @@ test123",
"N:$>^_$ ^_$ ^_$<.", "N:$>^_$ ^_$ ^_$<.",
} }
summary_test! { lex_test! {
tokenize_strings, tokenize_strings,
r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#, r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#,
r#"N = " + ' + " + '#."#, r#"N = " + ' + " + '#."#,
} }
summary_test! { lex_test! {
tokenize_recipe_interpolation_eol, tokenize_recipe_interpolation_eol,
"foo: # some comment "foo: # some comment
{{hello}} {{hello}}
@ -852,7 +845,7 @@ test123",
"N: #$>^{N}$<.", "N: #$>^{N}$<.",
} }
summary_test! { lex_test! {
tokenize_recipe_interpolation_eof, tokenize_recipe_interpolation_eof,
"foo: # more comments "foo: # more comments
{{hello}} {{hello}}
@ -861,19 +854,19 @@ test123",
"N: #$>^{N}$<#$.", "N: #$>^{N}$<#$.",
} }
summary_test! { lex_test! {
tokenize_recipe_complex_interpolation_expression, tokenize_recipe_complex_interpolation_expression,
"foo: #lol\n {{a + b + \"z\" + blarg}}", "foo: #lol\n {{a + b + \"z\" + blarg}}",
"N: #$>^{N + N + \" + N}<.", "N: #$>^{N + N + \" + N}<.",
} }
summary_test! { lex_test! {
tokenize_recipe_multiple_interpolations, tokenize_recipe_multiple_interpolations,
"foo:,#ok\n {{a}}0{{b}}1{{c}}", "foo:,#ok\n {{a}}0{{b}}1{{c}}",
"N:,#$>^{N}_{N}_{N}<.", "N:,#$>^{N}_{N}_{N}<.",
} }
summary_test! { lex_test! {
tokenize_junk, tokenize_junk,
"bob "bob
@ -882,7 +875,7 @@ hello blah blah blah : a b c #whatever
"N$$N N N N : N N N #$ .", "N$$N N N N : N N N #$ .",
} }
summary_test! { lex_test! {
tokenize_empty_lines, tokenize_empty_lines,
" "
# this does something # this does something
@ -899,7 +892,7 @@ hello:
"$#$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$ .", "$#$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$ .",
} }
summary_test! { lex_test! {
tokenize_comment_before_variable, tokenize_comment_before_variable,
" "
# #
@ -910,25 +903,25 @@ echo:
"$#$N='$N:$>^_{N}$ <.", "$#$N='$N:$>^_{N}$ <.",
} }
summary_test! { lex_test! {
tokenize_interpolation_backticks, tokenize_interpolation_backticks,
"hello:\n echo {{`echo hello` + `echo goodbye`}}", "hello:\n echo {{`echo hello` + `echo goodbye`}}",
"N:$>^_{` + `}<.", "N:$>^_{` + `}<.",
} }
summary_test! { lex_test! {
tokenize_empty_interpolation, tokenize_empty_interpolation,
"hello:\n echo {{}}", "hello:\n echo {{}}",
"N:$>^_{}<.", "N:$>^_{}<.",
} }
summary_test! { lex_test! {
tokenize_assignment_backticks, tokenize_assignment_backticks,
"a = `echo hello` + `echo goodbye`", "a = `echo hello` + `echo goodbye`",
"N = ` + `.", "N = ` + `.",
} }
summary_test! { lex_test! {
tokenize_multiple, tokenize_multiple,
" "
hello: hello:
@ -947,19 +940,19 @@ bob:
"$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$N:$>^_$ <.", "$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$N:$>^_$ <.",
} }
summary_test! { lex_test! {
tokenize_comment, tokenize_comment,
"a:=#", "a:=#",
"N:=#." "N:=#."
} }
summary_test! { lex_test! {
tokenize_comment_with_bang, tokenize_comment_with_bang,
"a:=#foo!", "a:=#foo!",
"N:=#." "N:=#."
} }
summary_test! { lex_test! {
tokenize_order, tokenize_order,
r" r"
b: a b: a
@ -977,19 +970,19 @@ c: b
"$N: N$>^_$^$<N:$>^_$ ^_$^$<N: N$>^_$^$<N: N$>^_<.", "$N: N$>^_$^$<N:$>^_$ ^_$^$<N: N$>^_$^$<N: N$>^_<.",
} }
summary_test! { lex_test! {
tokenize_parens, tokenize_parens,
r"((())) )abc(+", r"((())) )abc(+",
"((())) )N(+.", "((())) )N(+.",
} }
summary_test! { lex_test! {
crlf_newline, crlf_newline,
"#\r\n#asdf\r\n", "#\r\n#asdf\r\n",
"#$#$.", "#$#$.",
} }
summary_test! { lex_test! {
multiple_recipes, multiple_recipes,
"a:\n foo\nb:", "a:\n foo\nb:",
"N:$>^_$<N:.", "N:$>^_$<N:.",

View File

@ -31,6 +31,7 @@ mod justfile;
mod lexer; mod lexer;
mod load_dotenv; mod load_dotenv;
mod misc; mod misc;
mod ordinal;
mod parameter; mod parameter;
mod parser; mod parser;
mod platform; mod platform;

View File

@ -65,7 +65,7 @@ pub fn write_error_context(
) -> Result<(), fmt::Error> { ) -> Result<(), fmt::Error> {
let width = if width == 0 { 1 } else { width }; let width = if width == 0 { 1 } else { width };
let line_number = line + 1; let line_number = line.ordinal();
let red = Color::fmt(f).error(); let red = Color::fmt(f).error();
match text.lines().nth(line) { match text.lines().nth(line) {
Some(line) => { Some(line) => {

10
src/ordinal.rs Normal file
View File

@ -0,0 +1,10 @@
pub trait Ordinal {
/// Convert an index starting at 0 to an ordinal starting at 1
fn ordinal(self) -> Self;
}
impl Ordinal for usize {
fn ordinal(self) -> Self {
self + 1
}
}

View File

@ -523,34 +523,27 @@ impl<'a> Parser<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::testing::parse_success; use crate::testing::parse;
macro_rules! summary_test { macro_rules! parse_test {
($name:ident, $input:expr, $expected:expr $(,)*) => { ($name:ident, $input:expr, $expected:expr $(,)*) => {
#[test] #[test]
fn $name() { fn $name() {
let input = $input; let input = $input;
let expected = $expected; let expected = $expected;
let justfile = parse_success(input); let justfile = parse(input);
let actual = format!("{:#}", justfile); let actual = format!("{:#}", justfile);
if actual != expected { use pretty_assertions::assert_eq;
println!("got:\n\"{}\"\n", actual);
println!("expected:\n\"{}\"", expected);
assert_eq!(actual, expected); assert_eq!(actual, expected);
}
println!("Re-parsing..."); println!("Re-parsing...");
let reparsed = parse_success(&actual); let reparsed = parse(&actual);
let redumped = format!("{:#}", reparsed); let redumped = format!("{:#}", reparsed);
if redumped != actual {
println!("reparsed:\n\"{}\"\n", redumped);
println!("expected:\n\"{}\"", actual);
assert_eq!(redumped, actual); assert_eq!(redumped, actual);
} }
}
}; };
} }
summary_test! { parse_test! {
parse_empty, parse_empty,
" "
@ -561,7 +554,7 @@ mod test {
"", "",
} }
summary_test! { parse_test! {
parse_string_default, parse_string_default,
r#" r#"
@ -572,7 +565,7 @@ foo a="b\t":
r#"foo a="b\t":"#, r#"foo a="b\t":"#,
} }
summary_test! { parse_test! {
parse_multiple, parse_multiple,
r#" r#"
a: a:
@ -583,7 +576,7 @@ b:
b:"#, b:"#,
} }
summary_test! { parse_test! {
parse_variadic, parse_variadic,
r#" r#"
@ -594,7 +587,7 @@ foo +a:
r#"foo +a:"#, r#"foo +a:"#,
} }
summary_test! { parse_test! {
parse_variadic_string_default, parse_variadic_string_default,
r#" r#"
@ -605,7 +598,7 @@ foo +a="Hello":
r#"foo +a="Hello":"#, r#"foo +a="Hello":"#,
} }
summary_test! { parse_test! {
parse_raw_string_default, parse_raw_string_default,
r#" r#"
@ -616,7 +609,7 @@ foo a='b\t':
r#"foo a='b\t':"#, r#"foo a='b\t':"#,
} }
summary_test! { parse_test! {
parse_export, parse_export,
r#" r#"
export a := "hello" export a := "hello"
@ -625,7 +618,7 @@ export a := "hello"
r#"export a := "hello""#, r#"export a := "hello""#,
} }
summary_test! { parse_test! {
parse_alias_after_target, parse_alias_after_target,
r#" r#"
foo: foo:
@ -638,7 +631,7 @@ foo:
echo a"# echo a"#
} }
summary_test! { parse_test! {
parse_alias_before_target, parse_alias_before_target,
r#" r#"
alias f := foo alias f := foo
@ -651,7 +644,7 @@ foo:
echo a"# echo a"#
} }
summary_test! { parse_test! {
parse_alias_with_comment, parse_alias_with_comment,
r#" r#"
alias f := foo #comment alias f := foo #comment
@ -664,7 +657,7 @@ foo:
echo a"# echo a"#
} }
summary_test! { parse_test! {
parse_complex, parse_complex,
" "
x: x:
@ -702,7 +695,7 @@ y:
z:" z:"
} }
summary_test! { parse_test! {
parse_shebang, parse_shebang,
" "
practicum := 'hello' practicum := 'hello'
@ -721,13 +714,13 @@ install:
fi", fi",
} }
summary_test! { parse_test! {
parse_simple_shebang, parse_simple_shebang,
"a:\n #!\n print(1)", "a:\n #!\n print(1)",
"a:\n #!\n print(1)", "a:\n #!\n print(1)",
} }
summary_test! { parse_test! {
parse_assignments, parse_assignments,
r#"a := "0" r#"a := "0"
c := a + b + a + b c := a + b + a + b
@ -740,7 +733,7 @@ b := "1"
c := a + b + a + b"#, c := a + b + a + b"#,
} }
summary_test! { parse_test! {
parse_assignment_backticks, parse_assignment_backticks,
"a := `echo hello` "a := `echo hello`
c := a + b + a + b c := a + b + a + b
@ -752,7 +745,7 @@ b := `echo goodbye`
c := a + b + a + b", c := a + b + a + b",
} }
summary_test! { parse_test! {
parse_interpolation_backticks, parse_interpolation_backticks,
r#"a: r#"a:
echo {{ `echo hello` + "blarg" }} {{ `echo bob` }}"#, echo {{ `echo hello` + "blarg" }} {{ `echo bob` }}"#,
@ -760,25 +753,25 @@ c := a + b + a + b",
echo {{`echo hello` + "blarg"}} {{`echo bob`}}"#, echo {{`echo hello` + "blarg"}} {{`echo bob`}}"#,
} }
summary_test! { parse_test! {
eof_test, eof_test,
"x:\ny:\nz:\na b c: x y z", "x:\ny:\nz:\na b c: x y z",
"a b c: x y z\n\nx:\n\ny:\n\nz:", "a b c: x y z\n\nx:\n\ny:\n\nz:",
} }
summary_test! { parse_test! {
string_quote_escape, string_quote_escape,
r#"a := "hello\"""#, r#"a := "hello\"""#,
r#"a := "hello\"""#, r#"a := "hello\"""#,
} }
summary_test! { parse_test! {
string_escapes, string_escapes,
r#"a := "\n\t\r\"\\""#, r#"a := "\n\t\r\"\\""#,
r#"a := "\n\t\r\"\\""#, r#"a := "\n\t\r\"\\""#,
} }
summary_test! { parse_test! {
parameters, parameters,
"a b c: "a b c:
{{b}} {{c}}", {{b}} {{c}}",
@ -786,7 +779,7 @@ c := a + b + a + b",
{{b}} {{c}}", {{b}} {{c}}",
} }
summary_test! { parse_test! {
unary_functions, unary_functions,
" "
x := arch() x := arch()
@ -799,7 +792,7 @@ a:
{{os()}} {{os_family()}}", {{os()}} {{os_family()}}",
} }
summary_test! { parse_test! {
env_functions, env_functions,
r#" r#"
x := env_var('foo',) x := env_var('foo',)
@ -812,7 +805,7 @@ a:
{{env_var_or_default('foo' + 'bar', 'baz')}} {{env_var(env_var("baz"))}}"#, {{env_var_or_default('foo' + 'bar', 'baz')}} {{env_var(env_var("baz"))}}"#,
} }
summary_test! { parse_test! {
parameter_default_string, parameter_default_string,
r#" r#"
f x="abc": f x="abc":
@ -820,7 +813,7 @@ f x="abc":
r#"f x="abc":"#, r#"f x="abc":"#,
} }
summary_test! { parse_test! {
parameter_default_raw_string, parameter_default_raw_string,
r#" r#"
f x='abc': f x='abc':
@ -828,7 +821,7 @@ f x='abc':
r#"f x='abc':"#, r#"f x='abc':"#,
} }
summary_test! { parse_test! {
parameter_default_backtick, parameter_default_backtick,
r#" r#"
f x=`echo hello`: f x=`echo hello`:
@ -836,7 +829,7 @@ f x=`echo hello`:
r#"f x=`echo hello`:"#, r#"f x=`echo hello`:"#,
} }
summary_test! { parse_test! {
parameter_default_concatination_string, parameter_default_concatination_string,
r#" r#"
f x=(`echo hello` + "foo"): f x=(`echo hello` + "foo"):
@ -844,7 +837,7 @@ f x=(`echo hello` + "foo"):
r#"f x=(`echo hello` + "foo"):"#, r#"f x=(`echo hello` + "foo"):"#,
} }
summary_test! { parse_test! {
parameter_default_concatination_variable, parameter_default_concatination_variable,
r#" r#"
x := "10" x := "10"
@ -855,7 +848,7 @@ f y=(`echo hello` + x) +z="foo":
f y=(`echo hello` + x) +z="foo":"#, f y=(`echo hello` + x) +z="foo":"#,
} }
summary_test! { parse_test! {
parameter_default_multiple, parameter_default_multiple,
r#" r#"
x := "10" x := "10"
@ -866,20 +859,20 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):
f y=(`echo hello` + x) +z=("foo" + "bar"):"#, f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
} }
summary_test! { parse_test! {
concatination_in_group, concatination_in_group,
"x := ('0' + '1')", "x := ('0' + '1')",
"x := ('0' + '1')", "x := ('0' + '1')",
} }
summary_test! { parse_test! {
string_in_group, string_in_group,
"x := ('0' )", "x := ('0' )",
"x := ('0')", "x := ('0')",
} }
#[rustfmt::skip] #[rustfmt::skip]
summary_test! { parse_test! {
escaped_dos_newlines, escaped_dos_newlines,
"@spam:\r "@spam:\r
\t{ \\\r \t{ \\\r
@ -896,7 +889,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
} | less", } | less",
} }
compilation_error_test! { error_test! {
name: duplicate_alias, name: duplicate_alias,
input: "alias foo = bar\nalias foo = baz", input: "alias foo = bar\nalias foo = baz",
offset: 22, offset: 22,
@ -906,7 +899,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DuplicateAlias { alias: "foo", first: 0 }, kind: DuplicateAlias { alias: "foo", first: 0 },
} }
compilation_error_test! { error_test! {
name: alias_syntax_multiple_rhs, name: alias_syntax_multiple_rhs,
input: "alias foo = bar baz", input: "alias foo = bar baz",
offset: 16, offset: 16,
@ -916,7 +909,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken { expected: vec![Eol, Eof], found: Name }, kind: UnexpectedToken { expected: vec![Eol, Eof], found: Name },
} }
compilation_error_test! { error_test! {
name: alias_syntax_no_rhs, name: alias_syntax_no_rhs,
input: "alias foo = \n", input: "alias foo = \n",
offset: 12, offset: 12,
@ -926,7 +919,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken {expected: vec![Name], found:Eol}, kind: UnexpectedToken {expected: vec![Name], found:Eol},
} }
compilation_error_test! { error_test! {
name: unknown_alias_target, name: unknown_alias_target,
input: "alias foo = bar\n", input: "alias foo = bar\n",
offset: 6, offset: 6,
@ -936,7 +929,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnknownAliasTarget {alias: "foo", target: "bar"}, kind: UnknownAliasTarget {alias: "foo", target: "bar"},
} }
compilation_error_test! { error_test! {
name: alias_shadows_recipe_before, name: alias_shadows_recipe_before,
input: "bar: \n echo bar\nalias foo = bar\nfoo:\n echo foo", input: "bar: \n echo bar\nalias foo = bar\nfoo:\n echo foo",
offset: 23, offset: 23,
@ -946,7 +939,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: AliasShadowsRecipe {alias: "foo", recipe_line: 3}, kind: AliasShadowsRecipe {alias: "foo", recipe_line: 3},
} }
compilation_error_test! { error_test! {
name: alias_shadows_recipe_after, name: alias_shadows_recipe_after,
input: "foo:\n echo foo\nalias foo = bar\nbar:\n echo bar", input: "foo:\n echo foo\nalias foo = bar\nbar:\n echo bar",
offset: 22, offset: 22,
@ -956,7 +949,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: AliasShadowsRecipe { alias: "foo", recipe_line: 0 }, kind: AliasShadowsRecipe { alias: "foo", recipe_line: 0 },
} }
compilation_error_test! { error_test! {
name: missing_colon, name: missing_colon,
input: "a b c\nd e f", input: "a b c\nd e f",
offset: 5, offset: 5,
@ -966,7 +959,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, Plus, Colon], found: Eol}, kind: UnexpectedToken{expected: vec![Name, Plus, Colon], found: Eol},
} }
compilation_error_test! { error_test! {
name: missing_default_eol, name: missing_default_eol,
input: "hello arg=\n", input: "hello arg=\n",
offset: 10, offset: 10,
@ -976,7 +969,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eol}, kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eol},
} }
compilation_error_test! { error_test! {
name: missing_default_eof, name: missing_default_eof,
input: "hello arg=", input: "hello arg=",
offset: 10, offset: 10,
@ -986,7 +979,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eof}, kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eof},
} }
compilation_error_test! { error_test! {
name: parameter_after_variadic, name: parameter_after_variadic,
input: "foo +a bbb:", input: "foo +a bbb:",
offset: 7, offset: 7,
@ -996,7 +989,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: ParameterFollowsVariadicParameter{parameter: "bbb"}, kind: ParameterFollowsVariadicParameter{parameter: "bbb"},
} }
compilation_error_test! { error_test! {
name: required_after_default, name: required_after_default,
input: "hello arg='foo' bar:", input: "hello arg='foo' bar:",
offset: 16, offset: 16,
@ -1006,7 +999,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: RequiredParameterFollowsDefaultParameter{parameter: "bar"}, kind: RequiredParameterFollowsDefaultParameter{parameter: "bar"},
} }
compilation_error_test! { error_test! {
name: missing_eol, name: missing_eol,
input: "a b c: z =", input: "a b c: z =",
offset: 9, offset: 9,
@ -1016,7 +1009,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, Eol, Eof], found: Equals}, kind: UnexpectedToken{expected: vec![Name, Eol, Eof], found: Equals},
} }
compilation_error_test! { error_test! {
name: duplicate_parameter, name: duplicate_parameter,
input: "a b b:", input: "a b b:",
offset: 4, offset: 4,
@ -1026,7 +1019,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DuplicateParameter{recipe: "a", parameter: "b"}, kind: DuplicateParameter{recipe: "a", parameter: "b"},
} }
compilation_error_test! { error_test! {
name: parameter_shadows_varible, name: parameter_shadows_varible,
input: "foo = \"h\"\na foo:", input: "foo = \"h\"\na foo:",
offset: 12, offset: 12,
@ -1036,7 +1029,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: ParameterShadowsVariable{parameter: "foo"}, kind: ParameterShadowsVariable{parameter: "foo"},
} }
compilation_error_test! { error_test! {
name: dependency_has_parameters, name: dependency_has_parameters,
input: "foo arg:\nb: foo", input: "foo arg:\nb: foo",
offset: 12, offset: 12,
@ -1046,7 +1039,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DependencyHasParameters{recipe: "b", dependency: "foo"}, kind: DependencyHasParameters{recipe: "b", dependency: "foo"},
} }
compilation_error_test! { error_test! {
name: duplicate_dependency, name: duplicate_dependency,
input: "a b c: b c z z", input: "a b c: b c z z",
offset: 13, offset: 13,
@ -1056,7 +1049,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DuplicateDependency{recipe: "a", dependency: "z"}, kind: DuplicateDependency{recipe: "a", dependency: "z"},
} }
compilation_error_test! { error_test! {
name: duplicate_recipe, name: duplicate_recipe,
input: "a:\nb:\na:", input: "a:\nb:\na:",
offset: 6, offset: 6,
@ -1066,7 +1059,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DuplicateRecipe{recipe: "a", first: 0}, kind: DuplicateRecipe{recipe: "a", first: 0},
} }
compilation_error_test! { error_test! {
name: duplicate_variable, name: duplicate_variable,
input: "a = \"0\"\na = \"0\"", input: "a = \"0\"\na = \"0\"",
offset: 8, offset: 8,
@ -1076,7 +1069,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: DuplicateVariable{variable: "a"}, kind: DuplicateVariable{variable: "a"},
} }
compilation_error_test! { error_test! {
name: extra_whitespace, name: extra_whitespace,
input: "a:\n blah\n blarg", input: "a:\n blah\n blarg",
offset: 10, offset: 10,
@ -1086,7 +1079,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: ExtraLeadingWhitespace, kind: ExtraLeadingWhitespace,
} }
compilation_error_test! { error_test! {
name: interpolation_outside_of_recipe, name: interpolation_outside_of_recipe,
input: "{{", input: "{{",
offset: 0, offset: 0,
@ -1096,7 +1089,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, At], found: InterpolationStart}, kind: UnexpectedToken{expected: vec![Name, At], found: InterpolationStart},
} }
compilation_error_test! { error_test! {
name: unclosed_parenthesis_in_expression, name: unclosed_parenthesis_in_expression,
input: "x = foo(", input: "x = foo(",
offset: 8, offset: 8,
@ -1106,7 +1099,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: Eof}, kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: Eof},
} }
compilation_error_test! { error_test! {
name: unclosed_parenthesis_in_interpolation, name: unclosed_parenthesis_in_interpolation,
input: "a:\n echo {{foo(}}", input: "a:\n echo {{foo(}}",
offset: 15, offset: 15,
@ -1116,7 +1109,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: InterpolationEnd}, kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: InterpolationEnd},
} }
compilation_error_test! { error_test! {
name: plus_following_parameter, name: plus_following_parameter,
input: "a b c+:", input: "a b c+:",
offset: 5, offset: 5,
@ -1126,7 +1119,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
kind: UnexpectedToken{expected: vec![Name], found: Plus}, kind: UnexpectedToken{expected: vec![Name], found: Plus},
} }
compilation_error_test! { error_test! {
name: bad_export, name: bad_export,
input: "export a", input: "export a",
offset: 8, offset: 8,
@ -1157,14 +1150,14 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
} }
for justfile in justfiles { for justfile in justfiles {
parse_success(&justfile); parse(&justfile);
} }
} }
#[test] #[test]
fn empty_recipe_lines() { fn empty_recipe_lines() {
let text = "a:"; let text = "a:";
let justfile = parse_success(&text); let justfile = parse(&text);
assert_eq!(justfile.recipes["a"].lines.len(), 0); assert_eq!(justfile.recipes["a"].lines.len(), 0);
} }
@ -1172,7 +1165,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
#[test] #[test]
fn simple_recipe_lines() { fn simple_recipe_lines() {
let text = "a:\n foo"; let text = "a:\n foo";
let justfile = parse_success(&text); let justfile = parse(&text);
assert_eq!(justfile.recipes["a"].lines.len(), 1); assert_eq!(justfile.recipes["a"].lines.len(), 1);
} }
@ -1185,7 +1178,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#,
b: b:
"; ";
let justfile = parse_success(&text); let justfile = parse(&text);
assert_eq!(justfile.recipes["a"].lines.len(), 1); assert_eq!(justfile.recipes["a"].lines.len(), 1);
} }

View File

@ -154,7 +154,7 @@ impl<'a, 'b> RecipeResolver<'a, 'b> {
mod test { mod test {
use super::*; use super::*;
compilation_error_test! { error_test! {
name: circular_recipe_dependency, name: circular_recipe_dependency,
input: "a: b\nb: a", input: "a: b\nb: a",
offset: 8, offset: 8,
@ -164,7 +164,7 @@ mod test {
kind: CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]}, kind: CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]},
} }
compilation_error_test! { error_test! {
name: self_recipe_dependency, name: self_recipe_dependency,
input: "a: a", input: "a: a",
offset: 3, offset: 3,
@ -174,7 +174,7 @@ mod test {
kind: CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]}, kind: CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]},
} }
compilation_error_test! { error_test! {
name: unknown_dependency, name: unknown_dependency,
input: "a: b", input: "a: b",
offset: 3, offset: 3,
@ -184,7 +184,7 @@ mod test {
kind: UnknownDependency{recipe: "a", unknown: "b"}, kind: UnknownDependency{recipe: "a", unknown: "b"},
} }
compilation_error_test! { error_test! {
name: unknown_interpolation_variable, name: unknown_interpolation_variable,
input: "x:\n {{ hello}}", input: "x:\n {{ hello}}",
offset: 9, offset: 9,
@ -194,7 +194,7 @@ mod test {
kind: UndefinedVariable{variable: "hello"}, kind: UndefinedVariable{variable: "hello"},
} }
compilation_error_test! { error_test! {
name: unknown_second_interpolation_variable, name: unknown_second_interpolation_variable,
input: "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}", input: "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}",
offset: 33, offset: 33,
@ -204,7 +204,7 @@ mod test {
kind: UndefinedVariable{variable: "lol"}, kind: UndefinedVariable{variable: "lol"},
} }
compilation_error_test! { error_test! {
name: unknown_function_in_interpolation, name: unknown_function_in_interpolation,
input: "a:\n echo {{bar()}}", input: "a:\n echo {{bar()}}",
offset: 11, offset: 11,
@ -214,7 +214,7 @@ mod test {
kind: UnknownFunction{function: "bar"}, kind: UnknownFunction{function: "bar"},
} }
compilation_error_test! { error_test! {
name: unknown_function_in_default, name: unknown_function_in_default,
input: "a f=baz():", input: "a f=baz():",
offset: 4, offset: 4,
@ -224,7 +224,7 @@ mod test {
kind: UnknownFunction{function: "baz"}, kind: UnknownFunction{function: "baz"},
} }
compilation_error_test! { error_test! {
name: unknown_variable_in_default, name: unknown_variable_in_default,
input: "a f=foo:", input: "a f=foo:",
offset: 4, offset: 4,

View File

@ -1,46 +1,13 @@
use crate::common::*; use crate::common::*;
pub fn parse_success(text: &str) -> Justfile { pub fn parse(text: &str) -> Justfile {
match Parser::parse(text) { match Parser::parse(text) {
Ok(justfile) => justfile, Ok(justfile) => justfile,
Err(error) => panic!("Expected successful parse but got error:\n {}", error), Err(error) => panic!("Expected successful parse but got error:\n {}", error),
} }
} }
pub fn token_summary(tokens: &[Token]) -> String { macro_rules! error_test {
use TokenKind::*;
tokens
.iter()
.map(|t| match t.kind {
At => "@",
Backtick => "`",
Colon => ":",
ColonEquals => ":=",
Comma => ",",
Comment => "#",
Dedent => "<",
Eof => ".",
Eol => "$",
Equals => "=",
Indent => ">",
InterpolationEnd => "}",
InterpolationStart => "{",
Line => "^",
Name => "N",
ParenL => "(",
ParenR => ")",
Plus => "+",
StringRaw => "'",
StringCooked => "\"",
Text => "_",
Whitespace => " ",
})
.collect::<Vec<&str>>()
.join("")
}
macro_rules! compilation_error_test {
( (
name: $name:ident, name: $name:ident,
input: $input:expr, input: $input:expr,
@ -52,33 +19,28 @@ macro_rules! compilation_error_test {
) => { ) => {
#[test] #[test]
fn $name() { fn $name() {
let input = $input; let text: &str = $input;
let offset: usize = $offset;
let column: usize = $column;
let width: usize = $width;
let line: usize = $line;
let kind: CompilationErrorKind = $kind;
let expected = crate::compilation_error::CompilationError { let expected = CompilationError {
text: input, text,
offset: $offset, offset,
line: $line, line,
column: $column, column,
width: $width, width,
kind: $kind, kind,
}; };
let mut tokens = Lexer::lex(input).unwrap(); match Parser::parse(text) {
Ok(_) => panic!("Compilation succeeded but expected: {}\n{}", expected, text),
tokens.retain(|token| token.kind != TokenKind::Whitespace); Err(actual) => {
use pretty_assertions::assert_eq;
let parser = crate::parser::Parser::new(input, tokens); assert_eq!(actual, expected);
}
if let Err(error) = parser.justfile() {
assert_eq!(error.text, expected.text);
assert_eq!(error.offset, expected.offset);
assert_eq!(error.line, expected.line);
assert_eq!(error.column, expected.column);
assert_eq!(error.width, expected.width);
assert_eq!(error.kind, expected.kind);
assert_eq!(error, expected);
} else {
panic!("parse succeeded but expected: {}\n{}", expected, input);
} }
} }
}; };

View File

@ -1,3 +1,4 @@
use colored_diff::PrettyDifference;
use executable_path::executable_path; use executable_path::executable_path;
use libc::{EXIT_FAILURE, EXIT_SUCCESS}; use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use std::{ use std::{
@ -93,10 +94,14 @@ fn integration_test(
} }
let stdout = str::from_utf8(&output.stdout).unwrap(); let stdout = str::from_utf8(&output.stdout).unwrap();
if stdout != expected_stdout { if stdout != expected_stdout {
println!( println!(
"bad stdout:\ngot:\n{}\n\nexpected:\n{}", "bad stdout:\n {}",
stdout, expected_stdout PrettyDifference {
expected: expected_stdout,
actual: stdout
},
); );
failure = true; failure = true;
} }
@ -104,8 +109,11 @@ fn integration_test(
let stderr = str::from_utf8(&output.stderr).unwrap(); let stderr = str::from_utf8(&output.stderr).unwrap();
if stderr != expected_stderr { if stderr != expected_stderr {
println!( println!(
"bad stderr:\ngot:\n{}\n\nexpected:\n{}", "bad stderr: {}",
stderr, expected_stderr PrettyDifference {
expected: expected_stderr,
actual: stderr
},
); );
failure = true; failure = true;
} }
@ -148,9 +156,13 @@ fn integration_test(
let reparsed = String::from_utf8(output.stdout).unwrap(); let reparsed = String::from_utf8(output.stdout).unwrap();
if reparsed != dumped { if reparsed != dumped {
print!("expected:\n{}", reparsed); println!(
print!("got:\n{}", dumped); "reparse mismatch:\n {}",
assert_eq!(reparsed, dumped); PrettyDifference {
expected: &dumped,
actual: &reparsed
},
);
} }
} }
} }