[RFC PATCH 17/28] test: Add tests for LIL

Sean Anderson
2021-07-01T06:19:36Z

This tests several aspects of the parser. These tests are primarily adapted
from the *.lil code examples included with upstream LIL. These tests should
probably get their own category, especially if I add additional styles of
tests.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
---
Yes, I know checkpatch complains about the quoted string being split, but
that warning is intended for user-visible strings.

MAINTAINERS | 1 +
test/cmd/Makefile | 1 +
test/cmd/lil.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 341 insertions(+)
create mode 100644 test/cmd/lil.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 0184de5f93..3bf460127b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -771,6 +771,7 @@ M: Sean Anderson <seanga2@gmail.com>
S: Maintained
F: common/cli_lil.c
F: include/cli_lil.h
+F: test/cmd/lil.c

LOGGING
M: Simon Glass <sjg@chromium.org>
diff --git a/test/cmd/Makefile b/test/cmd/Makefile
index 2cfe43a6bd..4f7440cb44 100644
--- a/test/cmd/Makefile
+++ b/test/cmd/Makefile
@@ -5,6 +5,7 @@
ifdef CONFIG_HUSH_PARSER
obj-$(CONFIG_CONSOLE_RECORD) += test_echo.o
endif
+obj-$(CONFIG_LIL_FULL) += lil.o
obj-y += mem.o
obj-$(CONFIG_CMD_ADDRMAP) += addrmap.o
obj-$(CONFIG_CMD_MEM_SEARCH) += mem_search.o
diff --git a/test/cmd/lil.c b/test/cmd/lil.c
new file mode 100644
index 0000000000..896b2fed15
--- /dev/null
+++ b/test/cmd/lil.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0+ AND Zlib
+/*
+ * Copyright (C) 2021 Sean Anderson <seanga2@gmail.com>
+ * Copyright (C) 2010-2021 Kostas Michalopoulos
+ *
+ * This file contains code which originated from the LIL project, licensed under
+ * Zlib. All modifications are licensed under GPL-2.0+
+ */
+
+#include <common.h>
+#include <asm/global_data.h>
+#include <cli_lil.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+const char helpers[] =
+ "proc assert {cond} {"
+ "if not [upeval expr [set cond]] {"
+ "error [set cond]"
+ "}"
+ "};"
+ "proc assert_err {cmd} {"
+ "set ok 1;"
+ "try {upeval $cmd; set ok 0} {};"
+ "assert {$ok};"
+ "};"
+ "proc asserteq {expr1 expr2} {"
+ "set val1 [upeval 'expr \"$expr1\"'];"
+ "set val2 [upeval 'expr \"$expr2\"'];"
+ "if {$val1 != $val2} {"
+ "error '$expr1 == ${expr2}: "
+ "Expected ${val1}, got $val2'"
+ "}"
+ "};"
+ "proc asserteq_str {expr1 expr2} {"
+ "set val1 [upeval 'subst \"$expr1\"'];"
+ "set val2 [upeval 'subst \"$expr2\"'];"
+ "if not [streq $val1 $val2] {"
+ "error '$expr1 == ${expr2}: "
+ "Expected ${val1}, got $val2'"
+ "}"
+ "};"
+ "proc asserteq_list {xs ys} {"
+ "set xlen [count $xs];"
+ "set ylen [count $ys];"
+ "if not {$xlen == $ylen} {"
+ "error '\\[count ${xs}\\] == \\[count ${ys}\\]: "
+ "Expected ${xlen}, got $ylen'\n"
+ "};"
+ "for {set i 0} {$i < $xlen} {incr i} {"
+ "set x [index $xs $i];"
+ "set y [index $ys $i];"
+ "if not {[streq $x $y]} {"
+ "error '$xs == ${ys}: "
+ "Expected $x at ${i}, got $y'"
+ "}"
+ "}"
+ "}";
+
+static const struct {
+ const char *name;
+ const char *cmd;
+} lil_tests[] = {
+ {"and",
+ "proc and args {"
+ "foreach [slice $args 1] {"
+ "upeval 'downeval \\'set v \\'\\[${i}\\]';"
+ "if not $v { return 0 }"
+ "};"
+ "return 1"
+ "};"
+ "set a 0;"
+ "set final [and {set a 3} {return 0} {set a 32}];"
+ "asserteq 0 {$final};"
+ "assert 3 {$a};"
+ },
+ {"assert",
+ "assert 1;"
+ "assert_err {assert 0};"
+ "asserteq 1 1;"
+ "assert_err {asserteq 1 0};"
+ "asserteq_str {string one} {string one};"
+ "assert_err {asserteq_str {string one} {string two}};"
+ "asserteq_list [list 1 2 3] [list 1 2 3];"
+ "assert_err {asserteq_list [list 1 2] [list 1 2 3]};"
+ "assert_err {asserteq_list [list 1 2 3] [list 1 2]};"
+ "assert_err {asserteq_list [list 1 2 3] [list 1 2 4]};"
+ },
+ {"downeval",
+ "proc grab-some-list {} {"
+ "set items {};"
+ "upeval {"
+ "foreach $some-list {"
+ "downeval 'append items $i'"
+ "}"
+ "};"
+ "return $items"
+ "};"
+ "set some-list [list foo bar baz blah moo boo];"
+ "asserteq_list $some-list [grab-some-list]"
+ },
+ {"expr",
+ "asserteq 7 {1 + ( 2 * 3 )};"
+ "asserteq 7 {1+(2*3)};"
+ "asserteq -6 {1+ ~(2*3)};"
+ "asserteq -6 {1 + ~( 2 * 3 )};"
+ "asserteq -6 {1 +~ (2*3 )};"
+ "asserteq -6 {~(2*3)+1};"
+ "asserteq 0 {1*!(2+2)};"
+ "asserteq -1 {~!(!{})};"
+ "asserteq 1 {1 +~*(2*3)};"
+ "asserteq 1 {'hello'};"
+ "asserteq 0 {0};"
+ "asserteq 0 {{}};"
+ "asserteq 1 {()};"
+ "asserteq 1 {( )};"
+ "asserteq_str '' {[expr]};"
+ },
+ {"factorial",
+ "proc fact {n} {"
+ "if {$n} {"
+ "expr {$n * [fact [expr {$n - 1}]]}"
+ "} {"
+ "return 1"
+ "}"
+ "};"
+ "asserteq 1 {[fact 0]};"
+ "asserteq 1 {[fact 1]};"
+ "asserteq 6 {[fact 3]};"
+ "asserteq 3628800 {[fact 10]};"
+ "asserteq 2432902008176640000 {[fact 20]}"
+ },
+ {"filter",
+ "set short_procs [filter [reflect procs] {[length $x] < 5}];"
+ "foreach $short_procs {assert {[length $i] < 5}}"
+ },
+ {"funcs",
+ "proc lapply {list proc} {"
+ "set ret {};"
+ "foreach $list {"
+ "append ret [$proc $i];"
+ "};"
+ "return $ret"
+ "};"
+ "set list [list {bad's day} {good's day} eh??];"
+ "asserteq_list [lapply $list split] [list "
+ "[list {bad's} day] "
+ "[list {good's} day] "
+ "[list eh??]"
+ "];"
+ "asserteq_list [lapply $list length] [list 9 10 4];"
+ "asserteq_list [lapply $list [proc {a} {"
+ "return [index [split $a] 0]"
+ "}]] [list {bad's} {good's} eh??]"
+ },
+ {"lists",
+ "set l [list foo bar baz bad];"
+ "asserteq_str baz {[index $l 2]};"
+ "append l 'Hello, world!';"
+ "asserteq_list $l [list foo bar baz bad 'Hello, world!'];"
+ "set l [subst $l];"
+ "asserteq_list $l [list foo bar baz bad Hello, world!];"
+ "lmap $l foox barx bamia;"
+ "asserteq_str foo {$foox};"
+ "asserteq_str bar {$barx};"
+ "asserteq_str baz {$bamia};"
+ "set l {one # linebreaks are ignored in list parsing mode\n"
+ "\n"
+ "two;three # a semicolon still counts as line break\n"
+ " # (which in list mode is treated as a\n"
+ " # separator for list entries)\n"
+ "# of course a semicolon inside quotes is treated like normal\n"
+ "three';'and';a;half'\n"
+ "# like in code mode, a semicolon will stop the comment; four\n"
+ "\n"
+ "# below we have a quote, square brackets for inline\n"
+ "# expansions are still taken into consideration\n"
+ "[quote {this line will be ignored completely\n"
+ " as will this line and instead be replaced\n"
+ " with the 'five' below since while in code\n"
+ " mode (that is, inside the brackets here)\n"
+ " linebreaks are still processed}\n"
+ " quote five]\n"
+ "\n"
+ "# The curly brackets are also processed so the next three\n"
+ "# lines will show up as three separate lines\n"
+ "{six\n"
+ "seven\n"
+ "eight}}\n"
+ "asserteq_list $l [list one two three 'three;and;a;half' four "
+ "five 'six\\nseven\\neight'];"
+ },
+ {"local",
+ "proc bits-for {x} {"
+ "local y bits;"
+ "set y 0 bits 0;"
+ "while {$y <= $x} {"
+ "incr bits;"
+ "set y [expr 1 << $bits]"
+ "};"
+ "return $bits"
+ "};"
+ "set y 1001;"
+ "set bits [bits-for $y];"
+ "set x 45;"
+ "set bitsx [bits-for $x];"
+ "asserteq 1001 {$y};"
+ "asserteq 10 {$bits};"
+ "asserteq 45 {$x};"
+ "asserteq 6 {$bitsx}"
+ },
+ {"multiline comment",
+ "# this line will not be executed, but the following will\n"
+ "set ok1 1\n"
+ "## This is a multiline comment\n"
+ " which, as the name implies,\n"
+ " spans multiple lines.\n"
+ "set ok2 1\n"
+ " the code above wouldn't execute,\n"
+ " but this will --> ##set ok3 1\n"
+ "### more than two #s will not count as multiline comments\n"
+ "set ok4 1\n"
+ "# Note that semicolons can be used as linebreaks so\n"
+ "# this code will be executed: ; set ok5 1\n"
+ "##\n"
+ " ...however inside multiline comments semicolons do not\n"
+ " stop the comment section (pretty much like linebreaks)\n"
+ " and this code will not be executed: ; set ok6 1\n"
+ "##\n"
+ "# Also note that unlike in regular code, semicolons cannot\n"
+ "# be escaped in single-line comments, e.g.: ; set ok7 1\n"
+ "asserteq_str 1 {$ok1};"
+ "assert {![reflect has-var ok2]}"
+ "asserteq_str 1 {$ok3};"
+ "asserteq_str 1 {$ok4};"
+ "asserteq_str 1 {$ok5};"
+ "assert {![reflect has-var ok6]}"
+ "asserteq_str 1 {$ok7};"
+ },
+ {"multiline code",
+ "asserteq_list [list hello \\\n"
+ " world] [list hello world]"
+ },
+ {"return",
+ "proc uses_return {} {"
+ "return 1;"
+ "return 0;"
+ "};"
+ "proc doesnt_use_return {} {"
+ "quote 1;"
+ "};"
+ "proc uses_result {} {"
+ "result 1;"
+ "quote 0;"
+ "};"
+ "assert {[uses_return]};"
+ "assert {[doesnt_use_return]};"
+ "assert {[uses_result]}"
+ },
+ {"strings",
+ "set a 'This is a string';"
+ "set b 'This is another string';"
+ "asserteq 16 {[length $a]};"
+ "asserteq 22 {[length $b]};"
+ "asserteq_str a {[charat $a [expr [length $a] / 2]]};"
+ "asserteq_str t {[charat $b [expr [length $b] / 2]]};"
+ "asserteq 97 {[codeat $a [expr [length $a] / 2]]};"
+ "asserteq 116 {[codeat $b [expr [length $b] / 2]]};"
+ "asserteq 10 {[strpos $a string]};"
+ "asserteq 16 {[strpos $b string]};"
+ "asserteq -78 {[compare $a $b]};"
+ "assert {![streq $a $b]};"
+ "asserteq_str 'This is a foo' {[repstr $a string foo]};"
+ "asserteq_str 'This is another foo' {[repstr $b string foo]};"
+ "asserteq_list [split $a] [list This is a string];"
+ "asserteq_list [split $b] [list This is another string];"
+ },
+ {"topeval",
+ "proc does-something {} {"
+ "topeval {"
+ "asserteq 10 {$x};"
+ "set x 42;"
+ "downeval {set y [expr $x * 10]}"
+ "};"
+ "asserteq 420 {$y}"
+ "};"
+ "proc calls-something {} {"
+ "local x;"
+ "set x 33;"
+ "does-something;"
+ "asserteq 33 {$x};"
+ "asserteq 420 {$y}"
+ "};"
+ "set x 10;"
+ "set y 20;"
+ "calls-something;"
+ "asserteq 42 {$x};"
+ "asserteq 420 {$y}"
+ },
+ {"trim",
+ "set str ' Hello, world! ';"
+ "asserteq_str 'Hello, world!' {[trim $str]};"
+ "asserteq_str 'Hello, world! ' {[ltrim $str]};"
+ "asserteq_str ' Hello, world!' {[rtrim $str]};"
+ "asserteq_str 'Hello world' {[foreach [split $str] {"
+ "quote [trim $i {,!}]"
+ "}]};"
+ "asserteq_str 'Hello world' {[filter [split $str {,! }] {"
+ "[length $x] > 0"
+ "}]};"
+ },
+};
+
+static int lib_test_lil(struct unit_test_state *uts)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lil_tests); i++) {
+ const char *err_msg;
+ enum lil_error err;
+ struct lil *lil = lil_new(NULL);
+
+ lil_free_value(lil_parse(lil, helpers, sizeof(helpers) - 1, 0));
+ ut_asserteq(LIL_ERR_NONE, lil_error(lil, &err_msg));
+ lil_free_value(lil_parse(lil, lil_tests[i].cmd, 0, 0));
+ err = lil_error(lil, &err_msg);
+ if (err) {
+ ut_failf(uts, __FILE__, __LINE__, __func__,
+ lil_tests[i].name, "err=%d: %s", err, err_msg);
+ lil_free(lil);
+ return CMD_RET_FAILURE;
+ };
+ lil_free(lil);
+ }
+
+ return 0;
+}
+LIB_TEST(lib_test_lil, 0);
--
2.32.0