aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-03-18 13:19:12 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-03-23 15:40:19 +0300
commit58f0d15c5da74f7908e57ef60ceb5c3d0a7319e3 (patch)
treeccf9a01aec53f2c1396c6ec85fc6a3186af22260 /tests
parentede5f2102b2047a75476d3f5db81dac572196aa6 (diff)
Add command running API
Diffstat (limited to 'tests')
-rw-r--r--tests/command/buildfile8
-rw-r--r--tests/command/driver.cxx255
-rw-r--r--tests/command/testscript155
3 files changed, 418 insertions, 0 deletions
diff --git a/tests/command/buildfile b/tests/command/buildfile
new file mode 100644
index 0000000..cb0f272
--- /dev/null
+++ b/tests/command/buildfile
@@ -0,0 +1,8 @@
+# file : tests/command/buildfile
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+libs += $stdmod_lib
+
+exe{driver}: {hxx cxx}{*} $libs testscript
diff --git a/tests/command/driver.cxx b/tests/command/driver.cxx
new file mode 100644
index 0000000..a1baf68
--- /dev/null
+++ b/tests/command/driver.cxx
@@ -0,0 +1,255 @@
+// file : tests/command/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules
+#include <ios>
+#include <string>
+#include <vector>
+#include <iostream>
+#include <stdexcept> // invalid_argument
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules
+#ifdef __cpp_lib_modules
+import std.core;
+import std.io;
+#endif
+import butl.path;
+import butl.path_io;
+import butl.process; // process::print()
+import butl.command;
+import butl.utility;
+import butl.optional;
+#else
+#include <libbutl/path.mxx>
+#include <libbutl/path-io.mxx>
+#include <libbutl/process.mxx>
+#include <libbutl/command.mxx>
+#include <libbutl/utility.mxx>
+#include <libbutl/optional.mxx>
+#endif
+
+using namespace std;
+using namespace butl;
+
+// Usages:
+//
+// argv[0] [-d <dir>] [-v <name>[=<value>]] [-s <name>=<value>] [-c <char>]
+// [-p] <command>
+//
+// argv[0] -C [-A] [-D] [-V <name>] [-S <status>] <arguments>
+//
+// In the first form run the specified command, changing the current
+// directory, (re)setting the environment variables, performing substitutions,
+// and printing the "expanded" command line, if requested.
+//
+// In the second form optionally print the program arguments, CWD, the
+// environment variable values and exit with the status specified. This mode
+// is normally used for the command being tested to dump the environment
+// obtained from the caller.
+//
+// -d <dir>
+// Change the CWD for the command process.
+//
+// -v <name>[=<value>]
+// (Un)set the environment variable for the command process. Can be
+// specified multiple times.
+//
+// -s <name>=<value>
+// Perform command line substitutions using the specified variable. Can be
+// specified multiple times.
+//
+// -c <char>
+// Substitution symbol. The default is '@'.
+//
+// -p
+// Print the "expanded" command line.
+//
+// -C
+// Print the program arguments (-A), CWD (-D), environment variables (-V)
+// to stdout and exit with the status specifies (-S).
+//
+// -A
+// Print the program arguments to stdout.
+//
+// -D
+// Print the process CWD to stdout.
+//
+// -V <name>
+// Print the environment variable value (or <unset>) to stdout. Can be
+// specified multiple times.
+//
+// -S <status>
+// Exit with the specified status code.
+//
+int
+main (int argc, const char* argv[])
+{
+ using butl::optional;
+ using butl::getenv;
+
+ // Parse and validate the arguments.
+ //
+ dir_path cwd;
+ vector<const char*> vars;
+ optional<command_substitution_map> substitutions;
+ char subst ('@');
+ optional<string> command;
+ bool print (false);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string o (argv[i]);
+
+ if (o == "-d")
+ {
+ assert (++i != argc);
+ cwd = dir_path (argv[i]);
+ }
+ else if (o == "-v")
+ {
+ assert (++i != argc);
+ vars.push_back (argv[i]);
+ }
+ else if (o == "-s")
+ {
+ assert (++i != argc);
+
+ if (!substitutions)
+ substitutions = command_substitution_map ();
+
+ string v (argv[i]);
+ size_t p (v.find ('='));
+
+ assert (p != string::npos && p != 0);
+
+ (*substitutions)[string (v, 0, p)] = string (v, p + 1);
+ }
+ else if (o == "-c")
+ {
+ assert (++i != argc);
+
+ string v (argv[i]);
+
+ assert (v.size () == 1);
+ subst = v[0];
+ }
+ else if (o == "-p")
+ {
+ print = true;
+ }
+ else if (o == "-C")
+ {
+ assert (i == 1); // Must go first.
+
+ int ec (0);
+ bool print_cwd (false);
+
+ // Include the program path into the arguments list.
+ //
+ vector<const char*> args ({argv[0]});
+
+ for (++i; i != argc; ++i)
+ {
+ o = argv[i];
+
+ if (o == "-A")
+ {
+ print = true;
+ }
+ else if (o == "-D")
+ {
+ print_cwd = true;
+ }
+ else if (o == "-V")
+ {
+ assert (++i != argc);
+ vars.push_back (argv[i]);
+ }
+ else if (o == "-S")
+ {
+ assert (++i != argc);
+ ec = stoi (argv[i]);
+ }
+ else
+ args.push_back (argv[i]);
+ }
+
+ args.push_back (nullptr);
+
+ if (print)
+ {
+ process::print (cout, args.data ());
+ cout << endl;
+ }
+
+ if (print_cwd)
+ cout << dir_path::current_directory () << endl;
+
+ for (const auto& v: vars)
+ {
+ optional<string> vv (getenv (v));
+ cout << (vv ? *vv : "<unset>") << endl;
+ }
+
+ return ec;
+ }
+ else
+ {
+ assert (!command);
+ command = argv[i];
+ }
+ }
+
+ assert (command);
+
+ // Run the command.
+ //
+ try
+ {
+ optional<process_env> pe;
+
+ if (!cwd.empty () || !vars.empty ())
+ pe = process_env (process_path (), cwd, vars);
+
+ process_exit e (command_run (*command,
+ pe,
+ substitutions,
+ subst,
+ [print] (const char* const args[], size_t n)
+ {
+ if (print)
+ {
+ process::print (cout, args, n);
+ cout << endl;
+ }
+ }));
+
+ if (!e)
+ cerr << "process " << argv[0] << " " << e << endl;
+
+ return e.normal () ? e.code () : 1;
+ }
+ catch (const invalid_argument& e)
+ {
+ cerr << e << endl;
+ return 1;
+ }
+ catch (const ios::failure& e)
+ {
+ cerr << e << endl;
+ return 1;
+ }
+ catch (const process_error& e)
+ {
+ cerr << e << endl;
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/tests/command/testscript b/tests/command/testscript
new file mode 100644
index 0000000..4d45f4f
--- /dev/null
+++ b/tests/command/testscript
@@ -0,0 +1,155 @@
+# file : tests/command/testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+cmd="'$0' -C -A" # Command that prints its arguments to stdout.
+
+# Note that when cross-testing the driver may not be able to run the command
+# due to the meaningless program path.
+#
++if ($test.target != $build.host)
+ exit
+end
+
+: quioting
+:
+{
+ $* "$cmd 'abc def'" >~'%.+ "abc def"%'
+}
+
+: substitution
+:
+{
+ test.options += -s v1=abc -s v2=def
+
+ : program
+ :
+ {
+ $* -s "program=$0" '@program@ -C -A abc' >~'%.+driver.* abc%'
+ }
+
+ : args
+ :
+ {
+ $* "$cmd abc" >~'%.+ abc%' : none
+ $* "$cmd x@v1@" >~'%.+ xabc%' : single
+ $* "$cmd x@v1@y@v2@z" >~'%.+ xabcydefz%' : multiple
+ $* "$cmd @v1@@v2@" >~'%.+ abcdef%' : adjacent
+ }
+
+ : redirect
+ :
+ {
+ $* -s v=f "$cmd abc >@v@" &f;
+ cat f >~'%.+ abc%'
+ }
+}
+
+: redirect
+:
+{
+ : overwrite
+ :
+ {
+ $* -p "$cmd abc >f" >~'%.+driver.* -C -A abc >f%' &f;
+ cat f >~'%.+ abc%'
+ }
+
+ : append
+ :
+ {
+ echo 'xyz' >=f;
+ $* -p "$cmd abc >>f" >~'%.+driver.* -C -A abc >>f%';
+
+ cat f >>~%EOO%
+ xyz
+ %.+ abc%
+ EOO
+ }
+
+ : space-separated
+ :
+ {
+ : overwrite
+ :
+ {
+ $* "$cmd abc > f" &f;
+ cat f >~'%.+ abc%'
+ }
+
+ : append
+ :
+ {
+ echo 'xyz' >=f;
+ $* "$cmd abc >> f";
+
+ cat f >>~%EOO%
+ xyz
+ %.+ abc%
+ EOO
+ }
+ }
+
+ : not-redirect
+ :
+ $* -s v='>f' "$cmd abc @v@" >~'%.+ abc >f%'
+
+ : errors
+ :
+ {
+ $* "$cmd >d/f" 2>~"%unable to open stdout redirect file 'd/f'.*%" != 0 : io-failure
+ $* "$cmd > ''" 2> 'empty stdout redirect file path' != 0 : empty-path
+ }
+}
+
+: invalid-argument
+:
+{
+ $* "" 2>'no program path specified' != 0 : no-prog
+ $* "p 'abc def" 2>'unterminated quoted string' != 0 : unterminated
+ $* "p >" 2>'no stdout redirect file specified' != 0 : no-redirect-file
+ $* "p >>" 2>'no stdout redirect file specified' != 0 : no-append-file
+
+ : substitution
+ :
+ {
+ test.options += -s v=a
+
+ $* 'p @a b@' 2>"unmatched substitution character '@' in '@a'" != 0 : unterm-var
+ $* "p '@a b@'" 2>"whitespace in variable name 'a b'" != 0 : ws-var
+ $* 'p @x@' 2>"unknown variable 'x'" != 0 : unknown-var
+ }
+}
+
+: process
+:
+{
+ : cwd
+ :
+ {
+ mkdir abc;
+ $* -d abc "$cmd -D" >>/~%EOO%
+ %.+/driver.*%
+ %.+/abc%
+ EOO
+ }
+
+ : env-var
+ :
+ {
+ $* -v test=abc "$cmd -V test" >>/~%EOO%
+ %.+/driver.*%
+ abc
+ EOO
+ }
+
+ : error
+ :
+ {
+ $* "''" 2>'no such file or directory' != 0 : empty-prog
+ }
+
+ : non-zero-status
+ :
+ $* "$0 -C -S 10" 2>/~'%.+ exited with code 10%' == 10
+}