From 2407a5f51b58a8c64379345f5eeefe92f72680c9 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Fri, 10 Jun 2022 15:26:19 -0500 Subject: [PATCH] Make test understand [[ < > =~ ]] and add tests to test.test. (And reorder tests so the line up with the posix page more.) --- tests/test.test | 39 +++++++++++++++++++++++++++++++-------- toys/posix/test.c | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/tests/test.test b/tests/test.test index 300eb2ff..2174f405 100644 --- a/tests/test.test +++ b/tests/test.test @@ -4,14 +4,23 @@ #testing "name" "command" "result" "infile" "stdin" -testcmd '0 args' '; echo $?' '1\n' '' '' -testcmd '1 arg' '== ; echo $?' '0\n' '' '' +testcmd "-- isn't parsed" "-- == -- && echo yes" "yes\n" "" "" + +# Number and position of args is important +testcmd 'no args is false' '; echo $?' '1\n' '' '' +testcmd 'empty string is false' '""; echo $?' '1\n' '' '' +testcmd '1 arg is true if not empty string' '== ; echo $?' '0\n' '' '' +testcmd "1 arg isn't an operand" '-t 2>&1; echo $?' '0\n' '' '' testcmd '2 args' '-e == ; echo $?' '1\n' '' '' testcmd '3 args' '-e == -e ; echo $?' '0\n' '' '' + +# parse as operator before parsing as parentheses around one argument testcmd '' '\( == \) ; echo $?' '1\n' '' '' testcmd '' '\( == \( ; echo $?' '0\n' '' '' +testcmd '' '\( "" \) ; echo $?' '1\n' '' '' +testcmd '' '\( x \) ; echo $?' '0\n' '' '' -# TODO: Should also have device and socket files +# TODO: Should also have device and socket files, but requires root mkdir d touch f @@ -30,18 +39,22 @@ type_test() testing "-b" "type_test -b" "" "" "" testing "-c" "type_test -c" "L" "" "" testing "-d" "type_test -d" "d" "" "" +testing "-e" "type_test -e" "dfLsp" "" "" testing "-f" "type_test -f" "fs" "" "" testing "-h" "type_test -h" "L" "" "" testing "-L" "type_test -L" "L" "" "" -testing "-s" "type_test -s" "ds" "" "" -testing "-S" "type_test -S" "" "" "" testing "-p" "type_test -p" "p" "" "" -testing "-e" "type_test -e" "dfLsp" "" "" +testing "-S" "type_test -S" "" "" "" +testing "-s" "type_test -s" "ds" "" "" testing "! -e" 'type_test ! -e' "n" "" "" rm f L s p rmdir d +# Alas can't expand to a redirect, so just test one success/fail +testcmd "-t" '-t 0 < /dev/null; echo $?' '1\n' '' '' +testcmd "-t2" '-t 0 < /dev/ptmx; echo $?' '0\n' '' '' + # test -rwx each bit position and failure touch walrus MASK=111 @@ -65,6 +78,7 @@ for i in uu+s gg+s k+t; do done # test each ugo+rwx bit position individually XX=no +# Note: chmod 007 means everybody EXCEPT owner/group can access it. (Unix!) [ $(id -u) -eq 0 ] && XX=yes # Root always has access for i in 1 10 100; do for j in x w r; do chmod $i walrus @@ -75,8 +89,7 @@ for i in 1 10 100; do for j in x w r; do done; done rm -f walrus -testcmd "" "'' || echo yes" "yes\n" "" "" -testcmd "" "a && echo yes" "yes\n" "" "" +# Not zero length, zero length, equals, not equals testcmd "-n" "-n '' || echo yes" "yes\n" "" "" testcmd "-n2" "-n a && echo yes" "yes\n" "" "" testcmd "-z" "-z '' && echo yes" "yes\n" "" "" @@ -103,6 +116,16 @@ testing "-le" "arith_test -le" "le" "" "" testing "positional" "test -a == -a && echo yes" "yes\n" "" "" testing "! stacks" 'test \! \! \! \! 2 -eq 2 && echo yes' "yes\n" "" "" +# bash builtin "test" has these, but /usr/bin/test does not. +testing "<1" 'test abc \< def && echo yes' "yes\n" "" "" +testing "<2" 'test def \< abc || echo yes' "yes\n" "" "" +testing ">1" 'test abc \> def || echo yes' "yes\n" "" "" +testing ">2" 'test def \> abc && echo yes' "yes\n" "" "" +# bash only has this for [[ ]] but extra tests to _exclude_ silly... +toyonly testcmd "=~" 'abc =~ a.c && echo yes' "yes\n" "" "" +toyonly testcmd "=~ fail" 'abc =~ d.c; echo $?' '1\n' "" "" +toyonly testcmd "=~ zero length match" 'abc =~ "1*" && echo yes' 'yes\n' '' '' + # test ! = -o a # test ! \( = -o a \) # test \( ! = \) -o a diff --git a/toys/posix/test.c b/toys/posix/test.c index b1f119c4..e6ea508f 100644 --- a/toys/posix/test.c +++ b/toys/posix/test.c @@ -4,10 +4,11 @@ * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html * - * TODO sh [[ ]] options: < aaa bbb>aaa ~= regex + * Deviations from posix: -k, [[ < > =~ ]] USE_TEST(NEWTOY(test, 0, TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOHELP|TOYFLAG_MAYFORK)) USE_TEST_GLUE(OLDTOY([, test, TOYFLAG_BIN|TOYFLAG_MAYFORK|TOYFLAG_NOHELP)) +USE_SH(OLDTOY([[, test, TOYFLAG_NOFORK|TOYFLAG_NOHELP)) config TEST bool "test" @@ -15,7 +16,8 @@ config TEST help usage: test [-bcdefghLPrSsuwx PATH] [-nz STRING] [-t FD] [X ?? Y] - Return true or false by performing tests. (With no arguments return false.) + Return true or false by performing tests. No arguments is false, one argument + is true if not empty string. --- Tests with a single argument (after the option): PATH is/has: @@ -24,14 +26,15 @@ config TEST -d directory -h symlink -S socket -x executable -e exists -L symlink -s nonzero size -k sticky bit STRING is: - -n nonzero size -z zero size (STRING by itself implies -n) + -n nonzero size -z zero size FD (integer file descriptor) is: -t a TTY --- Tests with one argument on each side of an operator: Two strings: - = are identical != differ - + = are identical != differ =~ string matches regex + Alphabetical sort: + < first is lower > first higher Two integers: -eq equal -gt first > second -lt first < second -ne not equal -ge first >= second -le first <= second @@ -57,8 +60,24 @@ static int do_test(char **args, int *count) if (*count>=3) { *count = 3; char *s = args[1], *ss = "eqnegtgeltle"; + // TODO shell integration case insensitivity if (!strcmp(s, "=") || !strcmp(s, "==")) return !strcmp(args[0], args[2]); if (!strcmp(s, "!=")) return strcmp(args[0], args[2]); + if (!strcmp(s, "=~")) { + regex_t reg; + + // TODO: regex needs integrated quoting support with the shell. + // Ala [[ abc =~ "1"* ]] matches but [[ abc =~ 1"*" ]] does not + xregcomp(®, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE? + i = regexec(®, args[0], 0, 0, 0); + regfree(®); + + return !i; + } + if ((*s=='<' || *s=='>') && !s[1]) { + i = strcmp(args[0], args[2]); + return (*s=='<') ? i<0 : i>0; + } if (*s=='-' && strlen(s)==3 && (s = strstr(ss, s+1)) && !((i = s-ss)&1)) { long long a = atolx(args[0]), b = atolx(args[2]); @@ -98,13 +117,14 @@ static int do_test(char **args, int *count) #define OR 4 // test before -o succeeded since ( so force true void test_main(void) { - char *s; + char *s = (void *)1; int pos, paren, pstack, result = 0; toys.exitval = 2; - if (CFG_TOYBOX && !strcmp("[", toys.which->name)) - if (!toys.optc || strcmp("]", toys.optargs[--toys.optc])) - error_exit("Missing ']'"); + if (CFG_TOYBOX && *toys.which->name=='[') { + if (toys.optc) for (s = toys.optargs[--toys.optc]; *s==']'; s++); + if (*s) error_exit("Missing ']'"); + } // loop through command line arguments if (toys.optc) for (pos = paren = pstack = 0; ; pos++) { -- 2.39.2