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 build --verbose
- cargo test --verbose - cargo test --verbose
- cargo run -- test integration_tests - 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 ### Assert Statements
The assert statement defines an expression that must evaluate to either true or false. The assert statement defines an expression that must evaluate to tuple with an
Assert statements are noops except during a validation compile. They give you a way to ok field that is either true or false and a desc field that is a string. Assert
assert certains properties about your data and can be used as a form of unit testing statements are noops except during a validation compile. They give you a way to
for your configurations. It starts with the `assert` keyword followed by a valid block assert certains properties about your data and can be used as a form of unit
of UCG statements delimited by `|` characters. The final statement in the block testing for your configurations. It starts with the `assert` keyword followed
must evaluate to a boolean expression. by a valid ucg expression.
``` ```
assert | assert {
host == "www.example.com"; ok = host == "www.example.com",
|; desc = "Host is www.example.com",
};
assert | assert {
select qa, 443, { ok = select qa, 443, {
qa = 80, qa = 80,
prod = 443, prod = 443,
} == 443; } == 443,
|; desc = "select default was 443",
};
``` ```
Assert statements are only evaluated when running the `ucg test` command. That Assert statements are only evaluated when running the `ucg test` command. That

View File

@ -1,17 +1,17 @@
assert |macro |; assert {macro };
assert |{ foo = hello world}|; assert {{ foo = hello world}};
assert |{ foo = "hello world"|; assert {{ foo = "hello world"};
assert |{ = }|; assert {{ = }};
assert |"|; assert {"};
assert |=|; assert {=};
assert |let |; assert {let };
assert |let foo |; assert {let foo };
assert |let foo =|; assert {let foo =};
assert |import |; assert {import };
assert |import foo |; assert {import foo };
assert |out |; assert {out };
assert |out "|; assert {out "};
assert |out json|; 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 list2 = list;
let list3 = [1, 2]; let list3 = [1, 2];
assert | assert {
one == one; ok = one == one,
|; desc = "one == one",
assert | };
one == one; assert {
|; ok = one == one,
assert | desc = "one == one",
one >= one; };
|; assert {
assert | ok = one >= one,
two > one; desc = "one >= one"
|; };
assert | assert {
two >= two; ok = two > one,
|; desc = "two > one"
assert | };
tpl1 == tpl2; assert {
|; ok = two >= two,
assert | desc = "two >= two",
tpl1 != tpl3; };
|; assert {
assert | ok = tpl1 == tpl2,
list == list2; desc = "tpl1 == tpl2"
|; };
assert | assert {
list != list3; ok = tpl1 != tpl3,
|; desc = "tpl1 != tpl3",
};
assert {
ok = list == list2,
desc = "list == list2",
};
assert {
ok = list != list3,
desc = "list != list3",
};
// Deep Comparisons // Deep Comparisons
let tpl4 = { let tpl4 = {
@ -58,73 +67,105 @@ let less = {
foo = "bar" foo = "bar"
}; };
assert | assert {
tpl4.inner == copy.inner; ok = tpl4.inner == copy.inner,
|; desc = "tpl4.inner == copy.inner",
assert | };
tpl4.inner.fld == copy.inner.fld; assert {
|; ok = tpl4.inner.fld == copy.inner.fld,
assert | desc = "tpl4.inner.fld == copy.inner.fld",
tpl4.lst == copy.lst; };
|; assert {
assert | ok = tpl4.lst == copy.lst,
tpl4.foo == copy.foo; desc = "tpl4.lst == copy.lst",
|; };
assert | assert {
tpl4 == copy; ok = tpl4.foo == copy.foo,
|; desc = "tpl4.foo == copy.foo",
assert | };
tpl4 != extra; assert {
|; ok = tpl4 == copy,
assert | desc = "tpl4 == copy",
tpl4 != less; };
|; assert {
ok = tpl4 != extra,
desc = "tpl4 != extra",
};
assert {
ok = tpl4 != less,
desc = "tpl4 != less",
};
assert | assert {
[[1, 2, [3]], 4] == [[1, 2, [3]], 4]; ok = [[1, 2, [3]], 4] == [[1, 2, [3]], 4],
|; desc = "[[1, 2, [3]], 4] == [[1, 2, [3]], 4]",
};
assert | assert {
[[1, 2, [3]], 4] != [[1, 2, [6]], 4]; ok = [[1, 2, [3]], 4] != [[1, 2, [6]], 4],
|; desc = "[[1, 2, [3]], 4] != [[1, 2, [6]], 4]",
};
// Expression comparisons // Expression comparisons
assert |2 == 1+1;|; assert {
assert |(1+1) == 2;|; ok = 2 == 1+1,
assert |(1+1) == (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"; let want = "foo";
assert | assert {
select want, 1, { foo=2, } == 2; ok = select want, 1, { foo=2, } == 2,
|; desc = "select want, 1, { foo=2, } == 2",
};
// Contains comparison operators. // Contains comparison operators.
assert | assert {
"foo" in { ok = "foo" in {
foo = "bar", foo = "bar",
},
desc = "\"foo\" in {
foo = \"bar\",
}
",
}; };
|; assert {
assert | ok = foo in {
foo in {
foo = "bar", 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 | assert {
"foo" ~ "o+"; ok = "foo" ~ "o+",
|; desc = "\"foo\" ~ \"o+\""
};
assert | assert {
"foo" !~ "bar"; ok = "foo" !~ "bar",
|; desc = "\"foo\" !~ \"bar\"",
};

View File

@ -1,6 +1,8 @@
assert | assert {
"hello " + "world" == "hello world"; ok = "hello " + "world" == "hello world",
|; desc = "\"hello \" + \"world\" == \"hello world\"",
assert | };
[1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]; 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 = { let tpl = {
foo = NULL, foo = NULL,
}; };
assert | assert {
tpl.foo == empty; ok = tpl.foo == empty,
|; desc = "tpl.foo == empty",
};

View File

@ -1,12 +1,16 @@
assert | assert {
"hello @" % ("world") == "hello world"; ok = "hello @" % ("world") == "hello world",
|; desc = "\"hello @\" % (\"world\") == \"hello world\"",
assert | };
"1 @ @" % (2, 3) == "1 2 3"; assert {
|; ok = "1 @ @" % (2, 3) == "1 2 3",
assert | desc = "\"1 @ @\" % (2, 3) == \"1 2 3\"",
"@ or @" % (true, false) == "true or false"; };
|; assert {
assert | ok = "@ or @" % (true, false) == "true or false",
"@" % (NULL) == "NULL"; 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], result = acc + [item],
}; };
assert | assert {
reduce identity_list_reducer.result [], list1 == list1; ok = reduce identity_list_reducer.result [], list1 == list1,
|; desc = "reduce identity_list_reducer.result [], list1 == list1",
};
let nested_list = { let nested_list = {
list = list1, list = list1,
}; };
assert | assert {
reduce identity_list_reducer.result [], (nested_list.list) == list1; 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) => { let list_reducer = macro(acc, item) => {
result = acc + item, result = acc + item,
}; };
assert | assert {
reduce list_reducer.result 0, list1 == 0 + 1 + 2 + 3 + 4; 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 | assert {
map mapper.result list1 == [2, 3, 4, 5]; ok = map mapper.result list1 == [2, 3, 4, 5],
|; desc = "map mapper.result list1 == [2, 3, 4, 5]",
assert | };
(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]; assert {
|; ok = (map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5],
assert | desc = "(map mapper.result [1, 2, 3, 4]) == [2, 3, 4, 5]",
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 | assert {
filter filtrator.result list2 == ["foo", "foo"]; ok = filter filtrator.result list2 == ["foo", "foo"],
|; desc = "filter filtrator.result list2 == [\"foo\", \"foo\"]",
assert | };
(filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"]; assert {
|; ok = (filter filtrator.result ["foo", "bar", "foo", "bar"]) == ["foo", "foo"],
assert | desc = "(filter filtrator.result [\"foo\", \"bar\", \"foo\", \"bar\"]) == [\"foo\", \"foo\"]",
filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"]; };
|; assert {
assert | ok = filter filtrator.result ["foo", "bar", "foo", "bar"] == ["foo", "foo"],
filter boolfiltrator.result [1, 2, 3, 4, 5, 6, 7] == [1, 2, 3, 4]; 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 // Tuple processing
let test_tpl = { let test_tpl = {
@ -71,9 +81,10 @@ let identity_tpl_mapper = macro(name, val) => {
result = [name, val], result = [name, val],
}; };
assert | assert {
map identity_tpl_mapper.result test_tpl == test_tpl; 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) => { let tpl_mapper = macro(name, val) => {
result = select name, [name, val], { result = select name, [name, val], {
@ -82,9 +93,10 @@ let tpl_mapper = macro(name, val) => {
}, },
}; };
assert | assert {
map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"}; 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) => { let identity_tpl_filter = macro(name, val) => {
result = true, result = true,
@ -95,13 +107,15 @@ let tpl_filter = macro(name, val) => {
result = name != "foo", result = name != "foo",
}; };
assert | assert {
filter identity_tpl_filter.result test_tpl == test_tpl; ok = filter identity_tpl_filter.result test_tpl == test_tpl,
|; desc = "filter identity_tpl_filter.result test_tpl == test_tpl",
};
assert | assert {
filter tpl_filter.result test_tpl == { quux = "baz" }; 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) => { let tpl_reducer = macro(acc, name, val) => {
result = acc{ result = acc{
@ -110,6 +124,7 @@ let tpl_reducer = macro(acc, name, val) => {
}, },
}; };
assert | assert {
reduce tpl_reducer.result {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}; 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"; let script = include str "./include_example.sh";
assert | assert {
script == "#!/usr/bin/env bash ok = script == "#!/usr/bin/env bash
echo \"included\""; echo \"included\"",
|; desc = "script was successfully included",
};
let expected = "IyEvdXNyL2Jpbi9lbnYgYmFzaAplY2hvICJpbmNsdWRlZCI="; let expected = "IyEvdXNyL2Jpbi9lbnYgYmFzaAplY2hvICJpbmNsdWRlZCI=";
let base64 = include b64 "./include_example.sh"; let base64 = include b64 "./include_example.sh";
assert | assert {
base64 == expected; ok = base64 == expected,
|; desc = "base64 == expected",
};
let base64safe = include b64urlsafe "./include_example.sh"; let base64safe = include b64urlsafe "./include_example.sh";
assert | assert {
base64safe == expected; ok = base64safe == expected,
|; desc = "base64safe == expected",
};

View File

@ -2,10 +2,12 @@ let empty_list = [];
let concatenated = ["foo"] + ["bar"]; let concatenated = ["foo"] + ["bar"];
assert | assert {
concatenated == ["foo", "bar"]; ok = concatenated == ["foo", "bar"],
|; desc = "concatenated == [\"foo\", \"bar\"]",
};
assert | assert {
concatenated + empty_list == ["foo", "bar"]; 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 simpleresult = simplemacro(1, 2, 3);
let cplxresult = cplxmacro(1, "We", 3.0); let cplxresult = cplxmacro(1, "We", 3.0);
assert | assert {
noargresult.field1 == "value"; ok = noargresult.field1 == "value",
|; desc = "noargresult.field1 == \"value\"",
assert | };
simpleresult.field1 == 1; assert {
|; ok = simpleresult.field1 == 1,
assert | desc = "simpleresult.field1 == 1",
simpleresult.field2 == 2; };
|; assert {
assert | ok = simpleresult.field2 == 2,
simpleresult.field3 == 3; desc = "simpleresult.field2 == 2",
|; };
assert {
ok = simpleresult.field3 == 3,
desc = "simpleresult.field3 == 3",
};
assert | assert {
cplxresult.field1 == 2; ok = cplxresult.field1 == 2,
|; desc = "cplxresult.field1 == 2",
assert | };
cplxresult.field2 == "We are here"; assert {
|; ok = cplxresult.field2 == "We are here",
assert | desc = "cplxresult.field2 == \"We are here\"",
cplxresult.field3 == 2.0; };
|; assert {
assert | ok = cplxresult.field3 == 2.0,
cplxresult.boolfield == true; desc = "cplxresult.field3 == 2.0",
|; };
assert {
ok = cplxresult.boolfield == true,
desc = "cplxresult.boolfield == true",
};
let macro_tpl_arg = macro(tpl) => { let macro_tpl_arg = macro(tpl) => {
result = tpl.field, result = tpl.field,
@ -53,6 +61,7 @@ let arg_tpl = {
field = "value", field = "value",
}; };
assert | assert {
macro_tpl_arg(arg_tpl).result == arg_tpl.field; 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{}; let simple_mod_instance = test_simple_mod{};
assert | assert {
simple_mod_instance.value == "value"; ok = simple_mod_instance.value == "value",
|; desc = "simple_mod_instance.value == \"value\"",
};
let simple_mod_with_args = test_simple_mod{arg = "othervalue"}; let simple_mod_with_args = test_simple_mod{arg = "othervalue"};
assert | assert {
simple_mod_with_args.value == "othervalue"; ok = simple_mod_with_args.value == "othervalue",
|; desc = "simple_mod_with_args.value == \"othervalue\"",
};
let embedded_mod = module { let embedded_mod = module {
deep_value = "None", deep_value = "None",
@ -41,19 +43,23 @@ let embedded_mod = module {
let embedded_default_params = embedded_mod{}; let embedded_default_params = embedded_mod{};
assert | assert {
embedded_default_params.embedded.value == "None"; ok = embedded_default_params.embedded.value == "None",
|; desc = "embedded_default_params.embedded.value == \"None\"",
};
assert | assert {
embedded_default_params.env_name == "qa"; ok = embedded_default_params.env_name == "qa",
|; desc = "embedded_default_params.env_name == \"qa\"",
};
let embedded_with_params = embedded_mod{deep_value = "Some"}; let embedded_with_params = embedded_mod{deep_value = "Some"};
assert | assert {
embedded_with_params.embedded.value == "Some"; ok = embedded_with_params.embedded.value == "Some",
|; desc = "embedded_with_params.embedded.value == \"Some\"",
assert | };
embedded_mod{dep_value="Some"}.embedded_def{}.value == "None"; 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 | assert {
2 * 2 + 1 == 5; ok = 2 * 2 + 1 == 5,
|; desc = "2 * 2 + 1 == 5",
assert | };
2 + 2 * 3 == 8; assert {
|; ok = 2 + 2 * 3 == 8,
assert | desc = "2 + 2 * 3 == 8",
2 * (2 + 1) == 6; };
|; assert {
assert | ok = 2 * (2 + 1) == 6,
2 * 2 + 1 > 4; desc = "2 * (2 + 1) == 6",
|; };
assert | assert {
2 * 2 + 1 < 6; ok = 2 * 2 + 1 > 4,
|; desc = "2 * 2 + 1 > 4",
assert | };
2 * 2 + 1 >= 5; assert {
|; ok = 2 * 2 + 1 < 6,
assert | desc = "2 * 2 + 1 < 6",
2 * 2 + 1 <= 5; };
|; assert {
assert | ok = 2 * 2 + 1 >= 5,
2 / 2 == 1; desc = "2 * 2 + 1 >= 5",
|; };
assert | assert {
2 - 1 == 1; ok = 2 * 2 + 1 <= 5,
|; desc = "2 * 2 + 1 <= 5",
assert | };
1 + 1 + 1 + 1 == 4; assert {
|; ok = 2 / 2 == 1,
assert | desc = "2 / 2 == 1",
1 + 1 + 2 * 2 + 1 + 1 == 1 + 1 + (2 * 2) + 1 + 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 = { let tpl = {
one = { one = {
two = 12, two = 12,
}, },
}; };
assert | assert {
1 + tpl.one.two * 2 + 3 == 28; 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", door2 = "you lose",
}; };
assert | assert {
got == "grand prize"; ok = got == "grand prize",
|; desc = "got == \"grand prize\"",
assert | };
defaultgot == "OOPS"; assert {
|; ok = defaultgot == "OOPS",
desc = "defaultgot == \"OOPS\"",
};
// select inside a macro // select inside a macro
@ -29,21 +31,25 @@ let condmacro = macro(arg) => {
let result = condmacro("opt1"); let result = condmacro("opt1");
assert | assert {
condmacro("opt1") == {output = "yay"}; ok = condmacro("opt1") == {output = "yay"},
|; desc = "condmacro(\"opt1\") == {output = \"yay\"}",
assert | };
condmacro("opt2") == {output = "boo"}; assert {
|; ok = condmacro("opt2") == {output = "boo"},
assert | desc = "condmacro(\"opt2\") == {output = \"boo\"}",
condmacro("invalid") == {output = NULL}; };
|; assert {
ok = condmacro("invalid") == {output = NULL},
desc = "condmacro(\"invalid\") == {output = NULL}",
};
let iflike = select true, "default", { let iflike = select true, "default", {
true = "I was true", true = "I was true",
false = "I was false", false = "I was false",
}; };
assert | assert {
iflike == "I was true"; ok = iflike == "I was true",
|; desc = "iflike == \"I was true\"",
};

View File

@ -10,44 +10,55 @@ let testmacro = macro(arg) => {
output = arg, output = arg,
}; };
assert | assert {
list.0 == 1; ok = list.0 == 1,
|; desc = "list.0 == 1",
assert | };
list.1 == 2; assert {
|; ok = list.1 == 2,
assert | desc = "list.1 == 2",
list.3 == 4; };
|; assert {
assert | ok = list.3 == 4,
tuple.field1 == 1; desc = "list.3 == 4",
|; };
assert | assert {
tuple.field2 == 3; ok = tuple.field1 == 1,
|; desc = "tuple.field1 == 1",
assert | };
tuple.deeplist.0 == "foo"; assert {
|; ok = tuple.field2 == 3,
assert | desc = "tuple.field2 == 3",
tuple.deeplist.1 == "bar"; };
|; assert {
assert | ok = tuple.deeplist.0 == "foo",
{foo = "bar"}.foo == "bar"; desc = "tuple.deeplist.0 == \"foo\"",
|; };
assert | assert {
["one", "two", "three"].0 == "one"; 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() => { let macro_for_test = macro() => {
foo = "bar", foo = "bar",
}; };
assert | assert {
macro_for_test().foo == "bar"; ok = macro_for_test().foo == "bar",
|; desc = "macro_for_test().foo == \"bar\"",
};
let mymodule = module { foo = "bar" } => { let mymodule = module { foo = "bar" } => {
let foo = mod.foo; let foo = mod.foo;
}; };
assert | assert {
mymodule{}.foo == "bar"; ok = mymodule{}.foo == "bar",
|; desc = "mymodule{}.foo == \"bar\"",
};

View File

@ -15,33 +15,42 @@ let nestedtpl = {
maybe_val = NULL, maybe_val = NULL,
}; };
assert | assert {
simpletpl.foo == "bar"; ok = simpletpl.foo == "bar",
|; desc = "simpletpl.foo == \"bar\"",
assert | };
stringfieldtpl."field 1" == 1; assert {
|; ok = stringfieldtpl."field 1" == 1,
assert | desc = "stringfieldtpl.\"field 1\" == 1",
nestedtpl.scalar == 1; };
|; assert {
assert | ok = nestedtpl.scalar == 1,
nestedtpl.maybe_val == NULL; desc = "nestedtpl.scalar == 1",
|; };
assert | assert {
nestedtpl.inner.field == "value"; ok = nestedtpl.maybe_val == NULL,
|; desc = "nestedtpl.maybe_val == NULL",
assert | };
nestedtpl.list.0 == 1; assert {
|; ok = nestedtpl.inner.field == "value",
assert | desc = "nestedtpl.inner.field == \"value\"",
nestedtpl.list.1 == 2; };
|; assert {
assert | ok = nestedtpl.list.0 == 1,
nestedtpl.list.2 == 3; desc = "nestedtpl.list.0 == 1",
|; };
assert | assert {
nestedtpl.list.3 == 4; 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{ let nestedcopy = nestedtpl{
inner = self.inner{ inner = self.inner{
@ -62,18 +71,22 @@ let deepnestedcopy = nestedcopy{
}, },
}; };
assert | assert {
nestedcopy.inner.field2 == 2; ok = nestedcopy.inner.field2 == 2,
|; desc = "nestedcopy.inner.field2 == 2",
assert | };
nestedcopy.inner.inner.field3 == "three"; assert {
|; ok = nestedcopy.inner.inner.field3 == "three",
assert | desc = "nestedcopy.inner.inner.field3 == \"three\"",
deepnestedcopy.inner.inner.field4 == 4; };
|; assert {
assert | ok = deepnestedcopy.inner.inner.field4 == 4,
deepnestedcopy.maybe_val == "some val"; desc = "deepnestedcopy.inner.inner.field4 == 4",
|; };
assert {
ok = deepnestedcopy.maybe_val == "some val",
desc = "deepnestedcopy.maybe_val == \"some val\"",
};
let base_maybe = { let base_maybe = {
real = "A real value", real = "A real value",
@ -83,38 +96,43 @@ let copy_maybe = base_maybe{
real = NULL, real = NULL,
}; };
assert | assert {
copy_maybe.real == NULL; ok = copy_maybe.real == NULL,
|; desc = "copy_maybe.real == NULL",
};
let quotedself_tpl = { let quotedself_tpl = {
"self" = "myself", "self" = "myself",
}; };
assert | assert {
quotedself_tpl."self" == "myself"; ok = quotedself_tpl."self" == "myself",
|; desc = "quotedself_tpl.\"self\" == \"myself\"",
};
let quotedenv_tpl = { let quotedenv_tpl = {
"env" = "myenv", "env" = "myenv",
}; };
assert | assert {
quotedenv_tpl."env" == "myenv"; ok = quotedenv_tpl."env" == "myenv",
|; desc = "quotedenv_tpl.\"env\" == \"myenv\"",
};
let unquotedenv_tpl = { let unquotedenv_tpl = {
env = "myenv", env = "myenv",
}; };
assert | assert {
unquotedenv_tpl."env" == "myenv"; ok = unquotedenv_tpl."env" == "myenv",
|; desc = "unquotedenv_tpl.\"env\" == \"myenv\"",
};
let unquotedself_tpl = { let unquotedself_tpl = {
self = "myself", self = "myself",
}; };
assert | assert {
unquotedself_tpl."self" == "myself"; ok = unquotedself_tpl."self" == "myself",
|; desc = "unquotedself_tpl.\"self\" == \"myself\"",
};

View File

@ -742,7 +742,7 @@ pub enum Statement {
Import(ImportDef), Import(ImportDef),
// Assert statement // Assert statement
Assert(Token), Assert(Expression),
// Identify an Expression for output. // Identify an Expression for output.
Output(Token, Expression), 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>> { fn eval_stmt(&mut self, stmt: &Statement) -> Result<Rc<Val>, Box<dyn Error>> {
let child_scope = self.scope.clone(); let child_scope = self.scope.clone();
match stmt { 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::Let(ref def) => self.eval_let(def),
&Statement::Import(ref def) => self.eval_import(def), &Statement::Import(ref def) => self.eval_import(def),
&Statement::Expression(ref expr) => self.eval_expr(expr, &child_scope), &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 { if !self.validate_mode {
// we are not in validate_mode then build_asserts are noops. // we are not in validate_mode then build_asserts are noops.
return Ok(Rc::new(Val::Empty)); return Ok(Rc::new(Val::Empty));
} }
let expr = &tok.fragment; let ok = match self.eval_expr(expr, scope) {
let assert_input =
OffsetStrIter::new_with_offsets(expr, tok.pos.line - 1, tok.pos.column - 1);
let ok = match self.eval_input(assert_input) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
// failure! // failure!
let msg = format!( let msg = format!("CompileError: {}\n", e);
"NOT OK - '{}' at line: {} column: {}\n\tCompileError: {}\n",
expr, tok.pos.line, tok.pos.column, e
);
self.assert_collector.summary.push_str(&msg); self.assert_collector.summary.push_str(&msg);
self.assert_collector.failures.push_str(&msg); self.assert_collector.failures.push_str(&msg);
self.assert_collector.success = false; self.assert_collector.success = false;
@ -1383,34 +1390,69 @@ impl<'a> FileBuilder<'a> {
} }
}; };
if let &Val::Boolean(b) = ok.as_ref() { match ok.as_ref() {
// record the assertion result. &Val::Tuple(ref fs) => {
if b { let ok_field = match find_in_fieldlist("ok", fs) {
// success! Some(ref val) => match val.as_ref() {
&Val::Boolean(b) => b,
_ => {
let msg = format!( let msg = format!(
"OK - '{}' at line: {} column: {}\n", "TYPE FAIL - Expected Boolean field ok in tuple {}, line: {} column: {}",
expr, tok.pos.line, tok.pos.column ok.as_ref(), expr.pos().line, expr.pos().column
); );
self.assert_collector.summary.push_str(&msg); self.record_assert_result(&msg, false);
} else { return Ok(Rc::new(Val::Empty));
// 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;
} }
} 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. // record an assertion type-failure result.
let msg = format!( let msg = format!(
"TYPE FAIL - '{}' Expected Boolean got {} at line: {} column: {}\n", "TYPE FAIL - Expected tuple with ok and desc fields got {} at line: {} column: {}\n",
expr, ok, tok.pos.line, tok.pos.column ok, expr.pos().line, expr.pos().column
); );
self.assert_collector.failures.push_str(&msg); self.record_assert_result(&msg, false);
self.assert_collector.success = false; return Ok(Rc::new(Val::Empty));
self.assert_collector.summary.push_str(&msg); }
} }
Ok(ok) Ok(ok)
} }

View File

@ -736,9 +736,9 @@ make_fn!(
assert_statement<SliceIter<Token>, Statement>, assert_statement<SliceIter<Token>, Statement>,
do_each!( do_each!(
_ => word!("assert"), _ => word!("assert"),
tok => must!(match_type!(PIPEQUOTE)), expr => must!(expression),
_ => must!(punct!(";")), _ => 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>, make_fn!(barewordtok<OffsetStrIter, Token>,
do_each!( do_each!(
span => input!(), span => input!(),
@ -378,7 +364,6 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
either!( either!(
input, input,
strtok, strtok,
pipequotetok,
emptytok, // This must come before the barewordtok emptytok, // This must come before the barewordtok
digittok, digittok,
commatok, commatok,
@ -528,14 +513,6 @@ macro_rules! match_type {
match_type!($i, STR => token_clone) 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) => { ($i:expr,DIGIT => $h:expr) => {
match_type!($i, TokenType::DIGIT, "Not a DIGIT", $h) 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] #[test]
fn test_string_with_escaping() { fn test_string_with_escaping() {
let result = strtok(OffsetStrIter::new("\"foo \\\\ \\\"bar\"")); 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",
};