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 <sean...@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 <sean...@gmail.com> S: Maintained F: common/cli_lil.c F: include/cli_lil.h +F: test/cmd/lil.c LOGGING M: Simon Glass <s...@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 <sean...@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