Переглянути джерело

Make test understand [[ < > =~ ]] and add tests to test.test.
(And reorder tests so the line up with the posix page more.)

Rob Landley 2 роки тому
батько
коміт
2407a5f51b
2 змінених файлів з 60 додано та 17 видалено
  1. 31 8
      tests/test.test
  2. 29 9
      toys/posix/test.c

+ 31 - 8
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

+ 29 - 9
toys/posix/test.c

@@ -4,10 +4,11 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
  *
- * TODO sh [[ ]] options: <   aaa<bbb  >   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(&reg, args[2], REG_NOSUB); // REG_EXTENDED? REG_ICASE?
+      i = regexec(&reg, args[0], 0, 0, 0);
+      regfree(&reg);
+
+      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++) {