aboutsummaryrefslogtreecommitdiff
path: root/tests/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'tests/builtin')
-rw-r--r--tests/builtin/buildfile9
-rw-r--r--tests/builtin/cat.testscript82
-rw-r--r--tests/builtin/cp-dir/cp-file0
-rw-r--r--tests/builtin/cp.testscript447
-rw-r--r--tests/builtin/driver.cxx159
-rw-r--r--tests/builtin/echo.testscript26
-rw-r--r--tests/builtin/ln.testscript217
-rw-r--r--tests/builtin/mkdir.testscript102
-rw-r--r--tests/builtin/mv.testscript182
-rw-r--r--tests/builtin/rm.testscript105
-rw-r--r--tests/builtin/rmdir.testscript95
-rw-r--r--tests/builtin/sed.testscript350
-rw-r--r--tests/builtin/sleep.testscript42
-rw-r--r--tests/builtin/test.testscript84
-rw-r--r--tests/builtin/touch.testscript92
15 files changed, 1992 insertions, 0 deletions
diff --git a/tests/builtin/buildfile b/tests/builtin/buildfile
new file mode 100644
index 0000000..26acc13
--- /dev/null
+++ b/tests/builtin/buildfile
@@ -0,0 +1,9 @@
+# file : tests/builtin/buildfile
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+
+./: exe{driver} file{cp-dir/cp-file}
+
+exe{driver}: {hxx cxx}{*} $libs testscript{*}
diff --git a/tests/builtin/cat.testscript b/tests/builtin/cat.testscript
new file mode 100644
index 0000000..336bb03
--- /dev/null
+++ b/tests/builtin/cat.testscript
@@ -0,0 +1,82 @@
+# file : tests/builtin/cat.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "cat"
+
+: unknown-option
+:
+$* -u 2>"cat: unknown option '-u'" == 1
+
+: in
+:
+$* <<EOF >>EOF
+ foo
+ bar
+ EOF
+
+: dash
+:
+$* - <<EOF >>EOF
+ foo
+ bar
+ EOF
+
+: file
+:
+{
+ $* <<EOF >=out;
+ foo
+ bar
+ EOF
+
+ cat out >>EOO
+ foo
+ bar
+ EOO
+}
+
+: in-repeat
+:
+$* - - <<EOF >>EOF
+ foo
+ bar
+ EOF
+
+: non-existent
+:
+$* in 2>>/~%EOE% != 0
+ %cat: unable to print '.+/in': .+%
+ EOE
+
+: empty-path
+:
+: Cat an empty path.
+:
+$* '' 2>"cat: invalid path ''" == 1
+
+: big
+:
+: Cat a big file (about 100K) to test that the builtin is asynchronous.
+:
+{
+ s="--------------------------------";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ $* <"$s" | $* >"$s"
+}
+
+: cwd
+:
+: When cross-testing we cannot guarantee that host absolute paths are
+: recognized by the target process.
+:
+if ($test.target == $build.host)
+{
+ test.options += -d $~/a;
+ mkdir a;
+ echo 'foo' >=a/b;
+
+ $* b >'foo'
+}
diff --git a/tests/builtin/cp-dir/cp-file b/tests/builtin/cp-dir/cp-file
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/builtin/cp-dir/cp-file
diff --git a/tests/builtin/cp.testscript b/tests/builtin/cp.testscript
new file mode 100644
index 0000000..4f83586
--- /dev/null
+++ b/tests/builtin/cp.testscript
@@ -0,0 +1,447 @@
+# file : tests/builtin/cp.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "cp"
+test.options += -c
+
+: unknown-option
+:
+$* -u >'option -u' 2>"cp: unknown option '-u'" == 1
+
+: args
+:
+{
+ : none
+ :
+ $* 2>"cp: missing arguments" == 1
+
+ : no-source
+ :
+ $* -R a 2>"cp: missing source path" == 1
+
+ : no-trailing-sep
+ :
+ $* a b c 2>"cp: multiple source paths without trailing separator for destination directory" == 1
+
+ : empty
+ :
+ {
+ : dest
+ :
+ $* '' 2>"cp: invalid path ''" == 1
+
+ : src1
+ :
+ $* '' a 2>"cp: invalid path ''" == 1
+
+ : src2
+ :
+ $* '' a b/ 2>"cp: invalid path ''" == 1
+ }
+}
+
+: file
+:
+: Test synopsis 1: make a file copy at the specified path.
+:
+{
+ : existing
+ :
+ {
+ : to-non-existing
+ :
+ {
+ touch a;
+
+ $* a b >>/~%EOO% &b;
+ %create .+/b true%
+ %create .+/b false%
+ EOO
+
+ test -f b
+ }
+
+ : to-existing
+ :
+ {
+ touch a b;
+
+ $* a b >>/~%EOO%
+ %create .+/b true%
+ %create .+/b false%
+ EOO
+ }
+
+ : to-dir
+ :
+ {
+ touch a;
+ mkdir b;
+
+ $* a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b': .+%
+ EOE
+ }
+ }
+
+ : non-existing
+ :
+ {
+ $* a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b': .+%
+ EOE
+ }
+
+ : non-file
+ :
+ {
+ mkdir a;
+
+ $* a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b': .+%
+ EOE
+ }
+}
+
+: dir
+:
+: Test synopsis 2: make a directory copy at the specified path.
+:
+{
+ : existing
+ :
+ {
+ : to-non-existing
+ :
+ {
+ mkdir a;
+
+ $* -r a b >>/~%EOO% &b/;
+ %create .+/b/ true%
+ %create .+/b/ false%
+ EOO
+
+ test -d b
+ }
+
+ : to-existing
+ :
+ {
+ mkdir a b;
+
+ $* -R a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/ true%
+ EOO
+ %cp: unable to copy directory '.+/a' to '.+/b': .+%
+ EOE
+ }
+
+ : to-file
+ :
+ {
+ mkdir a;
+ touch b;
+
+ $* -r a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/ true%
+ EOO
+ %cp: unable to copy directory '.+/a' to '.+/b': .+%
+ EOE
+ }
+
+ : recursively
+ :
+ {
+ mkdir -p a/b/c;
+ touch a/x a/b/y;
+
+ $* -r a d >>/~%EOO% &d/ &d/x &d/b/ &d/b/y &d/b/c/;
+ %create .+/d/ true%
+ %create .+/d/ false%
+ %(
+ %create .+/d/.+ true%
+ %create .+/d/.+ false%
+ %){4}
+ EOO
+
+ test -d d/b/c && test -f d/x && test -f d/b/y
+ }
+ }
+
+ : non-existing
+ :
+ {
+ $* -r a b >>/~%EOO% 2>>/~%EOE% &b/ != 0
+ %create .+/b/ true%
+ %create .+/b/ false%
+ EOO
+ %cp: unable to copy directory '.+/a' to '.+/b': .+%
+ EOE
+ }
+
+ : non-dir
+ :
+ {
+ touch a;
+
+ $* -r a b >>/~%EOO% 2>>/~%EOE% &b/ != 0
+ %create .+/b/ true%
+ %create .+/b/ false%
+ EOO
+ %cp: unable to copy directory '.+/a' to '.+/b': .+%
+ EOE
+ }
+}
+
+: files
+:
+: Test synopsis 3: copy files into the specified directory.
+:
+{
+ : existing
+ :
+ {
+ : into-dir
+ :
+ {
+ : over-non-existing
+ :
+ {
+ mkdir b;
+ touch a;
+
+ $* a b/ >>/~%EOO% &b/a;
+ %create .+/b/a true%
+ %create .+/b/a false%
+ EOO
+
+ test -f b/a
+ }
+
+ : over-dir
+ :
+ {
+ mkdir -p b/a;
+ touch a;
+
+ $* a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+
+ : multiple
+ :
+ {
+ touch a b;
+ mkdir c;
+
+ $* a b c/ >>/~%EOO% &c/a &c/b &c/;
+ %create .+/c/a true%
+ %create .+/c/a false%
+ %create .+/c/b true%
+ %create .+/c/b false%
+ EOO
+
+ test -f c/a && test -f c/b
+ }
+ }
+
+ : into-non-existing-dir
+ :
+ {
+ touch a;
+
+ $* a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+
+ : into-non-dir
+ :
+ {
+ touch a b;
+
+ $* a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+ }
+
+ : non-existing
+ :
+ {
+ mkdir b;
+
+ $* a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+
+ : non-file
+ :
+ {
+ mkdir a b;
+
+ $* a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a true%
+ EOO
+ %cp: unable to copy file '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+}
+
+: filesystem-entries
+:
+: Test synopsis 4: copy filesystem entries into the specified directory.
+:
+{
+ : file
+ :
+ {
+ mkdir b;
+ touch a;
+
+ $* -R a b/ >>/~%EOO% &b/a;
+ %create .+/b/a true%
+ %create .+/b/a false%
+ EOO
+
+ test -f b/a
+ }
+
+ : dir
+ :
+ {
+ : over-non-existing
+ :
+ {
+ mkdir a b;
+ touch a/c;
+
+ $* -R a b/ >>/~%EOO% &b/a/ &b/a/c;
+ %create .+/b/a/ true%
+ %create .+/b/a/ false%
+ %create .+/b/a/c true%
+ %create .+/b/a/c false%
+ EOO
+
+ test -f b/a/c
+ }
+
+ : over-existing
+ :
+ {
+ mkdir -p a b/a;
+
+ $* -R a b/ >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/a/ true%
+ EOO
+ %cp: unable to copy directory '.+/a' to '.+/b/a': .+%
+ EOE
+ }
+ }
+}
+
+: attrs
+:
+if ($cxx.target.class != 'windows')
+{
+ fs = 's/.+ (\S+\s+\S+\s+\S+)\s+cp-file/\1/p'
+ ds = 's/.+ (\S+\s+\S+\s+\S+)\s+cp-dir/\1/p'
+
+ : copy
+ :
+ {
+ : file
+ :
+ {
+ ls -l $src_base/cp-dir | sed -n -e "$fs" | set t;
+
+ $* -p $src_base/cp-dir/cp-file ./ >! &cp-file;
+
+ ls -l | sed -n -e "$fs" >"$t"
+ }
+
+ : dir
+ :
+ {
+ ls -l $src_base | sed -n -e "$ds" | set t;
+
+ $* -pr $src_base/cp-dir ./ >! &cp-dir/ &cp-dir/cp-file;
+
+ ls -l | sed -n -e "$ds" >"$t"
+ }
+ }
+
+ : no-copy
+ :
+ : Note that the `ls -l` command by default displays the filesystem entry
+ : modification time with the minute resolution and building from git
+ : repository may not preserve the filesystem entry original modification
+ : times. That is why we also pass --full-time and enable the test for only
+ : platforms where ls supports this option.
+ :
+ if ($cxx.target.class == 'linux')
+ {
+ : file
+ :
+ {
+ ls -l --full-time $src_base/cp-dir | sed -n -e "$fs" | set t;
+
+ $* $src_base/cp-dir/cp-file ./ >! &cp-file;
+
+ ls -l --full-time | sed -n -e "$fs" | set tn;
+
+ if ("$tn" == "$t")
+ exit "unexpectedly copied timestamp \($t\)"
+ end
+ }
+
+ : dir
+ :
+ {
+ ls -l --full-time $src_base | sed -n -e "$ds" | set t;
+
+ $* -r $src_base/cp-dir ./ >! &cp-dir/ &cp-dir/cp-file;
+
+ ls -l --full-time | sed -n -e "$ds" | set tn;
+
+ if ("$tn" == "$t")
+ exit "unexpectedly copied timestamp \($t\)"
+ end
+ }
+ }
+}
+
+: cwd
+:
+: When cross-testing we cannot guarantee that host absolute paths are
+: recognized by the target process.
+:
+if ($test.target == $build.host)
+{
+ test.options += -d $~/a;
+ mkdir -p a/b;
+
+ $* -R b c >>/~%EOO% &a/c/;
+ %create .+/a/c/ true%
+ %create .+/a/c/ false%
+ EOO
+
+ test -d a/c
+}
diff --git a/tests/builtin/driver.cxx b/tests/builtin/driver.cxx
new file mode 100644
index 0000000..bf171cb
--- /dev/null
+++ b/tests/builtin/driver.cxx
@@ -0,0 +1,159 @@
+// file : tests/builtin/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules_ts
+#include <string>
+#include <vector>
+#include <utility> // move()
+#include <ostream>
+#include <iostream>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules_ts
+#ifdef __cpp_lib_modules_ts
+import std.core;
+import std.io;
+#endif
+import butl.path;
+import butl.utility; // eof()
+import butl.builtin;
+import butl.optional;
+import butl.timestamp; // to_stream(duration)
+#else
+#include <libbutl/path.mxx>
+#include <libbutl/utility.mxx>
+#include <libbutl/builtin.mxx>
+#include <libbutl/optional.mxx>
+#include <libbutl/timestamp.mxx>
+#endif
+
+using namespace std;
+using namespace butl;
+
+inline ostream&
+operator<< (ostream& os, const path& p)
+{
+ return os << p.representation ();
+}
+
+// Usage: argv[0] [-d <dir>] [-o <opt>] [-c] [-i] <builtin> <builtin-args>
+//
+// Execute the builtin and exit with its exit status.
+//
+// -d <dir> use as a current working directory
+// -c use callbacks that, in particular, trace calls to stdout
+// -o <opt> additional builtin option recognized by the callback
+// -i read lines from stdin and append them to the builtin arguments
+//
+int
+main (int argc, char* argv[])
+{
+ using butl::optional;
+
+ cin.exceptions (ios::badbit);
+ cout.exceptions (ios::failbit | ios::badbit);
+ cerr.exceptions (ios::failbit | ios::badbit);
+
+ bool in (false);
+ dir_path cwd;
+ string option;
+ builtin_callbacks callbacks;
+
+ string name;
+ vector<string> args;
+
+ auto flag = [] (bool v) {return v ? "true" : "false";};
+
+ // Parse the driver options and arguments.
+ //
+ int i (1);
+ for (; i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-d")
+ {
+ ++i;
+
+ assert (i != argc);
+ cwd = dir_path (argv[i]);
+ }
+ else if (a == "-o")
+ {
+ ++i;
+
+ assert (i != argc);
+ option = argv[i];
+ }
+ else if (a == "-c")
+ {
+ callbacks = builtin_callbacks (
+ [&flag] (const path& p, bool pre)
+ {
+ cout << "create " << p << ' ' << flag (pre) << endl;
+ },
+ [&flag] (const path& from, const path& to, bool force, bool pre)
+ {
+ cout << "move " << from << ' ' << to << ' ' << flag (force) << ' '
+ << flag (pre) << endl;
+ },
+ [&flag] (const path& p, bool force, bool pre)
+ {
+ cout << "remove " << p << ' ' << flag (force) << ' ' << flag (pre)
+ << endl;
+ },
+ [&option] (const vector<string>& args, size_t i)
+ {
+ cout << "option " << args[i] << endl;
+ return !option.empty () && args[i] == option ? 1 : 0;
+ },
+ [] (const duration& d)
+ {
+ cout << "sleep ";
+ to_stream (cout, d, false /* nanoseconds */);
+ cout << endl;
+ }
+ );
+ }
+ else if (a == "-i")
+ in = true;
+ else
+ break;
+ }
+
+ // Parse the builtin name and arguments.
+ //
+ assert (i != argc);
+ name = argv[i++];
+
+ for (; i != argc; ++i)
+ args.push_back (argv[i]);
+
+ // Read out additional arguments from stdin.
+ //
+ if (in)
+ {
+ string s;
+ while (!eof (getline (cin, s)))
+ args.push_back (move (s));
+ }
+
+ // Execute the builtin.
+ //
+ builtin_function* bf (builtins.find (name));
+
+ if (bf == nullptr)
+ {
+ cerr << "unknown builtin '" << name << "'" << endl;
+ return 1;
+ }
+
+ uint8_t r; // Storage.
+ builtin b (bf (r, args, nullfd, nullfd, nullfd, cwd, callbacks));
+ return b.wait ();
+}
diff --git a/tests/builtin/echo.testscript b/tests/builtin/echo.testscript
new file mode 100644
index 0000000..562a14c
--- /dev/null
+++ b/tests/builtin/echo.testscript
@@ -0,0 +1,26 @@
+# file : tests/builtin/echo.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "echo"
+
+: string
+:
+$* foo >foo
+
+: strings
+:
+$* foo bar >"foo bar"
+
+: big
+:
+: Echo a big string (about 100K) to test that the builtin is asynchronous.
+:
+{
+ s="--------------------------------";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s";
+ test.options += -i; # Pass the echo argument via the driver's stdin.
+ $* <"$s" | cat >"$s"
+}
diff --git a/tests/builtin/ln.testscript b/tests/builtin/ln.testscript
new file mode 100644
index 0000000..ed4ca67
--- /dev/null
+++ b/tests/builtin/ln.testscript
@@ -0,0 +1,217 @@
+# file : tests/builtin/ln.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "ln"
+test.options += -c
+
+: unknown-option
+:
+$* -u >'option -u' 2>"ln: unknown option '-u'" == 1
+
+: args
+:
+{
+ : -s-option
+ :
+ $* 2>"ln: missing -s|--symbolic option" == 1
+
+ : none
+ :
+ $* -s 2>"ln: missing arguments" == 1
+
+ : no-target
+ :
+ $* -s a 2>"ln: missing target path" == 1
+
+ : no-trailing-sep
+ :
+ $* -s a b c 2>"ln: multiple target paths with non-directory link path" == 1
+
+ : empty
+ :
+ {
+ : link
+ :
+ $* -s '' 2>"ln: invalid path ''" == 1
+
+ : target1
+ :
+ $* -s '' a 2>"ln: invalid path ''" == 1
+
+ : target2
+ :
+ $* -s '' a b/ 2>"ln: invalid path ''" == 1
+ }
+}
+
+: file
+:
+: Test creating a file symlink.
+:
+{
+ : non-existing-link-path
+ :
+ {
+ touch a;
+
+ $* -s a b >>/~%EOO% &b;
+ %create .+/b true%
+ %create .+/b false%
+ EOO
+
+ test -f b
+ }
+
+ : existing-link
+ :
+ {
+ : file
+ :
+ {
+ touch a b;
+
+ $* -s a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b' to '.+/a': .+%|
+ %ln: unable to copy file '.+/a' to '.+/b': .+%
+ %)
+ EOE
+ }
+
+ : dir
+ :
+ {
+ touch a;
+ mkdir b;
+
+ $* -s a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b' to '.+/a': .+%|
+ %ln: unable to copy file '.+/a' to '.+/b': .+%
+ %)
+ EOE
+ }
+ }
+
+ : non-existing
+ {
+ : target
+ :
+ {
+ $* -s a b 2>>/~%EOE% != 0
+ %ln: unable to create symlink to '.+/a': no such file or directory%
+ EOE
+ }
+
+ : link-dir
+ :
+ {
+ touch a;
+
+ $* -s a b/c >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/c true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b/c' to '.+/a': .+%|
+ %ln: unable to copy file '.+/a' to '.+/b/c': .+%
+ %)
+ EOE
+ }
+ }
+}
+
+: dir
+:
+: Test creating a directory symlink.
+:
+{
+ : non-existing-link-path
+ :
+ {
+ mkdir a;
+ touch a/b;
+
+ $* -s a c >>/~%EOO% &c;
+ %create .+/c true%
+ %create .+/c false%
+ EOO
+
+ test -f c/b
+ }
+
+ : existing-link
+ :
+ {
+ : dir
+ :
+ {
+ mkdir a b;
+
+ $* -s a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b' to '.+/a': .+%|
+ %ln: unable to copy directory '.+/a' to '.+/b': .+%
+ %)
+ EOE
+ }
+
+ : file
+ :
+ {
+ mkdir a;
+ touch b;
+
+ $* -s a b >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b' to '.+/a': .+%|
+ %ln: unable to copy directory '.+/a' to '.+/b': .+%
+ %)
+ EOE
+ }
+ }
+
+ : non-existing
+ {
+ : link-dir
+ :
+ {
+ mkdir a;
+
+ $* -s a b/c >>/~%EOO% 2>>/~%EOE% != 0
+ %create .+/b/c true%
+ EOO
+ %(
+ %ln: unable to create .+link '.+/b/c' to '.+/a': .+%|
+ %ln: unable to copy directory '.+/a' to '.+/b/c': .+%
+ %)
+ EOE
+ }
+ }
+}
+
+: multiple-targets
+:
+: Test creating links for multiple targets in the specified directory.
+:
+{
+ touch a;
+ mkdir b c;
+
+ $* -s a b c/ >>/~%EOO% &c/a &c/b;
+ %create .+/c/a true%
+ %create .+/c/a false%
+ %create .+/c/b true%
+ %create .+/c/b false%
+ EOO
+
+ test -f c/a && test -d c/b
+}
diff --git a/tests/builtin/mkdir.testscript b/tests/builtin/mkdir.testscript
new file mode 100644
index 0000000..5225caa
--- /dev/null
+++ b/tests/builtin/mkdir.testscript
@@ -0,0 +1,102 @@
+# file : tests/builtin/mkdir.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "mkdir"
+test.options += -c
+
+: unknown-option
+:
+$* -u >'option -u' 2>"mkdir: unknown option '-u'" == 1
+
+: parent
+:
+{
+ $* -p a/b >>/~%EOO% &a/ &a/b/;
+ %create .+/a/ true%
+ %create .+/a/ false%
+ %create .+/a/b/ true%
+ %create .+/a/b/ false%
+ EOO
+
+ touch a/a a/b/b
+}
+
+: exists
+:
+{
+ $* -p a a a/b a/b >>/~%EOO% &a/ &a/b/
+ %create .+/a/ true%
+ %create .+/a/ false%
+ %create .+/a/b/ true%
+ %create .+/a/b/ false%
+ EOO
+}
+
+: dirs
+:
+{
+ $* a b >>/~%EOO% &a/ &b/;
+ %create .+/a/ true%
+ %create .+/a/ false%
+ %create .+/b/ true%
+ %create .+/b/ false%
+ EOO
+
+ touch a/a b/b
+}
+
+: double-dash
+:
+: Make sure '-p' directory is created.
+:
+{
+ $* -p -- -p >>/~%EOO% &-p/;
+ %create .+/-p/ true%
+ %create .+/-p/ false%
+ EOO
+
+ touch -- -p/a
+}
+
+: no-args
+:
+: Test passing no arguments.
+:
+{
+ $* 2>"mkdir: missing directory" == 1
+}
+
+: empty-path
+:
+: Test creation of empty directory path.
+:
+{
+ $* '' 2>"mkdir: invalid path ''" == 1
+}
+
+: already-exists
+:
+: Test creation of an existing directory.
+:
+{
+ $* a a >>/~%EOO% 2>>/~%EOE% &a/ == 1
+ %create .+/a/ true%
+ %create .+/a/ false%
+ %create .+/a/ true%
+ EOO
+ %mkdir: unable to create directory '.+/a': .+%
+ EOE
+}
+
+: not-exists
+:
+: Test creation of a directory with non-existent parent.
+:
+{
+ $* a/b >>/~%EOO% 2>>/~%EOE% == 1
+ %create .+/a/b/ true%
+ EOO
+ %mkdir: unable to create directory '.+/a/b': .+%
+ EOE
+}
diff --git a/tests/builtin/mv.testscript b/tests/builtin/mv.testscript
new file mode 100644
index 0000000..2647d0f
--- /dev/null
+++ b/tests/builtin/mv.testscript
@@ -0,0 +1,182 @@
+# file : tests/builtin/mv.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "mv"
+test.options += -c
+
+: unknown-option
+:
+$* -u >'option -u' 2>"mv: unknown option '-u'" == 1
+
+: args
+:
+{
+ : none
+ :
+ $* 2>"mv: missing arguments" == 1
+
+ : no-source
+ :
+ $* a 2>"mv: missing source path" == 1
+
+ : no-trailing-sep
+ :
+ $* a b c 2>"mv: multiple source paths without trailing separator for destination directory" == 1
+
+ : empty
+ :
+ {
+ : dest
+ :
+ $* '' 2>"mv: invalid path ''" == 1
+
+ : src1
+ :
+ $* '' a 2>"mv: invalid path ''" == 1
+
+ : src2
+ :
+ $* '' a b/ 2>"mv: invalid path ''" == 1
+ }
+}
+
+: synopsis-1
+:
+: Move an entity to the specified path.
+:
+{
+ : file
+ :
+ {
+ : existing
+ :
+ {
+ : to-non-existing
+ :
+ {
+ touch a &!a;
+
+ $* a b >>/~%EOO% &b;
+ %move .+/a .+/b false true%
+ %move .+/a .+/b false false%
+ EOO
+
+ test -f b && test -f a == 1
+ }
+
+ : to-existing
+ :
+ {
+ touch a b &!a;
+
+ $* a b >>/~%EOO%;
+ %move .+/a .+/b false true%
+ %move .+/a .+/b false false%
+ EOO
+
+ test -f b && test -f a == 1
+ }
+
+ : to-self
+ :
+ {
+ touch a;
+
+ $* a a >/~'%move .+/a .+/a false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to itself%
+ EOE
+ }
+
+ : to-dir
+ :
+ {
+ touch a;
+ mkdir b;
+
+ $* a b >/~'%move .+/a .+/b false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to '.+/b': .+%
+ EOE
+ }
+ }
+ }
+
+ : dir
+ :
+ {
+ : existing
+ :
+ {
+ : to-non-existing
+ :
+ {
+ mkdir a &!a/;
+
+ $* a b &b/ >>/~%EOO%;
+ %move .+/a .+/b false true%
+ %move .+/a .+/b false false%
+ EOO
+
+ test -d b && test -d a == 1
+ }
+
+ : to-non-empty
+ :
+ {
+ mkdir a b;
+ touch b/c;
+
+ $* a b >/~'%move .+/a .+/b false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to '.+/b': .+%
+ EOE
+ }
+
+ : to-non-dir
+ :
+ {
+ mkdir a;
+ touch b;
+
+ $* a b >/~'%move .+/a .+/b false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to '.+/b': .+%
+ EOE
+ }
+ }
+
+ : overlap
+ :
+ {
+ mkdir a;
+
+ $* a a/b >/~'%move .+/a .+/a/b false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to '.+/a/b': .+%
+ EOE
+ }
+ }
+
+ : non-existing
+ :
+ {
+ $* a b >/~'%move .+/a .+/b false true%' 2>>/~%EOE% != 0
+ %mv: unable to move entity '.+/a' to '.+/b': .+%
+ EOE
+ }
+}
+
+: synopsis-2
+:
+: Move entities into the specified directory.
+:
+{
+ mkdir a c &!a/;
+ touch a/b b &!a/b &!b;
+
+ $* a b c/ >>/~%EOO% &c/a/ &c/a/b &c/b;
+ %move .+/a .+/c/a false true%
+ %move .+/a .+/c/a false false%
+ %move .+/b .+/c/b false true%
+ %move .+/b .+/c/b false false%
+ EOO
+
+ test -d c/a && test -f c/a/b && test -f c/b
+}
diff --git a/tests/builtin/rm.testscript b/tests/builtin/rm.testscript
new file mode 100644
index 0000000..991b0f6
--- /dev/null
+++ b/tests/builtin/rm.testscript
@@ -0,0 +1,105 @@
+# file : tests/builtin/rm.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "rm"
+test.options += -c
+
+: unknown-option
+:
+$* -u >'option -u' 2>"rm: unknown option '-u'" == 1
+
+: no-args
+:
+{
+ : fail
+ :
+ : Removing with no arguments fails.
+ :
+ $* 2>"rm: missing file" == 1
+
+ : force
+ :
+ : Removing with no arguments succeeds with -f option.
+ :
+ $* -f
+}
+
+: file
+:
+{
+ : exists
+ :
+ : Removing existing file succeeds.
+ :
+ {
+ touch a &!a;
+
+ $* a >>/~%EOO%
+ %remove .+/a false true%
+ %remove .+/a false false%
+ EOO
+ }
+
+ : not-exists
+ :
+ {
+ : fail
+ :
+ : Removing non-existing file fails.
+ :
+ $* a >/~'%remove .+/a false true%' 2>>/~%EOE% == 1
+ %rm: unable to remove '.+/a': .+%
+ EOE
+
+ : force
+ :
+ : Removing non-existing file succeeds with -f option.
+ :
+ $* -f a >>/~%EOO%
+ %remove .+/a true true%
+ %remove .+/a true false%
+ EOO
+ }
+}
+
+: dir
+:
+{
+ : default
+ :
+ : Removing directory fails by default.
+ :
+ {
+ mkdir a;
+
+ $* a >/~'%remove .+/a false true%' 2>>/~%EOE% == 1
+ %rm: '.+/a' is a directory%
+ EOE
+ }
+
+ : recursive
+ :
+ : Removing directory succeeds with -r option.
+ :
+ {
+ mkdir -p a/b &!a &!a/b;
+
+ $* -r a >>/~%EOO%
+ %remove .+/a false true%
+ %remove .+/a false false%
+ EOO
+ }
+}
+
+: path
+:
+{
+ : empty
+ :
+ : Removing an empty path fails.
+ :
+ {
+ $* '' 2>"rm: invalid path ''" == 1
+ }
+}
diff --git a/tests/builtin/rmdir.testscript b/tests/builtin/rmdir.testscript
new file mode 100644
index 0000000..a63f701
--- /dev/null
+++ b/tests/builtin/rmdir.testscript
@@ -0,0 +1,95 @@
+# file : tests/builtin/rmdir.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "rmdir"
+test.options += -c
+
+: unknown-option
+:
+rmdir -u 2>"rmdir: unknown option '-u'" == 1
+
+: no-args
+:
+{
+ : fail
+ :
+ : Removing with no arguments fails.
+ :
+ $* 2>"rmdir: missing directory" == 1
+
+ : force
+ :
+ : Removing with no arguments succeeds with -f option.
+ :
+ $* -f
+}
+
+: dir
+:
+{
+ : empty-path
+ :
+ : Removing an empty path fails.
+ :
+ $* '' 2>"rmdir: invalid path ''" == 1
+
+ : exists
+ :
+ : Removing existing directory succeeds.
+ :
+ {
+ mkdir a &!a;
+
+ $* a >>/~%EOO%
+ %remove .+/a/ false true%
+ %remove .+/a/ false false%
+ EOO
+ }
+
+ : not-exists
+ :
+ {
+ : fail
+ : Removing non-existing directory fails.
+ :
+ {
+ $* a >/~'%remove .+/a/ false true%' 2>>/~%EOE% == 1
+ %rmdir: unable to remove '.+/a': .+%
+ EOE
+ }
+
+ : force
+ :
+ : Removing non-existing directory succeeds with -f option.
+ :
+ $* -f a >>/~%EOO%
+ %remove .+/a/ true true%
+ %remove .+/a/ true false%
+ EOO
+ }
+
+ : not-empty
+ :
+ : Removing non-empty directory fails.
+ :
+ {
+ mkdir -p a/b;
+
+ $* a >/~'%remove .+/a/ false true%' 2>>/~%EOE% == 1
+ %rmdir: unable to remove '.+/a': .+%
+ EOE
+ }
+
+ : not-dir
+ :
+ : Removing not a directory path fails.
+ :
+ {
+ touch a;
+
+ $* a >/~'%remove .+/a/ false true%' 2>>/~%EOE% == 1
+ %rmdir: unable to remove '.+/a': .+%
+ EOE
+ }
+}
diff --git a/tests/builtin/sed.testscript b/tests/builtin/sed.testscript
new file mode 100644
index 0000000..0d032bc
--- /dev/null
+++ b/tests/builtin/sed.testscript
@@ -0,0 +1,350 @@
+# file : tests/builtin/sed.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "sed"
+test.options += -c
+
+: unknown-option
+:
+{
+ : unparsed
+ :
+ $* -u >'option -u' 2>"sed: unknown option '-u'" == 1
+
+ : parsed
+ :
+ {
+ test.options += -o -u
+
+ : start
+ :
+ $* -u -n -e 's/a/b/p' <'a' >>EOO
+ option -u
+ b
+ EOO
+
+ : middle
+ :
+ $* -n -u -e 's/a/b/p' <'a' >>EOO
+ option -u
+ b
+ EOO
+
+ : end
+ :
+ $* -n -e 's/a/b/p' -u <'a' >>EOO
+ option -u
+ b
+ EOO
+
+ : before-args
+ :
+ {
+ echo 'a' >=f;
+
+ $* -n -e 's/a/b/p' -u f >>EOO
+ option -u
+ b
+ EOO
+ }
+ }
+
+ : arg
+ :
+ {
+ echo 'a' >=-u;
+
+ $* -n -e 's/a/b/p' -- -u >'b'
+ }
+}
+
+: arg
+:
+{
+ : auto-prn
+ :
+ {
+ $* -n -e 's/fox/bar/' <'foo' : on
+ $* -e 's/fox/bar/' <'foo' >'foo' : off
+ }
+
+ : script
+ :
+ {
+ : missed
+ :
+ $* 2>>EOE != 0
+ sed: missing script
+ EOE
+
+ : missed-val
+ :
+ $* -e 2>>EOE != 0
+ sed: missing value for option '-e'
+ EOE
+
+ : empty
+ :
+ $* -e '' 2>>EOE != 0
+ sed: empty script
+ EOE
+
+ : multiple
+ :
+ $* -e 's/a//' -e 's/a//' 2>>EOE != 0
+ sed: multiple scripts
+ EOE
+
+ : invalid
+ :
+ $* -e 'z' 2>>EOE != 0
+ sed: only 's' command supported
+ EOE
+ }
+
+ : file
+ :
+ {
+ : exist
+ :
+ {
+ cat <'foo' >=f;
+
+ $* -e 's/foo/bar/' f >'bar'
+ }
+
+ : none
+ :
+ $* -e 's/foo/bar/' <'foo' >'bar'
+
+ : dash
+ :
+ $* -e 's/foo/bar/' - <'foo' >'bar'
+
+ : not-exist
+ :
+ $* -e 's/foo/bar/' f 2>>/~%EOE% != 0
+ %sed: unable to edit '.+/f': .+%
+ EOE
+
+ : empty
+ :
+ $* -e 's/foo/bar/' '' 2>>EOE != 0
+ sed: invalid path ''
+ EOE
+ }
+
+ : unexpected
+ :
+ $* -e 's/a//' a b 2>>EOE != 0
+ sed: unexpected argument 'b'
+ EOE
+}
+
+: command
+:
+{
+ : subst
+ :
+ {
+ : parsing
+ :
+ {
+ : delim
+ :
+ {
+ : none
+ :
+ $* -e 's' 2>>EOE != 0
+ sed: no delimiter for 's' command
+ EOE
+
+ : invalid
+ :
+ $* -e 's\\' 2>>EOE != 0
+ sed: invalid delimiter for 's' command
+ EOE
+ }
+
+ : regex
+ :
+ {
+ : unterminated
+ :
+ $* -e 's/foo' 2>>/EOE != 0
+ sed: unterminated 's' command regex
+ EOE
+
+ : empty
+ :
+ $* -e 's///' 2>>EOE != 0
+ sed: empty regex in 's' command
+ EOE
+
+ : invalid
+ :
+ : Note that old versions of libc++ (for example 1.1) do not detect some
+ : regex errors. For example '*' is parsed successfully.
+ :
+ $* -e 's/foo[/bar/' 2>>~%EOE% != 0
+ %sed: invalid regex.*%
+ EOE
+ }
+
+ : unterminated-replacement
+ :
+ $* -e 's/foo/bar' 2>>/EOE != 0
+ sed: unterminated 's' command replacement
+ EOE
+
+ : invalid-flags
+ :
+ $* -e 's/foo/bar/a' 2>>EOE != 0
+ sed: invalid 's' command flag 'a'
+ EOE
+ }
+
+ : exec
+ :
+ {
+ : flags
+ :
+ {
+ : global
+ :
+ {
+ $* -e 's/o/a/g' <'foo' >'faa' : on
+ $* -e 's/o/a/' <'foo' >'fao' : off
+ }
+
+ : icase
+ :
+ {
+ $* -e 's/O/a/i' <'foo' >'fao' : on
+ $* -e 's/O/a/' <'foo' >'foo' : off
+ }
+
+ : print
+ :
+ {
+ $* -n -e 's/o/a/p' <'foo' >'fao' : on-match
+ $* -n -e 's/o/a/' <'foo' : off-match
+ $* -n -e 's/u/a/p' <'foo' : on-no-match
+ }
+ }
+
+ : search
+ {
+ : anchor
+ :
+ {
+ $* -n -e 's/^o/a/gp' <'oof' >'aof' : begin
+ $* -n -e 's/o$/a/gp' <'foo' >'foa' : end
+ }
+
+ : match
+ : Match corner cases
+ :
+ {
+ $* -n -e 's/a/b/p' <'a' >'b' : full
+ $* -n -e 's/a/b/p' <'ac' >'bc' : left
+ $* -n -e 's/a/b/p' <'ca' >'cb' : right
+ $* -n -e 's/a/b/pg' <'xaax' >'xbbx' : adjacent
+ }
+ }
+
+ : replacement
+ :
+ {
+ : ecma-escape
+ :
+ {
+ $* <'xay' -e 's/a/$b/' >'x$by' : none
+ $* <'xay' -e 's/a/$/' >'x$y' : none-term
+ $* <'xay' -e 's/a/$$/' >'x$y' : self
+ $* <'xay' -e 's/a/b$&c/' >'xbacy' : match
+ $* <'xay' -e 's/a/b$`c/' >'xbxcy' : match-precede
+ $* <'xay' -e "s/a/b\$'c/" >'xbycy' : match-follow
+
+ : capture
+ :
+ $* <'abcdefghij' -e 's/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)/$1$10/' >'aj'
+ }
+
+ : perl-escape
+ :
+ {
+ $* <'xay' -e 's/a/\b/' >'xby' : none
+ $* <'xay' -e 's/a/\/' >'xy' : none-term
+ $* <'xay' -e 's/a/\\/' >'x\y' : self
+
+ : capture
+ :
+ $* <'abcdefghij' -e 's/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)/\1\10/' >'aa0'
+
+ : upper
+ :
+ {
+ $* <'xay' -e 's/a/\U/' >'xy' : none
+ $* <'xay' -e 's/a/\Uvz/' >'xVZy' : repl
+ $* <'xay' -e 's/a/\Uv\Ez/' >'xVzy' : end
+ $* <'aa' -e 's/a/v\Uz/g' >'vZvZ' : locality
+ $* <'xay' -e 's/(a)/\U\1/' >'xAy' : capt
+ $* <'x-y' -e 's/(a?)-/\U\1z/' >'xZy' : capt-empty
+ $* <'xay' -e 's/a/\uvz/' >'xVzy' : once
+ }
+
+ : lower
+ :
+ {
+ $* <'xay' -e 's/a/\lVZ/' >'xvZy' : once
+ }
+ }
+ }
+
+ $* -e 's/a//' <:'b' >'b' : no-newline
+ $* -e 's/a//' <:'' : empty-stdin
+
+ : empty-file
+ :
+ {
+ touch f;
+
+ $* -e 's/a//' f
+ }
+ }
+ }
+}
+
+: in-place
+:
+{
+ : no-file
+ :
+ $* -i -e 's/a/b/' 2>>EOE != 0
+ sed: -i|--in-place option specified while reading from stdin
+ EOE
+
+ : edit
+ :
+ {
+ cat <'foo' >=f;
+
+ $* -i -e 's/foo/bar/' f;
+
+ cat f >'bar'
+ }
+}
+
+: big
+:
+: Sed a big file (about 100K) to test that the builtin is asynchronous.
+:
+{
+ s="--------------------------------"
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s"
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s"
+ s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s"
+ cat <"$s" | $* -e 's/^x//' | cat >"$s"
+}
diff --git a/tests/builtin/sleep.testscript b/tests/builtin/sleep.testscript
new file mode 100644
index 0000000..a334cba
--- /dev/null
+++ b/tests/builtin/sleep.testscript
@@ -0,0 +1,42 @@
+# file : tests/builtin/sleep.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "sleep"
+
+: unknown-option
+:
+$* -u 2>"sleep: unknown option '-u'" == 1
+
+: success
+:
+{
+ : custom
+ :
+ {
+ test.options += -c;
+ $* 1 >'sleep 01 seconds'
+ }
+
+ : own
+ :
+ $* 1
+}
+
+: no-time
+:
+: Test passing no time interval.
+:
+$* 2>"sleep: missing time interval" != 0
+
+: invalid-time
+:
+: Test passing invalid time interval.
+:
+$* 1a 2>"sleep: invalid time interval '1a'" != 0
+
+: unexpected-arg
+:
+: Test passing extra argument.
+:
+$* 1 1 2>"sleep: unexpected argument '1'" != 0
diff --git a/tests/builtin/test.testscript b/tests/builtin/test.testscript
new file mode 100644
index 0000000..3e132d9
--- /dev/null
+++ b/tests/builtin/test.testscript
@@ -0,0 +1,84 @@
+# file : tests/builtin/test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "test"
+
+: file
+:
+{
+ : exists
+ :
+ touch a;
+ $* -f a
+
+ : not-exists
+ :
+ $* -f a == 1
+
+ : not-file
+ :
+ $* -f . == 1
+}
+
+: dir
+:
+{
+ : exists
+ :
+ $* -d .
+
+ : not-exists
+ :
+ $* -d a == 1
+
+ : not-dir
+ :
+ touch a;
+ $* -d a == 1
+}
+
+: options
+:
+{
+ : unknown
+ :
+ $* -u 2>"test: unknown option '-u'" == 2
+
+ : none
+ :
+ $* 2>"test: either -f|--file or -d|--directory must be specified" == 2
+
+ : both-file-dir
+ :
+ $* -fd 2>"test: both -f|--file and -d|--directory specified" == 2
+}
+
+: args
+:
+{
+ : none
+ :
+ $* -f 2>"test: missing path" == 2
+
+ : unexpected
+ :
+ $* -f a b 2>"test: unexpected argument 'b'" == 2
+
+ : empty-path
+ :
+ $* -d '' 2>"test: invalid path ''" == 2
+}
+
+: cwd
+:
+: When cross-testing we cannot guarantee that host absolute paths are
+: recognized by the target process.
+:
+if ($test.target == $build.host)
+{
+ test.options += -d $~/a;
+ mkdir -p a/b;
+
+ $* -d b
+}
diff --git a/tests/builtin/touch.testscript b/tests/builtin/touch.testscript
new file mode 100644
index 0000000..aa0ea10
--- /dev/null
+++ b/tests/builtin/touch.testscript
@@ -0,0 +1,92 @@
+# file : tests/builtin/touch.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = "touch"
+
+: file
+:
+$* a &a
+
+: file-create
+:
+: Test that file is created. If it didn't then 'rm' would fail.
+:
+{
+ $* a &!a;
+
+ rm a
+}
+
+: file-update
+:
+: Test that existing file touch doesn't fail.
+:
+{
+ cat <'' >=a;
+
+ $* a
+}
+
+: callback
+:
+: Test that the callback is not called for touching an existing file.
+:
+{
+ test.options += -c;
+
+ $* a >>/~%EOO% &a;
+ %create .+/a true%
+ %create .+/a false%
+ EOO
+
+ $* >>/~%EOO% a
+ %create .+/a true%
+ %create .+/a false%
+ EOO
+}
+
+: unknown-option
+:
+$* -u 2>"touch: unknown option '-u'" == 1
+
+: no-args
+:
+: Test passing no arguments.
+:
+$* 2>"touch: missing file" != 0
+
+: empty-path
+:
+: Test touching an empty path.
+:
+$* '' 2>"touch: invalid path ''" != 0
+
+: dir-update
+:
+: Test touching an existing directory.
+:
+{
+ mkdir a;
+
+ $* a 2>~'%touch: cannot create/update .+: .+%' != 0
+}
+
+: after
+:
+{
+ : success
+ :
+ {
+ $* a &a;
+ $* --after a b &b
+ }
+
+ : no-value
+ :
+ $* --after 2>"touch: missing value for option '--after'" != 0
+
+ : not-exists
+ :
+ touch --after a b 2>~"%touch: cannot obtain file '.+a' modification time: .+%" != 0
+}