From d989e477069adaf8a3bc9fa572edbb2252937c10 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 8 Jan 2019 20:32:16 -0600 Subject: [PATCH] FEATURE: Use a better DSL for unit tests. * Assert now requires a tuple instead of a string containing statements. * We include a helpful ucg based unit testing module. Fixes: #26 --- .travis.yml | 1 + Makefile | 15 ++ docsite/site/content/reference/statements.md | 28 +-- example_errors/errors_test.ucg | 30 +-- integration_tests/comparisons_test.ucg | 213 +++++++++++------- integration_tests/concatenation_test.ucg | 14 +- integration_tests/empty_test.ucg | 7 +- integration_tests/format_test.ucg | 28 ++- .../functional_processing_test.ucg | 105 +++++---- integration_tests/include_test.ucg | 23 +- integration_tests/list_test.ucg | 14 +- integration_tests/macros_test.ucg | 63 +++--- integration_tests/modules_test.ucg | 42 ++-- .../operator_precedence_test.ucg | 84 ++++--- integration_tests/select_expressions_test.ucg | 42 ++-- integration_tests/selectors_test.ucg | 77 ++++--- integration_tests/tuple_test.ucg | 126 ++++++----- src/ast/mod.rs | 2 +- src/build/mod.rs | 116 +++++++--- src/parse/mod.rs | 6 +- src/tokenizer/mod.rs | 23 -- src/tokenizer/test.rs | 13 -- std/lists.ucg | 42 ++++ std/testing.ucg | 88 ++++++++ std/tests/lists_test.ucg | 35 +++ std/tests/testing_test.ucg | 69 ++++++ 26 files changed, 847 insertions(+), 459 deletions(-) create mode 100644 Makefile create mode 100644 std/lists.ucg create mode 100644 std/testing.ucg create mode 100644 std/tests/lists_test.ucg create mode 100644 std/tests/testing_test.ucg diff --git a/.travis.yml b/.travis.yml index db79ee9..384be16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,4 @@ script: - cargo build --verbose - cargo test --verbose - cargo run -- test integration_tests + - cargo run -- test -r std/tests diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8923dd8 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +all: test build + +build: + cargo build + +test: unit integration stdlibtest + +unit: + cargo test + +integration: unit + cargo run -- test -r integration_tests + +stdlibtest: integration + cargo run -- test -r std/tests \ No newline at end of file diff --git a/docsite/site/content/reference/statements.md b/docsite/site/content/reference/statements.md index 73daa5e..54d1ebe 100644 --- a/docsite/site/content/reference/statements.md +++ b/docsite/site/content/reference/statements.md @@ -54,24 +54,26 @@ configuration or the results of test assertions. ### Assert Statements -The assert statement defines an expression that must evaluate to either true or false. -Assert statements are noops except during a validation compile. They give you a way to -assert certains properties about your data and can be used as a form of unit testing -for your configurations. It starts with the `assert` keyword followed by a valid block -of UCG statements delimited by `|` characters. The final statement in the block -must evaluate to a boolean expression. +The assert statement defines an expression that must evaluate to tuple with an +ok field that is either true or false and a desc field that is a string. Assert +statements are noops except during a validation compile. They give you a way to +assert certains properties about your data and can be used as a form of unit +testing for your configurations. It starts with the `assert` keyword followed +by a valid ucg expression. ``` -assert | - host == "www.example.com"; -|; +assert { + ok = host == "www.example.com", + desc = "Host is www.example.com", +}; -assert | - select qa, 443, { +assert { + ok = select qa, 443, { qa = 80, prod = 443, - } == 443; -|; + } == 443, + desc = "select default was 443", +}; ``` Assert statements are only evaluated when running the `ucg test` command. That diff --git a/example_errors/errors_test.ucg b/example_errors/errors_test.ucg index 0cde743..a21b862 100644 --- a/example_errors/errors_test.ucg +++ b/example_errors/errors_test.ucg @@ -1,17 +1,17 @@ -assert |macro |; -assert |{ foo = hello world}|; -assert |{ foo = "hello world"|; -assert |{ = }|; -assert |"|; -assert |=|; +assert {macro }; +assert {{ foo = hello world}}; +assert {{ foo = "hello world"}; +assert {{ = }}; +assert {"}; +assert {=}; -assert |let |; -assert |let foo |; -assert |let foo =|; -assert |import |; -assert |import foo |; -assert |out |; -assert |out "|; -assert |out json|; +assert {let }; +assert {let foo }; +assert {let foo =}; +assert {import }; +assert {import foo }; +assert {out }; +assert {out "}; +assert {out json}; -assert |let env = 1;|; \ No newline at end of file +assert {let env = 1;}; \ No newline at end of file diff --git a/integration_tests/comparisons_test.ucg b/integration_tests/comparisons_test.ucg index 08b0469..1839d5f 100644 --- a/integration_tests/comparisons_test.ucg +++ b/integration_tests/comparisons_test.ucg @@ -16,33 +16,42 @@ let list = [1, 2, 3]; let list2 = list; let list3 = [1, 2]; -assert | - one == one; -|; -assert | - one == one; -|; -assert | - one >= one; -|; -assert | - two > one; -|; -assert | - two >= two; -|; -assert | - tpl1 == tpl2; -|; -assert | - tpl1 != tpl3; -|; -assert | - list == list2; -|; -assert | - list != list3; -|; +assert { + ok = one == one, + desc = "one == one", +}; +assert { + ok = one == one, + desc = "one == one", +}; +assert { + ok = one >= one, + desc = "one >= one" +}; +assert { + ok = two > one, + desc = "two > one" +}; +assert { + ok = two >= two, + desc = "two >= two", +}; +assert { + ok = tpl1 == tpl2, + desc = "tpl1 == tpl2" +}; +assert { + ok = tpl1 != tpl3, + desc = "tpl1 != tpl3", +}; +assert { + ok = list == list2, + desc = "list == list2", +}; +assert { + ok = list != list3, + desc = "list != list3", +}; // Deep Comparisons let tpl4 = { @@ -58,73 +67,105 @@ let less = { foo = "bar" }; -assert | - tpl4.inner == copy.inner; -|; -assert | - tpl4.inner.fld == copy.inner.fld; -|; -assert | - tpl4.lst == copy.lst; -|; -assert | - tpl4.foo == copy.foo; -|; -assert | - tpl4 == copy; -|; -assert | - tpl4 != extra; -|; -assert | - tpl4 != less; -|; +assert { + ok = tpl4.inner == copy.inner, + desc = "tpl4.inner == copy.inner", +}; +assert { + ok = tpl4.inner.fld == copy.inner.fld, + desc = "tpl4.inner.fld == copy.inner.fld", +}; +assert { + ok = tpl4.lst == copy.lst, + desc = "tpl4.lst == copy.lst", +}; +assert { + ok = tpl4.foo == copy.foo, + desc = "tpl4.foo == copy.foo", +}; +assert { + ok = tpl4 == copy, + desc = "tpl4 == copy", +}; +assert { + ok = tpl4 != extra, + desc = "tpl4 != extra", +}; +assert { + ok = tpl4 != less, + desc = "tpl4 != less", +}; -assert | - [[1, 2, [3]], 4] == [[1, 2, [3]], 4]; -|; +assert { + ok = [[1, 2, [3]], 4] == [[1, 2, [3]], 4], + desc = "[[1, 2, [3]], 4] == [[1, 2, [3]], 4]", +}; -assert | - [[1, 2, [3]], 4] != [[1, 2, [6]], 4]; -|; +assert { + ok = [[1, 2, [3]], 4] != [[1, 2, [6]], 4], + desc = "[[1, 2, [3]], 4] != [[1, 2, [6]], 4]", +}; // Expression comparisons -assert |2 == 1+1;|; -assert |(1+1) == 2;|; -assert |(1+1) == (1+1);|; +assert { + ok = 2 == 1+1, + desc = "2 == 1+1", +}; +assert { + ok = (1+1) == 2, + desc = "(1+1) == 2", +}; +assert { + ok = (1+1) == (1+1), + desc = "(1+1) == (1+1)", +}; let want = "foo"; -assert | - select want, 1, { foo=2, } == 2; -|; +assert { + ok = select want, 1, { foo=2, } == 2, + desc = "select want, 1, { foo=2, } == 2", +}; // Contains comparison operators. -assert | - "foo" in { +assert { + ok = "foo" in { foo = "bar", - }; -|; -assert | - foo in { + }, + desc = "\"foo\" in { + foo = \"bar\", + } + ", +}; +assert { + ok = foo in { foo = "bar", - }; -|; -assert | - "foo" in ["foo", "bar"]; -|; -assert | - "foo" in ["bar", "foo", "bar"]; -|; -assert | - { foo = 1 } in ["foo", { foo = 1 }]; -|; -assert | - true in [ "foo" in {foo = 1}, false ]; -|; + }, + desc = "foo in { + foo = \"bar\", + }", +}; +assert { + ok = "foo" in ["foo", "bar"], + desc = "\"foo\" in [\"foo\", \"bar\"]", +}; +assert { + ok = "foo" in ["bar", "foo", "bar"], + desc = "\"foo\" in [\"bar\", \"foo\", \"bar\"]", +}; +assert { + ok = { foo = 1 } in ["foo", { foo = 1 }], + desc = "{ foo = 1 } in [\"foo\", { foo = 1 }]", +}; +assert { + ok = true in [ "foo" in {foo = 1}, false ], + desc = "true in [ \"foo\" in {foo = 1}, false ]", +}; -assert | - "foo" ~ "o+"; -|; +assert { + ok = "foo" ~ "o+", + desc = "\"foo\" ~ \"o+\"" +}; -assert | - "foo" !~ "bar"; -|; \ No newline at end of file +assert { + ok = "foo" !~ "bar", + desc = "\"foo\" !~ \"bar\"", +}; \ No newline at end of file diff --git a/integration_tests/concatenation_test.ucg b/integration_tests/concatenation_test.ucg index a7c6558..65068fa 100644 --- a/integration_tests/concatenation_test.ucg +++ b/integration_tests/concatenation_test.ucg @@ -1,6 +1,8 @@ -assert | - "hello " + "world" == "hello world"; -|; -assert | - [1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]; -|; \ No newline at end of file +assert { + ok = "hello " + "world" == "hello world", + desc = "\"hello \" + \"world\" == \"hello world\"", +}; +assert { + ok = [1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6], + desc = "[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]", +}; \ No newline at end of file diff --git a/integration_tests/empty_test.ucg b/integration_tests/empty_test.ucg index 9707de3..eb45e6b 100644 --- a/integration_tests/empty_test.ucg +++ b/integration_tests/empty_test.ucg @@ -2,6 +2,7 @@ let empty = NULL; let tpl = { foo = NULL, }; -assert | - tpl.foo == empty; -|; \ No newline at end of file +assert { + ok = tpl.foo == empty, + desc = "tpl.foo == empty", +}; \ No newline at end of file diff --git a/integration_tests/format_test.ucg b/integration_tests/format_test.ucg index 77016ca..edb7020 100644 --- a/integration_tests/format_test.ucg +++ b/integration_tests/format_test.ucg @@ -1,12 +1,16 @@ -assert | - "hello @" % ("world") == "hello world"; -|; -assert | - "1 @ @" % (2, 3) == "1 2 3"; -|; -assert | - "@ or @" % (true, false) == "true or false"; -|; -assert | - "@" % (NULL) == "NULL"; -|; \ No newline at end of file +assert { + ok = "hello @" % ("world") == "hello world", + desc = "\"hello @\" % (\"world\") == \"hello world\"", +}; +assert { + ok = "1 @ @" % (2, 3) == "1 2 3", + desc = "\"1 @ @\" % (2, 3) == \"1 2 3\"", +}; +assert { + ok = "@ or @" % (true, false) == "true or false", + desc = "\"@ or @\" % (true, false) == \"true or false\"", +}; +assert { + ok = "@" % (NULL) == "NULL", + desc = "\"@\" % (NULL) == \"NULL\"", +}; \ No newline at end of file diff --git a/integration_tests/functional_processing_test.ucg b/integration_tests/functional_processing_test.ucg index 77434a0..dd36777 100644 --- a/integration_tests/functional_processing_test.ucg +++ b/integration_tests/functional_processing_test.ucg @@ -18,48 +18,58 @@ let identity_list_reducer = macro(acc, item) => { result = acc + [item], }; -assert | - reduce identity_list_reducer.result [], list1 == list1; -|; +assert { + ok = reduce identity_list_reducer.result [], list1 == list1, + desc = "reduce identity_list_reducer.result [], list1 == list1", +}; let nested_list = { list = list1, }; -assert | - reduce identity_list_reducer.result [], (nested_list.list) == list1; -|; +assert { + ok = reduce identity_list_reducer.result [], (nested_list.list) == list1, + desc = "reduce identity_list_reducer.result [], (nested_list.list) == list1", +}; let list_reducer = macro(acc, item) => { result = acc + item, }; -assert | - reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4; -|; +assert { + ok = reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4, + desc = "reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4", +}; -assert | - map mapper.result list1 == [2, 3, 4, 5]; -|; -assert | - (map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]; -|; -assert | - map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5]; -|; +assert { + ok = map mapper.result list1 == [2, 3, 4, 5], + desc = "map mapper.result list1 == [2, 3, 4, 5]", +}; +assert { + ok = (map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5], + desc = "(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]", +}; +assert { + ok = map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5], + desc = "map mapper.result [1, 2, 3, 4] == [2, 3, 4, 5]", +}; -assert | - filter filtrator.result list2 == ["foo", "foo"]; -|; -assert | - (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"]; -|; -assert | - filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"]; -|; -assert | - filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]; -|; +assert { + ok = filter filtrator.result list2 == ["foo", "foo"], + desc = "filter filtrator.result list2 == [\"foo\", \"foo\"]", +}; +assert { + ok = (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"], + desc = "(filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]", +}; +assert { + ok = filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"], + desc = "filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"] == [\"foo\", \"foo\"]", +}; +assert { + ok = filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4], + desc = "filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]", +}; // Tuple processing let test_tpl = { @@ -71,9 +81,10 @@ let identity_tpl_mapper = macro(name, val) => { result = [name, val], }; -assert | - map identity_tpl_mapper.result test_tpl == test_tpl; -|; +assert { + ok = map identity_tpl_mapper.result test_tpl == test_tpl, + desc = "map identity_tpl_mapper.result test_tpl == test_tpl", +}; let tpl_mapper = macro(name, val) => { result = select name, [name, val], { @@ -82,9 +93,10 @@ let tpl_mapper = macro(name, val) => { }, }; -assert | - map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"}; -|; +assert { + ok = map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"}, + desc = "map tpl_mapper.result test_tpl == {foo = \"barbar\", cute = \"pygmy\"}", +}; let identity_tpl_filter = macro(name, val) => { result = true, @@ -95,13 +107,15 @@ let tpl_filter = macro(name, val) => { result = name != "foo", }; -assert | - filter identity_tpl_filter.result test_tpl == test_tpl; -|; +assert { + ok = filter identity_tpl_filter.result test_tpl == test_tpl, + desc = "filter identity_tpl_filter.result test_tpl == test_tpl", +}; -assert | - filter tpl_filter.result test_tpl == { quux = "baz" }; -|; +assert { + ok = filter tpl_filter.result test_tpl == { quux = "baz" }, + desc = "filter tpl_filter.result test_tpl == { quux = \"baz\" }", +}; let tpl_reducer = macro(acc, name, val) => { result = acc{ @@ -110,6 +124,7 @@ let tpl_reducer = macro(acc, name, val) => { }, }; -assert | - reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}; -|; \ No newline at end of file +assert { + ok = reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}, + desc = "reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}", +}; \ No newline at end of file diff --git a/integration_tests/include_test.ucg b/integration_tests/include_test.ucg index ebaa1d5..605f5c3 100644 --- a/integration_tests/include_test.ucg +++ b/integration_tests/include_test.ucg @@ -1,14 +1,17 @@ let script = include str "./include_example.sh"; -assert | - script == "#!/usr/bin/env bash -echo \"included\""; -|; +assert { + ok = script == "#!/usr/bin/env bash +echo \"included\"", + desc = "script was successfully included", +}; let expected = "IyEvdXNyL2Jpbi9lbnYgYmFzaAplY2hvICJpbmNsdWRlZCI="; let base64 = include b64 "./include_example.sh"; -assert | - base64 == expected; -|; +assert { + ok = base64 == expected, + desc = "base64 == expected", +}; let base64safe = include b64urlsafe "./include_example.sh"; -assert | - base64safe == expected; -|; \ No newline at end of file +assert { + ok = base64safe == expected, + desc = "base64safe == expected", +}; \ No newline at end of file diff --git a/integration_tests/list_test.ucg b/integration_tests/list_test.ucg index 2b3cf62..122a5c9 100644 --- a/integration_tests/list_test.ucg +++ b/integration_tests/list_test.ucg @@ -2,10 +2,12 @@ let empty_list = []; let concatenated = ["foo"] + ["bar"]; -assert | - concatenated == ["foo", "bar"]; -|; +assert { + ok = concatenated == ["foo", "bar"], + desc = "concatenated == [\"foo\", \"bar\"]", +}; -assert | - concatenated + empty_list == ["foo", "bar"]; -|; \ No newline at end of file +assert { + ok = concatenated + empty_list == ["foo", "bar"], + desc = "concatenated + empty_list == [\"foo\", \"bar\"]", +}; \ No newline at end of file diff --git a/integration_tests/macros_test.ucg b/integration_tests/macros_test.ucg index 43cd377..2d41892 100644 --- a/integration_tests/macros_test.ucg +++ b/integration_tests/macros_test.ucg @@ -19,31 +19,39 @@ let noargresult = noargmacro(); let simpleresult = simplemacro(1, 2, 3); let cplxresult = cplxmacro(1, "We", 3.0); -assert | - noargresult.field1 == "value"; -|; -assert | - simpleresult.field1 == 1; -|; -assert | - simpleresult.field2 == 2; -|; -assert | - simpleresult.field3 == 3; -|; +assert { + ok = noargresult.field1 == "value", + desc = "noargresult.field1 == \"value\"", +}; +assert { + ok = simpleresult.field1 == 1, + desc = "simpleresult.field1 == 1", +}; +assert { + ok = simpleresult.field2 == 2, + desc = "simpleresult.field2 == 2", +}; +assert { + ok = simpleresult.field3 == 3, + desc = "simpleresult.field3 == 3", +}; -assert | - cplxresult.field1 == 2; -|; -assert | - cplxresult.field2 == "We are here"; -|; -assert | - cplxresult.field3 == 2.0; -|; -assert | - cplxresult.boolfield == true; -|; +assert { + ok = cplxresult.field1 == 2, + desc = "cplxresult.field1 == 2", +}; +assert { + ok = cplxresult.field2 == "We are here", + desc = "cplxresult.field2 == \"We are here\"", +}; +assert { + ok = cplxresult.field3 == 2.0, + desc = "cplxresult.field3 == 2.0", +}; +assert { + ok = cplxresult.boolfield == true, + desc = "cplxresult.boolfield == true", +}; let macro_tpl_arg = macro(tpl) => { result = tpl.field, @@ -53,6 +61,7 @@ let arg_tpl = { field = "value", }; -assert | - macro_tpl_arg(arg_tpl).result == arg_tpl.field; -|; \ No newline at end of file +assert { + ok = macro_tpl_arg(arg_tpl).result == arg_tpl.field, + desc = "macro_tpl_arg(arg_tpl).result == arg_tpl.field", +}; \ No newline at end of file diff --git a/integration_tests/modules_test.ucg b/integration_tests/modules_test.ucg index 3387d06..a78ac8b 100644 --- a/integration_tests/modules_test.ucg +++ b/integration_tests/modules_test.ucg @@ -11,14 +11,16 @@ let test_simple_mod = module { }; let simple_mod_instance = test_simple_mod{}; -assert | - simple_mod_instance.value == "value"; -|; +assert { + ok = simple_mod_instance.value == "value", + desc = "simple_mod_instance.value == \"value\"", +}; let simple_mod_with_args = test_simple_mod{arg = "othervalue"}; -assert | - simple_mod_with_args.value == "othervalue"; -|; +assert { + ok = simple_mod_with_args.value == "othervalue", + desc = "simple_mod_with_args.value == \"othervalue\"", +}; let embedded_mod = module { deep_value = "None", @@ -41,19 +43,23 @@ let embedded_mod = module { let embedded_default_params = embedded_mod{}; -assert | - embedded_default_params.embedded.value == "None"; -|; +assert { + ok = embedded_default_params.embedded.value == "None", + desc = "embedded_default_params.embedded.value == \"None\"", +}; -assert | - embedded_default_params.env_name == "qa"; -|; +assert { + ok = embedded_default_params.env_name == "qa", + desc = "embedded_default_params.env_name == \"qa\"", +}; let embedded_with_params = embedded_mod{deep_value = "Some"}; -assert | - embedded_with_params.embedded.value == "Some"; -|; -assert | - embedded_mod{dep_value="Some"}.embedded_def{}.value == "None"; -|; \ No newline at end of file +assert { + ok = embedded_with_params.embedded.value == "Some", + desc = "embedded_with_params.embedded.value == \"Some\"", +}; +assert { + ok = embedded_mod{dep_value="Some"}.embedded_def{}.value == "None", + desc = "embedded_mod{dep_value=\"Some\"}.embedded_def{}.value == \"None\"", +}; \ No newline at end of file diff --git a/integration_tests/operator_precedence_test.ucg b/integration_tests/operator_precedence_test.ucg index 5204715..4bdc46c 100644 --- a/integration_tests/operator_precedence_test.ucg +++ b/integration_tests/operator_precedence_test.ucg @@ -1,42 +1,54 @@ -assert | - 2 * 2 + 1 == 5; -|; -assert | - 2 + 2 * 3 == 8; -|; -assert | - 2 * (2 + 1) == 6; -|; -assert | - 2 * 2 + 1 > 4; -|; -assert | - 2 * 2 + 1 < 6; -|; -assert | - 2 * 2 + 1 >= 5; -|; -assert | - 2 * 2 + 1 <= 5; -|; -assert | - 2 / 2 == 1; -|; -assert | - 2 - 1 == 1; -|; -assert | - 1 + 1 + 1 + 1 == 4; -|; -assert | - 1 + 1 + 2 * 2 + 1 + 1 == 1 + 1 + (2 * 2) + 1 + 1; -|; +assert { + ok = 2 * 2 + 1 == 5, + desc = "2 * 2 + 1 == 5", +}; +assert { + ok = 2 + 2 * 3 == 8, + desc = "2 + 2 * 3 == 8", +}; +assert { + ok = 2 * (2 + 1) == 6, + desc = "2 * (2 + 1) == 6", +}; +assert { + ok = 2 * 2 + 1 > 4, + desc = "2 * 2 + 1 > 4", +}; +assert { + ok = 2 * 2 + 1 < 6, + desc = "2 * 2 + 1 < 6", +}; +assert { + ok = 2 * 2 + 1 >= 5, + desc = "2 * 2 + 1 >= 5", +}; +assert { + ok = 2 * 2 + 1 <= 5, + desc = "2 * 2 + 1 <= 5", +}; +assert { + ok = 2 / 2 == 1, + desc = "2 / 2 == 1", +}; +assert { + ok = 2 - 1 == 1, + desc = "2 - 1 == 1", +}; +assert { + ok = 1 + 1 + 1 + 1 == 4, + desc = "1 + 1 + 1 + 1 == 4", +}; +assert { + ok = 1 + 1 + 2 * 2 + 1 + 1 == 1 + 1 + (2 * 2) + 1 + 1, + desc = "1 + 1 + 2 * 2 + 1 + 1 == 1 + 1 + (2 * 2) + 1 + 1", +}; let tpl = { one = { two = 12, }, }; -assert | - 1 + tpl.one.two * 2 + 3 == 28; -|; \ No newline at end of file +assert { + ok = 1 + tpl.one.two * 2 + 3 == 28, + desc = "1 + tpl.one.two * 2 + 3 == 28", +}; \ No newline at end of file diff --git a/integration_tests/select_expressions_test.ucg b/integration_tests/select_expressions_test.ucg index 6856e9e..0368b76 100644 --- a/integration_tests/select_expressions_test.ucg +++ b/integration_tests/select_expressions_test.ucg @@ -11,12 +11,14 @@ let defaultgot = select badwant, "OOPS", { door2 = "you lose", }; -assert | - got == "grand prize"; -|; -assert | - defaultgot == "OOPS"; -|; +assert { + ok = got == "grand prize", + desc = "got == \"grand prize\"", +}; +assert { + ok = defaultgot == "OOPS", + desc = "defaultgot == \"OOPS\"", +}; // select inside a macro @@ -29,21 +31,25 @@ let condmacro = macro(arg) => { let result = condmacro("opt1"); -assert | - condmacro("opt1") == {output = "yay"}; -|; -assert | - condmacro("opt2") == {output = "boo"}; -|; -assert | - condmacro("invalid") == {output = NULL}; -|; +assert { + ok = condmacro("opt1") == {output = "yay"}, + desc = "condmacro(\"opt1\") == {output = \"yay\"}", +}; +assert { + ok = condmacro("opt2") == {output = "boo"}, + desc = "condmacro(\"opt2\") == {output = \"boo\"}", +}; +assert { + ok = condmacro("invalid") == {output = NULL}, + desc = "condmacro(\"invalid\") == {output = NULL}", +}; let iflike = select true, "default", { true = "I was true", false = "I was false", }; -assert | - iflike == "I was true"; -|; \ No newline at end of file +assert { + ok = iflike == "I was true", + desc = "iflike == \"I was true\"", +}; \ No newline at end of file diff --git a/integration_tests/selectors_test.ucg b/integration_tests/selectors_test.ucg index ae441e1..b936d73 100644 --- a/integration_tests/selectors_test.ucg +++ b/integration_tests/selectors_test.ucg @@ -10,44 +10,55 @@ let testmacro = macro(arg) => { output = arg, }; -assert | - list.0 == 1; -|; -assert | - list.1 == 2; -|; -assert | - list.3 == 4; -|; -assert | - tuple.field1 == 1; -|; -assert | - tuple.field2 == 3; -|; -assert | - tuple.deeplist.0 == "foo"; -|; -assert | - tuple.deeplist.1 == "bar"; -|; -assert | - {foo = "bar"}.foo == "bar"; -|; -assert | - ["one", "two", "three"].0 == "one"; -|; +assert { + ok = list.0 == 1, + desc = "list.0 == 1", +}; +assert { + ok = list.1 == 2, + desc = "list.1 == 2", +}; +assert { + ok = list.3 == 4, + desc = "list.3 == 4", +}; +assert { + ok = tuple.field1 == 1, + desc = "tuple.field1 == 1", +}; +assert { + ok = tuple.field2 == 3, + desc = "tuple.field2 == 3", +}; +assert { + ok = tuple.deeplist.0 == "foo", + desc = "tuple.deeplist.0 == \"foo\"", +}; +assert { + ok = tuple.deeplist.1 == "bar", + desc = "tuple.deeplist.1 == \"bar\"", +}; +assert { + ok = {foo = "bar"}.foo == "bar", + desc = "{foo = \"bar\"}.foo == \"bar\"", +}; +assert { + ok = ["one", "two", "three"].0 == "one", + desc = "[\"one\", \"two\", \"three\"].0 == \"one\"", +}; let macro_for_test = macro() => { foo = "bar", }; -assert | - macro_for_test().foo == "bar"; -|; +assert { + ok = macro_for_test().foo == "bar", + desc = "macro_for_test().foo == \"bar\"", +}; let mymodule = module { foo = "bar" } => { let foo = mod.foo; }; -assert | - mymodule{}.foo == "bar"; -|; \ No newline at end of file +assert { + ok = mymodule{}.foo == "bar", + desc = "mymodule{}.foo == \"bar\"", +}; \ No newline at end of file diff --git a/integration_tests/tuple_test.ucg b/integration_tests/tuple_test.ucg index 953e9a1..aa2fdc0 100644 --- a/integration_tests/tuple_test.ucg +++ b/integration_tests/tuple_test.ucg @@ -15,33 +15,42 @@ let nestedtpl = { maybe_val = NULL, }; -assert | - simpletpl.foo == "bar"; -|; -assert | - stringfieldtpl."field 1" == 1; -|; -assert | - nestedtpl.scalar == 1; -|; -assert | - nestedtpl.maybe_val == NULL; -|; -assert | - nestedtpl.inner.field == "value"; -|; -assert | - nestedtpl.list.0 == 1; -|; -assert | - nestedtpl.list.1 == 2; -|; -assert | - nestedtpl.list.2 == 3; -|; -assert | - nestedtpl.list.3 == 4; -|; +assert { + ok = simpletpl.foo == "bar", + desc = "simpletpl.foo == \"bar\"", +}; +assert { + ok = stringfieldtpl."field 1" == 1, + desc = "stringfieldtpl.\"field 1\" == 1", +}; +assert { + ok = nestedtpl.scalar == 1, + desc = "nestedtpl.scalar == 1", +}; +assert { + ok = nestedtpl.maybe_val == NULL, + desc = "nestedtpl.maybe_val == NULL", +}; +assert { + ok = nestedtpl.inner.field == "value", + desc = "nestedtpl.inner.field == \"value\"", +}; +assert { + ok = nestedtpl.list.0 == 1, + desc = "nestedtpl.list.0 == 1", +}; +assert { + ok = nestedtpl.list.1 == 2, + desc = "nestedtpl.list.1 == 2", +}; +assert { + ok = nestedtpl.list.2 == 3, + desc = "nestedtpl.list.2 == 3", +}; +assert { + ok = nestedtpl.list.3 == 4, + desc = "nestedtpl.list.3 == 4", +}; let nestedcopy = nestedtpl{ inner = self.inner{ @@ -62,18 +71,22 @@ let deepnestedcopy = nestedcopy{ }, }; -assert | - nestedcopy.inner.field2 == 2; -|; -assert | - nestedcopy.inner.inner.field3 == "three"; -|; -assert | - deepnestedcopy.inner.inner.field4 == 4; -|; -assert | - deepnestedcopy.maybe_val == "some val"; -|; +assert { + ok = nestedcopy.inner.field2 == 2, + desc = "nestedcopy.inner.field2 == 2", +}; +assert { + ok = nestedcopy.inner.inner.field3 == "three", + desc = "nestedcopy.inner.inner.field3 == \"three\"", +}; +assert { + ok = deepnestedcopy.inner.inner.field4 == 4, + desc = "deepnestedcopy.inner.inner.field4 == 4", +}; +assert { + ok = deepnestedcopy.maybe_val == "some val", + desc = "deepnestedcopy.maybe_val == \"some val\"", +}; let base_maybe = { real = "A real value", @@ -83,38 +96,43 @@ let copy_maybe = base_maybe{ real = NULL, }; -assert | - copy_maybe.real == NULL; -|; +assert { + ok = copy_maybe.real == NULL, + desc = "copy_maybe.real == NULL", +}; let quotedself_tpl = { "self" = "myself", }; -assert | - quotedself_tpl."self" == "myself"; -|; +assert { + ok = quotedself_tpl."self" == "myself", + desc = "quotedself_tpl.\"self\" == \"myself\"", +}; let quotedenv_tpl = { "env" = "myenv", }; -assert | - quotedenv_tpl."env" == "myenv"; -|; +assert { + ok = quotedenv_tpl."env" == "myenv", + desc = "quotedenv_tpl.\"env\" == \"myenv\"", +}; let unquotedenv_tpl = { env = "myenv", }; -assert | - unquotedenv_tpl."env" == "myenv"; -|; +assert { + ok = unquotedenv_tpl."env" == "myenv", + desc = "unquotedenv_tpl.\"env\" == \"myenv\"", +}; let unquotedself_tpl = { self = "myself", }; -assert | - unquotedself_tpl."self" == "myself"; -|; +assert { + ok = unquotedself_tpl."self" == "myself", + desc = "unquotedself_tpl.\"self\" == \"myself\"", +}; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9d5ec94..91038f1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -742,7 +742,7 @@ pub enum Statement { Import(ImportDef), // Assert statement - Assert(Token), + Assert(Expression), // Identify an Expression for output. Output(Token, Expression), diff --git a/src/build/mod.rs b/src/build/mod.rs index 32f97d4..7f0fdab 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -457,7 +457,7 @@ impl<'a> FileBuilder<'a> { fn eval_stmt(&mut self, stmt: &Statement) -> Result, Box> { let child_scope = self.scope.clone(); match stmt { - &Statement::Assert(ref expr) => self.build_assert(&expr), + &Statement::Assert(ref expr) => self.build_assert(&expr, &child_scope), &Statement::Let(ref def) => self.eval_let(def), &Statement::Import(ref def) => self.eval_import(def), &Statement::Expression(ref expr) => self.eval_expr(expr, &child_scope), @@ -1360,22 +1360,29 @@ impl<'a> FileBuilder<'a> { }; } - fn build_assert(&mut self, tok: &Token) -> Result, Box> { + fn record_assert_result(&mut self, msg: &str, is_success: bool) { + let msg = format!("{}\n", msg); + self.assert_collector.summary.push_str(&msg); + if !is_success { + self.assert_collector.failures.push_str(&msg); + self.assert_collector.success = false; + } + } + + fn build_assert( + &mut self, + expr: &Expression, + scope: &Scope, + ) -> Result, Box> { if !self.validate_mode { // we are not in validate_mode then build_asserts are noops. return Ok(Rc::new(Val::Empty)); } - let expr = &tok.fragment; - let assert_input = - OffsetStrIter::new_with_offsets(expr, tok.pos.line - 1, tok.pos.column - 1); - let ok = match self.eval_input(assert_input) { + let ok = match self.eval_expr(expr, scope) { Ok(v) => v, Err(e) => { // failure! - let msg = format!( - "NOT OK - '{}' at line: {} column: {}\n\tCompileError: {}\n", - expr, tok.pos.line, tok.pos.column, e - ); + let msg = format!("CompileError: {}\n", e); self.assert_collector.summary.push_str(&msg); self.assert_collector.failures.push_str(&msg); self.assert_collector.success = false; @@ -1383,34 +1390,69 @@ impl<'a> FileBuilder<'a> { } }; - if let &Val::Boolean(b) = ok.as_ref() { - // record the assertion result. - if b { - // success! - let msg = format!( - "OK - '{}' at line: {} column: {}\n", - expr, tok.pos.line, tok.pos.column - ); - self.assert_collector.summary.push_str(&msg); - } else { - // failure! - let msg = format!( - "NOT OK - '{}' at line: {} column: {}\n", - expr, tok.pos.line, tok.pos.column - ); - self.assert_collector.summary.push_str(&msg); - self.assert_collector.failures.push_str(&msg); - self.assert_collector.success = false; + match ok.as_ref() { + &Val::Tuple(ref fs) => { + let ok_field = match find_in_fieldlist("ok", fs) { + Some(ref val) => match val.as_ref() { + &Val::Boolean(b) => b, + _ => { + let msg = format!( + "TYPE FAIL - Expected Boolean field ok in tuple {}, line: {} column: {}", + ok.as_ref(), expr.pos().line, expr.pos().column + ); + self.record_assert_result(&msg, false); + return Ok(Rc::new(Val::Empty)); + } + }, + None => { + let msg = format!( + "TYPE FAIL - Expected Boolean field ok in tuple {}, line: {} column: {}", + ok.as_ref(), expr.pos().line, expr.pos().column + ); + self.record_assert_result(&msg, false); + return Ok(Rc::new(Val::Empty)); + } + }; + let desc = match find_in_fieldlist("desc", fs) { + Some(ref val) => match val.as_ref() { + Val::Str(ref s) => s.clone(), + _ => { + let msg = format!( + "TYPE FAIL - Expected Boolean field desc in tuple {} line: {} column: {}", + ok, expr.pos().line, expr.pos().column + ); + self.record_assert_result(&msg, false); + return Ok(Rc::new(Val::Empty)); + } + }, + None => { + let msg = format!( + "TYPE FAIL - Expected Boolean field desc in tuple {} line: {} column: {}\n", + ok, expr.pos().line, expr.pos().column + ); + self.record_assert_result(&msg, false); + return Ok(Rc::new(Val::Empty)); + } + }; + self.record_assert_result(&desc, ok_field); + } + &Val::Empty + | &Val::Boolean(_) + | &Val::Env(_) + | &Val::Float(_) + | &Val::Int(_) + | &Val::Str(_) + | &Val::List(_) + | &Val::Macro(_) + | &Val::Module(_) => { + // record an assertion type-failure result. + let msg = format!( + "TYPE FAIL - Expected tuple with ok and desc fields got {} at line: {} column: {}\n", + ok, expr.pos().line, expr.pos().column + ); + self.record_assert_result(&msg, false); + return Ok(Rc::new(Val::Empty)); } - } else { - // record an assertion type-failure result. - let msg = format!( - "TYPE FAIL - '{}' Expected Boolean got {} at line: {} column: {}\n", - expr, ok, tok.pos.line, tok.pos.column - ); - self.assert_collector.failures.push_str(&msg); - self.assert_collector.success = false; - self.assert_collector.summary.push_str(&msg); } Ok(ok) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 84ad425..312e01c 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -736,9 +736,9 @@ make_fn!( assert_statement, Statement>, do_each!( _ => word!("assert"), - tok => must!(match_type!(PIPEQUOTE)), - _ => must!(punct!(";")), - (Statement::Assert(tok.clone())) + expr => must!(expression), + _ => must!(punct!(";")), + (Statement::Assert(expr)) ) ); diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 293ecc0..1f1e1e7 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -84,20 +84,6 @@ make_fn!(strtok, ) ); -make_fn!(pipequotetok, - do_each!( - p => input!(), - _ => text_token!("|"), - frag => until!(text_token!("|")), - _ => text_token!("|"), - (Token{ - typ: TokenType::PIPEQUOTE, - pos: Position::from(&p), - fragment: frag.to_string(), - }) - ) -); - make_fn!(barewordtok, do_each!( span => input!(), @@ -378,7 +364,6 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result, Token> { either!( input, strtok, - pipequotetok, emptytok, // This must come before the barewordtok digittok, commatok, @@ -528,14 +513,6 @@ macro_rules! match_type { match_type!($i, STR => token_clone) }; - ($i:expr,PIPEQUOTE => $h:expr) => { - match_type!($i, TokenType::PIPEQUOTE, "Not a Pipe Quoted String", $h) - }; - - ($i:expr,PIPEQUOTE) => { - match_type!($i, PIPEQUOTE => token_clone) - }; - ($i:expr,DIGIT => $h:expr) => { match_type!($i, TokenType::DIGIT, "Not a DIGIT", $h) }; diff --git a/src/tokenizer/test.rs b/src/tokenizer/test.rs index 0765e3a..66a6eff 100644 --- a/src/tokenizer/test.rs +++ b/src/tokenizer/test.rs @@ -74,19 +74,6 @@ fn test_escape_quoted() { } } -#[test] -fn test_pipe_quoted() { - let result = pipequotetok(OffsetStrIter::new("|foo|")); - assert!( - result.is_complete(), - format!("result {:?} is not ok", result) - ); - if let Result::Complete(_, tok) = result { - assert_eq!(tok.fragment, "foo".to_string()); - assert_eq!(tok.typ, TokenType::PIPEQUOTE); - } -} - #[test] fn test_string_with_escaping() { let result = strtok(OffsetStrIter::new("\"foo \\\\ \\\"bar\"")); diff --git a/std/lists.ucg b/std/lists.ucg new file mode 100644 index 0000000..ca2ffd7 --- /dev/null +++ b/std/lists.ucg @@ -0,0 +1,42 @@ +let str_join = module{ + sep=" ", + list=NULL, +} => { + let joiner = macro(acc, item) => { + result = select (acc.out == ""), NULL, { + true = acc{ + out="@@" % (acc.out,item), + }, + false = acc{ + out="@@@" % (acc.out, acc.sep, item), + }, + }, + }; + + let result = (reduce joiner.result {sep=mod.sep, out=""}, (mod.list)).out; +}; + +let len = module{ + list = NULL, +} => { + let counter = macro(acc, item) => { + result = acc + 1, + }; + + let result = reduce counter.result 0, (mod.list); +}; + +let enumerate = module{ + start = 0, + step = 1, + list = NULL, +} => { + let reducer = macro(acc, item) => { + result = acc{count = acc.count + acc.step, list=acc.list + [[acc.count, item]]}, + }; + + let acc = {count=mod.start, list=[], step=mod.step}; + + let enumerated = reduce reducer.result acc, (mod.list); + let result = enumerated.list; +}; \ No newline at end of file diff --git a/std/testing.ucg b/std/testing.ucg new file mode 100644 index 0000000..d531beb --- /dev/null +++ b/std/testing.ucg @@ -0,0 +1,88 @@ +// A module with helper test assertions for ucg tests. +// Each contained module will output a struct with +// ok and desc fields. +// +// ok will be a boolean. +// desc will be a descriptive message. +let asserts = module{ + // allows you to specify a a prefix for messages. + ok_prefix = "OK - ", + // specify the prefix to add when the condition is not true. + not_prefix = "NOT ", + // A default description to use for messages. + default_description = "TODO description", + } => { + + // Test that a value is true. + let ok = module{ + ok_prefix = mod.ok_prefix, + not_prefix = mod.not_prefix, + // test value expected to be true for success. + test=false, + // descriptive message to use in output. + desc=mod.default_description, + } => { + let ok = mod.test; + + let desc = select ok, "NOT OK - ", { + true = "@@" % (mod.ok_prefix, mod.desc), + false = "@@@" % (mod.not_prefix, mod.ok_prefix, mod.desc), + }; + }; + + // Test that a value is not true. + let not_ok = module{ + ok_prefix = mod.ok_prefix, + not_prefix = mod.not_prefix, + // Test value expected to to be false for success. + test=true, + // descriptive message to use in output. + desc=mod.default_description, + } => { + let ok = mod.test != true; + + let desc_prefix = select ok, "", { + true = "@@" % (mod.ok_prefix, mod.desc), + false = "@@@" % (mod.not_prefix, mod.ok_prefix, mod.desc), + }; + + let desc = select (mod.desc == ""), "", { + true = "@ not succcessful" % (desc_prefix), + false = "@" % (desc_prefix), + }; + }; + + // Asserts that two values are equal. Does deep equal comparisons. + let equal = module{ + ok_prefix = mod.ok_prefix, + not_prefix = mod.not_prefix, + // Left value for deep equal comparison. + left=NULL, + // right value for deep equal comparison. + right=NULL, + } => { + let ok = mod.left == mod.right; + + let desc = select ok, "", { + true = "@@ == @" % (mod.ok_prefix, mod.left, mod.right), + false = "@@@ != @" % (mod.not_prefix, mod.ok_prefix, mod.left, mod.right), + }; + }; + + // Asserts that two values are not equal. Does deep equal comparisons. + let not_equal = module{ + ok_prefix = mod.ok_prefix, + not_prefix = mod.not_prefix, + // Left value for deep equal comparison. + left=NULL, + // right value for deep equal comparison. + right=NULL, + } => { + let ok = mod.left != mod.right; + + let desc = select ok, "", { + true = "@@ != @" % (mod.ok_prefix, mod.left, mod.right), + false = "@@@ == @" % (mod.not_prefix, mod.ok_prefix, mod.left, mod.right), + }; + }; +}; \ No newline at end of file diff --git a/std/tests/lists_test.ucg b/std/tests/lists_test.ucg new file mode 100644 index 0000000..8bd795f --- /dev/null +++ b/std/tests/lists_test.ucg @@ -0,0 +1,35 @@ +import "../lists.ucg" as list; +import "../testing.ucg" as t; + +let list_to_join = [1, 2, 3]; + +let asserts = t.asserts{}; + +assert asserts.equal{ + left=list.str_join{sep=",", list=list_to_join}.result, + right="1,2,3" +}; + +assert asserts.equal{ + left=list.len{list=[0, 1, 2, 3]}.result, + right=4, +}; + +assert asserts.equal{ + left=list.enumerate{list=["foo", "bar"]}.result, + right=[[0, "foo"], [1, "bar"]], +}; + +assert asserts.equal{ + left=list.enumerate{start=1, list=["foo", "bar"]}.result, + right=[[1, "foo"], [2, "bar"]], +}; + +assert asserts.equal{ + left=list.enumerate{ + start=1, + step=2, + list=["foo", "bar"] + }.result, + right=[[1, "foo"], [3, "bar"]], +}; \ No newline at end of file diff --git a/std/tests/testing_test.ucg b/std/tests/testing_test.ucg new file mode 100644 index 0000000..086067b --- /dev/null +++ b/std/tests/testing_test.ucg @@ -0,0 +1,69 @@ +import "../testing.ucg" as t; + +let asserts = t.asserts{}; + +let not_equal_result = asserts.not_equal{ + left=1, + right=2, +}; +assert not_equal_result; +assert asserts.equal{ + left=not_equal_result.desc, + right="OK - 1 != 2", +}; + +let bad_not_equal_result = asserts.not_equal{ + left=1, + right=1, +}; +assert asserts.not_ok{test=bad_not_equal_result.ok}; +assert asserts.equal{ + left=bad_not_equal_result.desc, + right="NOT OK - 1 == 1", +}; + + +let equal_result = asserts.equal{ + left=1, + right=1, +}; +assert equal_result; +assert asserts.equal{ + left=equal_result.desc, + right="OK - 1 == 1", +}; + +let bad_equal_result = asserts.equal{ + left=1, + right=2, +}; +assert asserts.equal{ + left=bad_equal_result.desc, + right="NOT OK - 1 != 2", +}; + +let ok_result = asserts.ok{ + test=true, +}; +assert ok_result; +assert asserts.equal{ + left=ok_result.desc, + right="OK - TODO description", +}; + +let bad_ok_result = t.asserts.ok{ + test=false, +}; +assert asserts.equal{ + left=bad_ok_result.desc, + right="NOT OK - TODO description", +}; + +let not_ok_result = asserts.not_ok{ + test=false, +}; +assert not_ok_result; +assert asserts.equal{ + left=not_ok_result.desc, + right="OK - TODO description", +}; \ No newline at end of file