diff --git a/Cargo.lock b/Cargo.lock index b8a1d6c..db29a28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,25 @@ dependencies = [ "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]] name = "ctrlc" version = "3.1.1" @@ -105,6 +124,11 @@ dependencies = [ "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]] name = "dotenv" version = "0.13.0" @@ -180,6 +204,14 @@ dependencies = [ "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]] name = "itertools" version = "0.8.0" @@ -197,6 +229,7 @@ dependencies = [ "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)", "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)", "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)", @@ -206,6 +239,7 @@ dependencies = [ "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)", "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)", "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)", @@ -246,6 +280,25 @@ dependencies = [ "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]] name = "proc-macro2" 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 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 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 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 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" @@ -510,12 +566,15 @@ dependencies = [ "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 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 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 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 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 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" diff --git a/Cargo.toml b/Cargo.toml index 63114eb..e8263d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,6 @@ version = "3.1" features = ["termination"] [dev-dependencies] -executable-path = "1.0.0" +executable-path = "1.0.0" +pretty_assertions = "0.6.1" +colored-diff = "0.2.1" diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index 5b6a204..70a5b5c 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -165,7 +165,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { #[cfg(test)] mod test { use super::*; - use crate::testing::parse_success; + use crate::testing::parse; use brev::OutputError; fn no_cwd_err() -> Result { @@ -174,7 +174,7 @@ mod test { #[test] 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()) .unwrap_err() { @@ -203,7 +203,7 @@ recipe: ..Default::default() }; - match parse_success(text) + match parse(text) .run(&no_cwd_err(), &["recipe"], &configuration) .unwrap_err() { diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index d103fcd..ea10aab 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -93,7 +93,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { mod test { use super::*; - compilation_error_test! { + error_test! { name: circular_variable_dependency, input: "a = b\nb = a", offset: 0, @@ -103,7 +103,7 @@ mod test { kind: CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]}, } - compilation_error_test! { + error_test! { name: self_variable_dependency, input: "a = a", offset: 0, @@ -113,7 +113,7 @@ mod test { kind: CircularVariableDependency{variable: "a", circle: vec!["a", "a"]}, } - compilation_error_test! { + error_test! { name: unknown_expression_variable, input: "x = yy", offset: 4, @@ -123,7 +123,7 @@ mod test { kind: UndefinedVariable{variable: "yy"}, } - compilation_error_test! { + error_test! { name: unknown_function, input: "a = foo()", offset: 4, diff --git a/src/common.rs b/src/common.rs index 52dd032..6d43494 100644 --- a/src/common.rs +++ b/src/common.rs @@ -64,3 +64,6 @@ pub(crate) use crate::command_ext::CommandExt; #[allow(unused_imports)] pub(crate) use crate::range_ext::RangeExt; + +#[allow(unused_imports)] +pub(crate) use crate::ordinal::Ordinal; diff --git a/src/compilation_error.rs b/src/compilation_error.rs index 1d62b2f..576a484 100644 --- a/src/compilation_error.rs +++ b/src/compilation_error.rs @@ -118,8 +118,8 @@ impl<'a> Display for CompilationError<'a> { f, "Alias `{}` defined on `{}` shadows recipe defined on `{}`", alias, - self.line + 1, - recipe_line + 1, + self.line.ordinal(), + recipe_line.ordinal(), )?; } CircularRecipeDependency { recipe, ref circle } => { @@ -181,8 +181,8 @@ impl<'a> Display for CompilationError<'a> { f, "Alias `{}` first defined on line `{}` is redefined on line `{}`", alias, - first + 1, - self.line + 1, + first.ordinal(), + self.line.ordinal(), )?; } DuplicateDependency { recipe, dependency } => { @@ -197,8 +197,8 @@ impl<'a> Display for CompilationError<'a> { f, "Recipe `{}` first defined on line {} is redefined on line {}", recipe, - first + 1, - self.line + 1 + first.ordinal(), + self.line.ordinal() )?; } DependencyHasParameters { recipe, dependency } => { diff --git a/src/justfile.rs b/src/justfile.rs index f86bde3..8dddad7 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -205,7 +205,7 @@ mod test { use super::*; use crate::runtime_error::RuntimeError::*; - use crate::testing::parse_success; + use crate::testing::parse; fn no_cwd_err() -> Result { Err(String::from("no cwd in tests")) @@ -213,7 +213,7 @@ mod test { #[test] fn unknown_recipes() { - match parse_success("a:\nb:\nc:") + match parse("a:\nb:\nc:") .run(&no_cwd_err(), &["a", "x", "y", "z"], &Default::default()) .unwrap_err() { @@ -246,7 +246,7 @@ a: x "; - match parse_success(text) + match parse(text) .run(&no_cwd_err(), &["a"], &Default::default()) .unwrap_err() { @@ -265,7 +265,7 @@ a: #[test] fn code_error() { - match parse_success("fail:\n @exit 100") + match parse("fail:\n @exit 100") .run(&no_cwd_err(), &["fail"], &Default::default()) .unwrap_err() { @@ -288,7 +288,7 @@ a: a return code: @x() { {{return}} {{code + "0"}}; }; x"#; - match parse_success(text) + match parse(text) .run(&no_cwd_err(), &["a", "return", "15"], &Default::default()) .unwrap_err() { @@ -307,7 +307,7 @@ a return code: #[test] 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()) .unwrap_err() { @@ -331,7 +331,7 @@ a return code: #[test] 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()) .unwrap_err() { @@ -355,7 +355,7 @@ a return code: #[test] 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()) .unwrap_err() { @@ -379,7 +379,7 @@ a return code: #[test] 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()) .unwrap_err() { @@ -403,7 +403,7 @@ a return code: #[test] 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()) .unwrap_err() { @@ -430,7 +430,7 @@ a return code: let mut configuration: Configuration = Default::default(); configuration.overrides.insert("foo", "bar"); 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) .unwrap_err() { @@ -458,7 +458,7 @@ wut: ..Default::default() }; - match parse_success(text) + match parse(text) .run(&no_cwd_err(), &["wut"], &configuration) .unwrap_err() { diff --git a/src/lexer.rs b/src/lexer.rs index 04de1b7..1fb05b4 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -626,9 +626,40 @@ impl<'a> Lexer<'a> { mod tests { 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::>() + .join("") + } + + macro_rules! lex_test { ($name:ident, $input:expr, $expected:expr $(,)*) => { #[test] fn $name() { @@ -640,116 +671,78 @@ mod tests { .map(Token::lexeme) .collect::>() .join(""); - let actual = token_summary(&tokens); - if actual != expected { - panic!( - "token summary mismatch:\nexpected: {}\ngot: {}\n", - expected, actual - ); - } - assert_eq!(input, roundtrip); + let actual = summary(&tokens); + + use pretty_assertions::assert_eq; + + assert_eq!(actual, expected, "token summary mismatch"); + + assert_eq!(input, roundtrip, "token round-trip not equal"); } }; } - macro_rules! error_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! { + lex_test! { name, "foo", "N.", } - summary_test! { + lex_test! { comment, "# hello", "#.", } - summary_test! { + lex_test! { backtick, "`echo`", "`.", } - summary_test! { + lex_test! { raw_string, "'hello'", "'.", } - summary_test! { + lex_test! { cooked_string, r#""hello""#, r#""."#, } - summary_test! { + lex_test! { export_concatination, "export foo = 'foo' + 'bar'", "N N = ' + '.", } - summary_test! { + lex_test! { export_complex, "export foo = ('foo' + 'bar') + `baz`", "N N = (' + ') + `.", } - summary_test! { + lex_test! { eol_linefeed, "\n", "$.", } - summary_test! { + lex_test! { eol_carriage_return_linefeed, "\r\n", "$.", } - summary_test! { + lex_test! { indented_line, "foo:\n a", "N:$>^_<.", } - summary_test! { + lex_test! { indented_block, r##"foo: a @@ -759,7 +752,7 @@ mod tests { "N:$>^_$ ^_$ ^_$<.", } - summary_test! { + lex_test! { indented_block_followed_by_item, "foo: a @@ -767,7 +760,7 @@ b:", "N:$>^_$^_$^$^_$<.", } - summary_test! { + lex_test! { indented_blocks, " b: a @@ -800,19 +793,19 @@ c: b "$N: N$>^_$^$^_$ ^_$^$^_$^$^_<.", } - summary_test! { + lex_test! { interpolation_empty, "hello:\n echo {{}}", "N:$>^_{}<.", } - summary_test! { + lex_test! { interpolation_expression, "hello:\n echo {{`echo hello` + `echo goodbye`}}", "N:$>^_{` + `}<.", } - summary_test! { + lex_test! { tokenize_names, "\ foo @@ -822,13 +815,13 @@ test123", "N$N$N$N.", } - summary_test! { + lex_test! { tokenize_indented_line, "foo:\n a", "N:$>^_<.", } - summary_test! { + lex_test! { tokenize_indented_block, r##"foo: a @@ -838,13 +831,13 @@ test123", "N:$>^_$ ^_$ ^_$<.", } - summary_test! { + lex_test! { tokenize_strings, r#"a = "'a'" + '"b"' + "'c'" + '"d"'#echo hello"#, r#"N = " + ' + " + '#."#, } - summary_test! { + lex_test! { tokenize_recipe_interpolation_eol, "foo: # some comment {{hello}} @@ -852,7 +845,7 @@ test123", "N: #$>^{N}$<.", } - summary_test! { + lex_test! { tokenize_recipe_interpolation_eof, "foo: # more comments {{hello}} @@ -861,19 +854,19 @@ test123", "N: #$>^{N}$<#$.", } - summary_test! { + lex_test! { tokenize_recipe_complex_interpolation_expression, "foo: #lol\n {{a + b + \"z\" + blarg}}", "N: #$>^{N + N + \" + N}<.", } - summary_test! { + lex_test! { tokenize_recipe_multiple_interpolations, "foo:,#ok\n {{a}}0{{b}}1{{c}}", "N:,#$>^{N}_{N}_{N}<.", } - summary_test! { + lex_test! { tokenize_junk, "bob @@ -882,7 +875,7 @@ hello blah blah blah : a b c #whatever "N$$N N N N : N N N #$ .", } - summary_test! { + lex_test! { tokenize_empty_lines, " # this does something @@ -899,7 +892,7 @@ hello: "$#$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$ .", } - summary_test! { + lex_test! { tokenize_comment_before_variable, " # @@ -910,25 +903,25 @@ echo: "$#$N='$N:$>^_{N}$ <.", } - summary_test! { + lex_test! { tokenize_interpolation_backticks, "hello:\n echo {{`echo hello` + `echo goodbye`}}", "N:$>^_{` + `}<.", } - summary_test! { + lex_test! { tokenize_empty_interpolation, "hello:\n echo {{}}", "N:$>^_{}<.", } - summary_test! { + lex_test! { tokenize_assignment_backticks, "a = `echo hello` + `echo goodbye`", "N = ` + `.", } - summary_test! { + lex_test! { tokenize_multiple, " hello: @@ -947,19 +940,19 @@ bob: "$N:$>^_$ ^_$^$ ^_$^$ ^_$^$<#$N:$>^_$ <.", } - summary_test! { + lex_test! { tokenize_comment, "a:=#", "N:=#." } - summary_test! { + lex_test! { tokenize_comment_with_bang, "a:=#foo!", "N:=#." } - summary_test! { + lex_test! { tokenize_order, r" b: a @@ -977,19 +970,19 @@ c: b "$N: N$>^_$^$^_$ ^_$^$^_$^$^_<.", } - summary_test! { + lex_test! { tokenize_parens, r"((())) )abc(+", "((())) )N(+.", } - summary_test! { + lex_test! { crlf_newline, "#\r\n#asdf\r\n", "#$#$.", } - summary_test! { + lex_test! { multiple_recipes, "a:\n foo\nb:", "N:$>^_$ Result<(), fmt::Error> { let width = if width == 0 { 1 } else { width }; - let line_number = line + 1; + let line_number = line.ordinal(); let red = Color::fmt(f).error(); match text.lines().nth(line) { Some(line) => { diff --git a/src/ordinal.rs b/src/ordinal.rs new file mode 100644 index 0000000..7c53dbd --- /dev/null +++ b/src/ordinal.rs @@ -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 + } +} diff --git a/src/parser.rs b/src/parser.rs index 0b6e7b6..b45d5eb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -523,34 +523,27 @@ impl<'a> Parser<'a> { #[cfg(test)] mod test { 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 $(,)*) => { #[test] fn $name() { let input = $input; let expected = $expected; - let justfile = parse_success(input); + let justfile = parse(input); let actual = format!("{:#}", justfile); - if actual != expected { - println!("got:\n\"{}\"\n", actual); - println!("expected:\n\"{}\"", expected); - assert_eq!(actual, expected); - } + use pretty_assertions::assert_eq; + assert_eq!(actual, expected); println!("Re-parsing..."); - let reparsed = parse_success(&actual); + let reparsed = parse(&actual); 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, " @@ -561,7 +554,7 @@ mod test { "", } - summary_test! { + parse_test! { parse_string_default, r#" @@ -572,7 +565,7 @@ foo a="b\t": r#"foo a="b\t":"#, } - summary_test! { + parse_test! { parse_multiple, r#" a: @@ -583,7 +576,7 @@ b: b:"#, } - summary_test! { + parse_test! { parse_variadic, r#" @@ -594,7 +587,7 @@ foo +a: r#"foo +a:"#, } - summary_test! { + parse_test! { parse_variadic_string_default, r#" @@ -605,7 +598,7 @@ foo +a="Hello": r#"foo +a="Hello":"#, } - summary_test! { + parse_test! { parse_raw_string_default, r#" @@ -616,7 +609,7 @@ foo a='b\t': r#"foo a='b\t':"#, } - summary_test! { + parse_test! { parse_export, r#" export a := "hello" @@ -625,7 +618,7 @@ export a := "hello" r#"export a := "hello""#, } - summary_test! { + parse_test! { parse_alias_after_target, r#" foo: @@ -638,7 +631,7 @@ foo: echo a"# } - summary_test! { + parse_test! { parse_alias_before_target, r#" alias f := foo @@ -651,7 +644,7 @@ foo: echo a"# } - summary_test! { + parse_test! { parse_alias_with_comment, r#" alias f := foo #comment @@ -664,7 +657,7 @@ foo: echo a"# } - summary_test! { + parse_test! { parse_complex, " x: @@ -702,7 +695,7 @@ y: z:" } - summary_test! { + parse_test! { parse_shebang, " practicum := 'hello' @@ -721,13 +714,13 @@ install: fi", } - summary_test! { + parse_test! { parse_simple_shebang, "a:\n #!\n print(1)", "a:\n #!\n print(1)", } - summary_test! { + parse_test! { parse_assignments, r#"a := "0" c := a + b + a + b @@ -740,7 +733,7 @@ b := "1" c := a + b + a + b"#, } - summary_test! { + parse_test! { parse_assignment_backticks, "a := `echo hello` c := a + b + a + b @@ -752,7 +745,7 @@ b := `echo goodbye` c := a + b + a + b", } - summary_test! { + parse_test! { parse_interpolation_backticks, r#"a: echo {{ `echo hello` + "blarg" }} {{ `echo bob` }}"#, @@ -760,25 +753,25 @@ c := a + b + a + b", echo {{`echo hello` + "blarg"}} {{`echo bob`}}"#, } - summary_test! { + parse_test! { eof_test, "x:\ny:\nz:\na b c: x y z", "a b c: x y z\n\nx:\n\ny:\n\nz:", } - summary_test! { + parse_test! { string_quote_escape, r#"a := "hello\"""#, r#"a := "hello\"""#, } - summary_test! { + parse_test! { string_escapes, r#"a := "\n\t\r\"\\""#, r#"a := "\n\t\r\"\\""#, } - summary_test! { + parse_test! { parameters, "a b c: {{b}} {{c}}", @@ -786,7 +779,7 @@ c := a + b + a + b", {{b}} {{c}}", } - summary_test! { + parse_test! { unary_functions, " x := arch() @@ -799,7 +792,7 @@ a: {{os()}} {{os_family()}}", } - summary_test! { + parse_test! { env_functions, r#" x := env_var('foo',) @@ -812,7 +805,7 @@ a: {{env_var_or_default('foo' + 'bar', 'baz')}} {{env_var(env_var("baz"))}}"#, } - summary_test! { + parse_test! { parameter_default_string, r#" f x="abc": @@ -820,7 +813,7 @@ f x="abc": r#"f x="abc":"#, } - summary_test! { + parse_test! { parameter_default_raw_string, r#" f x='abc': @@ -828,7 +821,7 @@ f x='abc': r#"f x='abc':"#, } - summary_test! { + parse_test! { parameter_default_backtick, r#" f x=`echo hello`: @@ -836,7 +829,7 @@ f x=`echo hello`: r#"f x=`echo hello`:"#, } - summary_test! { + parse_test! { parameter_default_concatination_string, r#" f x=(`echo hello` + "foo"): @@ -844,7 +837,7 @@ f x=(`echo hello` + "foo"): r#"f x=(`echo hello` + "foo"):"#, } - summary_test! { + parse_test! { parameter_default_concatination_variable, r#" x := "10" @@ -855,7 +848,7 @@ f y=(`echo hello` + x) +z="foo": f y=(`echo hello` + x) +z="foo":"#, } - summary_test! { + parse_test! { parameter_default_multiple, r#" x := "10" @@ -866,20 +859,20 @@ f y=(`echo hello` + x) +z=("foo" + "bar"): f y=(`echo hello` + x) +z=("foo" + "bar"):"#, } - summary_test! { + parse_test! { concatination_in_group, "x := ('0' + '1')", "x := ('0' + '1')", } - summary_test! { + parse_test! { string_in_group, "x := ('0' )", "x := ('0')", } #[rustfmt::skip] - summary_test! { + parse_test! { escaped_dos_newlines, "@spam:\r \t{ \\\r @@ -896,7 +889,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, } | less", } - compilation_error_test! { + error_test! { name: duplicate_alias, input: "alias foo = bar\nalias foo = baz", offset: 22, @@ -906,7 +899,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DuplicateAlias { alias: "foo", first: 0 }, } - compilation_error_test! { + error_test! { name: alias_syntax_multiple_rhs, input: "alias foo = bar baz", offset: 16, @@ -916,7 +909,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken { expected: vec![Eol, Eof], found: Name }, } - compilation_error_test! { + error_test! { name: alias_syntax_no_rhs, input: "alias foo = \n", offset: 12, @@ -926,7 +919,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken {expected: vec![Name], found:Eol}, } - compilation_error_test! { + error_test! { name: unknown_alias_target, input: "alias foo = bar\n", offset: 6, @@ -936,7 +929,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnknownAliasTarget {alias: "foo", target: "bar"}, } - compilation_error_test! { + error_test! { name: alias_shadows_recipe_before, input: "bar: \n echo bar\nalias foo = bar\nfoo:\n echo foo", offset: 23, @@ -946,7 +939,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: AliasShadowsRecipe {alias: "foo", recipe_line: 3}, } - compilation_error_test! { + error_test! { name: alias_shadows_recipe_after, input: "foo:\n echo foo\nalias foo = bar\nbar:\n echo bar", offset: 22, @@ -956,7 +949,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: AliasShadowsRecipe { alias: "foo", recipe_line: 0 }, } - compilation_error_test! { + error_test! { name: missing_colon, input: "a b c\nd e f", offset: 5, @@ -966,7 +959,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, Plus, Colon], found: Eol}, } - compilation_error_test! { + error_test! { name: missing_default_eol, input: "hello arg=\n", offset: 10, @@ -976,7 +969,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eol}, } - compilation_error_test! { + error_test! { name: missing_default_eof, input: "hello arg=", offset: 10, @@ -986,7 +979,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, StringCooked], found: Eof}, } - compilation_error_test! { + error_test! { name: parameter_after_variadic, input: "foo +a bbb:", offset: 7, @@ -996,7 +989,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: ParameterFollowsVariadicParameter{parameter: "bbb"}, } - compilation_error_test! { + error_test! { name: required_after_default, input: "hello arg='foo' bar:", offset: 16, @@ -1006,7 +999,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: RequiredParameterFollowsDefaultParameter{parameter: "bar"}, } - compilation_error_test! { + error_test! { name: missing_eol, input: "a b c: z =", offset: 9, @@ -1016,7 +1009,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, Eol, Eof], found: Equals}, } - compilation_error_test! { + error_test! { name: duplicate_parameter, input: "a b b:", offset: 4, @@ -1026,7 +1019,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DuplicateParameter{recipe: "a", parameter: "b"}, } - compilation_error_test! { + error_test! { name: parameter_shadows_varible, input: "foo = \"h\"\na foo:", offset: 12, @@ -1036,7 +1029,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: ParameterShadowsVariable{parameter: "foo"}, } - compilation_error_test! { + error_test! { name: dependency_has_parameters, input: "foo arg:\nb: foo", offset: 12, @@ -1046,7 +1039,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DependencyHasParameters{recipe: "b", dependency: "foo"}, } - compilation_error_test! { + error_test! { name: duplicate_dependency, input: "a b c: b c z z", offset: 13, @@ -1056,7 +1049,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DuplicateDependency{recipe: "a", dependency: "z"}, } - compilation_error_test! { + error_test! { name: duplicate_recipe, input: "a:\nb:\na:", offset: 6, @@ -1066,7 +1059,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DuplicateRecipe{recipe: "a", first: 0}, } - compilation_error_test! { + error_test! { name: duplicate_variable, input: "a = \"0\"\na = \"0\"", offset: 8, @@ -1076,7 +1069,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: DuplicateVariable{variable: "a"}, } - compilation_error_test! { + error_test! { name: extra_whitespace, input: "a:\n blah\n blarg", offset: 10, @@ -1086,7 +1079,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: ExtraLeadingWhitespace, } - compilation_error_test! { + error_test! { name: interpolation_outside_of_recipe, input: "{{", offset: 0, @@ -1096,7 +1089,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, At], found: InterpolationStart}, } - compilation_error_test! { + error_test! { name: unclosed_parenthesis_in_expression, input: "x = foo(", offset: 8, @@ -1106,7 +1099,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: Eof}, } - compilation_error_test! { + error_test! { name: unclosed_parenthesis_in_interpolation, input: "a:\n echo {{foo(}}", offset: 15, @@ -1116,7 +1109,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name, StringCooked, ParenR], found: InterpolationEnd}, } - compilation_error_test! { + error_test! { name: plus_following_parameter, input: "a b c+:", offset: 5, @@ -1126,7 +1119,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, kind: UnexpectedToken{expected: vec![Name], found: Plus}, } - compilation_error_test! { + error_test! { name: bad_export, input: "export a", offset: 8, @@ -1157,14 +1150,14 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, } for justfile in justfiles { - parse_success(&justfile); + parse(&justfile); } } #[test] fn empty_recipe_lines() { let text = "a:"; - let justfile = parse_success(&text); + let justfile = parse(&text); assert_eq!(justfile.recipes["a"].lines.len(), 0); } @@ -1172,7 +1165,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, #[test] fn simple_recipe_lines() { let text = "a:\n foo"; - let justfile = parse_success(&text); + let justfile = parse(&text); assert_eq!(justfile.recipes["a"].lines.len(), 1); } @@ -1185,7 +1178,7 @@ f y=(`echo hello` + x) +z=("foo" + "bar"):"#, b: "; - let justfile = parse_success(&text); + let justfile = parse(&text); assert_eq!(justfile.recipes["a"].lines.len(), 1); } diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs index 5eafb47..7b9b6d1 100644 --- a/src/recipe_resolver.rs +++ b/src/recipe_resolver.rs @@ -154,7 +154,7 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { mod test { use super::*; - compilation_error_test! { + error_test! { name: circular_recipe_dependency, input: "a: b\nb: a", offset: 8, @@ -164,7 +164,7 @@ mod test { kind: CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]}, } - compilation_error_test! { + error_test! { name: self_recipe_dependency, input: "a: a", offset: 3, @@ -174,7 +174,7 @@ mod test { kind: CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]}, } - compilation_error_test! { + error_test! { name: unknown_dependency, input: "a: b", offset: 3, @@ -184,7 +184,7 @@ mod test { kind: UnknownDependency{recipe: "a", unknown: "b"}, } - compilation_error_test! { + error_test! { name: unknown_interpolation_variable, input: "x:\n {{ hello}}", offset: 9, @@ -194,7 +194,7 @@ mod test { kind: UndefinedVariable{variable: "hello"}, } - compilation_error_test! { + error_test! { name: unknown_second_interpolation_variable, input: "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}", offset: 33, @@ -204,7 +204,7 @@ mod test { kind: UndefinedVariable{variable: "lol"}, } - compilation_error_test! { + error_test! { name: unknown_function_in_interpolation, input: "a:\n echo {{bar()}}", offset: 11, @@ -214,7 +214,7 @@ mod test { kind: UnknownFunction{function: "bar"}, } - compilation_error_test! { + error_test! { name: unknown_function_in_default, input: "a f=baz():", offset: 4, @@ -224,7 +224,7 @@ mod test { kind: UnknownFunction{function: "baz"}, } - compilation_error_test! { + error_test! { name: unknown_variable_in_default, input: "a f=foo:", offset: 4, diff --git a/src/testing.rs b/src/testing.rs index 773e2ee..e2b675c 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,46 +1,13 @@ use crate::common::*; -pub fn parse_success(text: &str) -> Justfile { +pub fn parse(text: &str) -> Justfile { match Parser::parse(text) { 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 { - 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::>() - .join("") -} - -macro_rules! compilation_error_test { +macro_rules! error_test { ( name: $name:ident, input: $input:expr, @@ -52,33 +19,28 @@ macro_rules! compilation_error_test { ) => { #[test] 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 { - text: input, - offset: $offset, - line: $line, - column: $column, - width: $width, - kind: $kind, + let expected = CompilationError { + text, + offset, + line, + column, + width, + kind, }; - let mut tokens = Lexer::lex(input).unwrap(); - - tokens.retain(|token| token.kind != TokenKind::Whitespace); - - let parser = crate::parser::Parser::new(input, tokens); - - 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); + match Parser::parse(text) { + Ok(_) => panic!("Compilation succeeded but expected: {}\n{}", expected, text), + Err(actual) => { + use pretty_assertions::assert_eq; + assert_eq!(actual, expected); + } } } }; diff --git a/tests/integration.rs b/tests/integration.rs index 8d15268..07b54ad 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,3 +1,4 @@ +use colored_diff::PrettyDifference; use executable_path::executable_path; use libc::{EXIT_FAILURE, EXIT_SUCCESS}; use std::{ @@ -93,10 +94,14 @@ fn integration_test( } let stdout = str::from_utf8(&output.stdout).unwrap(); + if stdout != expected_stdout { println!( - "bad stdout:\ngot:\n{}\n\nexpected:\n{}", - stdout, expected_stdout + "bad stdout:\n {}", + PrettyDifference { + expected: expected_stdout, + actual: stdout + }, ); failure = true; } @@ -104,8 +109,11 @@ fn integration_test( let stderr = str::from_utf8(&output.stderr).unwrap(); if stderr != expected_stderr { println!( - "bad stderr:\ngot:\n{}\n\nexpected:\n{}", - stderr, expected_stderr + "bad stderr: {}", + PrettyDifference { + expected: expected_stderr, + actual: stderr + }, ); failure = true; } @@ -148,9 +156,13 @@ fn integration_test( let reparsed = String::from_utf8(output.stdout).unwrap(); if reparsed != dumped { - print!("expected:\n{}", reparsed); - print!("got:\n{}", dumped); - assert_eq!(reparsed, dumped); + println!( + "reparse mismatch:\n {}", + PrettyDifference { + expected: &dumped, + actual: &reparsed + }, + ); } } }