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
This commit is contained in:
Jeremy Wall 2019-01-08 20:32:16 -06:00
parent f022e71c0c
commit d989e47706
26 changed files with 847 additions and 459 deletions

View File

@ -3,3 +3,4 @@ script:
- cargo build --verbose
- cargo test --verbose
- cargo run -- test integration_tests
- cargo run -- test -r std/tests

15
Makefile Normal file
View File

@ -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

View File

@ -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

View File

@ -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;|;
assert {let env = 1;};

View File

@ -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",
},
desc = "\"foo\" in {
foo = \"bar\",
}
",
};
|;
assert |
foo in {
assert {
ok = foo in {
foo = "bar",
},
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" in ["foo", "bar"];
|;
assert |
"foo" in ["bar", "foo", "bar"];
|;
assert |
{ foo = 1 } in ["foo", { foo = 1 }];
|;
assert |
true in [ "foo" in {foo = 1}, false ];
|;
assert |
"foo" ~ "o+";
|;
assert {
ok = "foo" ~ "o+",
desc = "\"foo\" ~ \"o+\""
};
assert |
"foo" !~ "bar";
|;
assert {
ok = "foo" !~ "bar",
desc = "\"foo\" !~ \"bar\"",
};

View File

@ -1,6 +1,8 @@
assert |
"hello " + "world" == "hello world";
|;
assert |
[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6];
|;
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]",
};

View File

@ -2,6 +2,7 @@ let empty = NULL;
let tpl = {
foo = NULL,
};
assert |
tpl.foo == empty;
|;
assert {
ok = tpl.foo == empty,
desc = "tpl.foo == empty",
};

View File

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

View File

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

View File

@ -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;
|;
assert {
ok = base64safe == expected,
desc = "base64safe == expected",
};

View File

@ -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"];
|;
assert {
ok = concatenated + empty_list == ["foo", "bar"],
desc = "concatenated + empty_list == [\"foo\", \"bar\"]",
};

View File

@ -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;
|;
assert {
ok = macro_tpl_arg(arg_tpl).result == arg_tpl.field,
desc = "macro_tpl_arg(arg_tpl).result == arg_tpl.field",
};

View File

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

View File

@ -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;
|;
assert {
ok = 1 + tpl.one.two * 2 + 3 == 28,
desc = "1 + tpl.one.two * 2 + 3 == 28",
};

View File

@ -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";
|;
assert {
ok = iflike == "I was true",
desc = "iflike == \"I was true\"",
};

View File

@ -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";
|;
assert {
ok = mymodule{}.foo == "bar",
desc = "mymodule{}.foo == \"bar\"",
};

View File

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

View File

@ -742,7 +742,7 @@ pub enum Statement {
Import(ImportDef),
// Assert statement
Assert(Token),
Assert(Expression),
// Identify an Expression for output.
Output(Token, Expression),

View File

@ -457,7 +457,7 @@ impl<'a> FileBuilder<'a> {
fn eval_stmt(&mut self, stmt: &Statement) -> Result<Rc<Val>, Box<dyn Error>> {
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<Rc<Val>, Box<dyn Error>> {
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<Rc<Val>, Box<dyn Error>> {
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!
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!(
"OK - '{}' at line: {} column: {}\n",
expr, tok.pos.line, tok.pos.column
"TYPE FAIL - Expected Boolean field ok in tuple {}, line: {} column: {}",
ok.as_ref(), expr.pos().line, expr.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;
self.record_assert_result(&msg, false);
return Ok(Rc::new(Val::Empty));
}
} else {
},
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 Boolean got {} at line: {} column: {}\n",
expr, ok, tok.pos.line, tok.pos.column
"TYPE FAIL - Expected tuple with ok and desc fields got {} at line: {} column: {}\n",
ok, expr.pos().line, expr.pos().column
);
self.assert_collector.failures.push_str(&msg);
self.assert_collector.success = false;
self.assert_collector.summary.push_str(&msg);
self.record_assert_result(&msg, false);
return Ok(Rc::new(Val::Empty));
}
}
Ok(ok)
}

View File

@ -736,9 +736,9 @@ make_fn!(
assert_statement<SliceIter<Token>, Statement>,
do_each!(
_ => word!("assert"),
tok => must!(match_type!(PIPEQUOTE)),
expr => must!(expression),
_ => must!(punct!(";")),
(Statement::Assert(tok.clone()))
(Statement::Assert(expr))
)
);

View File

@ -84,20 +84,6 @@ make_fn!(strtok<OffsetStrIter, Token>,
)
);
make_fn!(pipequotetok<OffsetStrIter, Token>,
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<OffsetStrIter, Token>,
do_each!(
span => input!(),
@ -378,7 +364,6 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, 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)
};

View File

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

42
std/lists.ucg Normal file
View File

@ -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;
};

88
std/testing.ucg Normal file
View File

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

35
std/tests/lists_test.ucg Normal file
View File

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

View File

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