diff options
Diffstat (limited to 'tests/process/driver.cxx')
-rw-r--r-- | tests/process/driver.cxx | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx new file mode 100644 index 0000000..ba61ea8 --- /dev/null +++ b/tests/process/driver.cxx @@ -0,0 +1,357 @@ +// file : tests/process/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <stdlib.h> // getenv(), setenv(), _putenv_s() + +#include <ios> +#include <string> +#include <vector> +#include <cassert> +#include <iostream> +#include <iterator> // istreambuf_iterator, ostream_iterator +#include <algorithm> // copy() + +#include <butl/path> +#include <butl/process> +#include <butl/fdstream> + +using namespace std; +using namespace butl; + +static bool +exec (const path& p, + vector<char> in = vector<char> (), + bool out = false, + bool err = false, + bool pipeline = false, + bool bin = true, // Set binary mode for file descriptors. + dir_path wd = dir_path ()) // Set the working dir for the child process. +{ + using cstrings = vector<const char*>; + + assert (!in.empty () || (!out && !err)); // Nothing to output if no input. + assert (!pipeline || out); // To pipeline need to output something. + + const char* cwd (!wd.empty () ? wd.string ().c_str () : nullptr); + + auto args = [&p, bin, &cwd](bool i, bool o, bool e) -> cstrings + { + cstrings a {p.string ().c_str (), "-c"}; + + if (i) + a.push_back ("-i"); + + if (o) + a.push_back ("-o"); + + if (e) + a.push_back ("-e"); + + if (bin) + a.push_back ("-b"); + + if (cwd != nullptr) + a.push_back (cwd); + + a.push_back (nullptr); + return a; + }; + + try + { + bool r (true); + cstrings a (args (!in.empty (), out, err)); + + // If both o and e are true, then redirect STDERR to STDOUT, so both can be + // read from the same stream. + // + process pr (cwd, + a.data (), + !in.empty () ? -1 : 0, + out ? -1 : 1, + err ? (out ? 1 : -1) : 2); + + try + { + if (!in.empty ()) + { + bool s; + r = !pr.try_wait (s); // Couldn't exit as waiting for the input. + + auto bin_mode = [bin](int fd) -> int + { + if (bin) + fdmode (fd, fdtranslate::binary); + + return fd; + }; + + ofdstream os (bin_mode (pr.out_fd)); + os.exceptions (ofdstream::badbit); + copy (in.begin (), in.end (), ostream_iterator<char> (os)); + os.close (); + + if (out) + { + vector<char> o; + + if (pipeline) + { + // Here we test both passing process output fd as an input for + // another process (pr2.in = pr.out), as well as passing process + // input fd as an output for another one (pr2.out = pr3.in). The + // overall pipeline looks like 'os -> pr -> pr2 -> pr3 -> is'. + // + cstrings a (args (true, true, false)); + process pr3 (cwd, a.data (), -1, -1); + process pr2 (cwd, a.data (), pr, bin_mode (pr3.out_fd)); + + bool cr (fdclose (pr3.out_fd)); + assert (cr); + + ifdstream is (bin_mode (pr3.in_ofd)); + + o = vector<char> ( + (istreambuf_iterator<char> (is)), istreambuf_iterator<char> ()); + + r = pr2.wait () && r; + r = pr3.wait () && r; + } + else + { + ifdstream is (bin_mode (pr.in_ofd)); + + o = vector<char> ( + (istreambuf_iterator<char> (is)), istreambuf_iterator<char> ()); + } + + if (err) + { + // If STDERR is redirected to STDOUT then output will be + // duplicated. + // + vector<char> v (in); + in.reserve (in.size () * 2); + in.insert (in.end (), v.begin (), v.end ()); + } + + r = in == o && r; + } + + if (err && !out) + { + ifdstream is (bin_mode (pr.in_efd)); + + vector<char> e + ((istreambuf_iterator<char> (is)), istreambuf_iterator<char> ()); + + r = in == e && r; + } + } + } + catch (const ios_base::failure&) + { + r = false; + } + + bool s; + return pr.wait () && pr.try_wait (s) && s && r; + } + catch (const process_error& e) + { + if (e.child ()) + exit (1); + + return false; + } +} + +static bool +exec (const path& p, + const string& i, + bool o = false, + bool e = false, + bool pipeline = false, + dir_path wd = dir_path ()) +{ + return exec ( + p, vector<char> (i.begin (), i.end ()), o, e, pipeline, false, wd); +} + +int +main (int argc, const char* argv[]) +try +{ + bool child (false); + bool in (false); + bool out (false); + bool err (false); + bool bin (false); + dir_path wd; // Working directory. + + assert (argc > 0); + + int i (1); + for (; i != argc; ++i) + { + string v (argv[i]); + if (v == "-c") + child = true; + else if (v == "-i") + in = true; + else if (v == "-o") + out = true; + else if (v == "-e") + err = true; + else if (v == "-b") + bin = true; + else + { + if (!wd.empty ()) + break; + + try + { + wd = dir_path (v); + } + catch (const invalid_path&) + { + break; + } + } + } + + if (i != argc) + { + if (!child) + cerr << "usage: " << argv[0] << " [-c] [-i] [-o] [-e] [-b] [<dir>]" + << endl; + + return 1; + } + + path p; + + try + { + p = path (argv[0]); + } + catch (const invalid_path&) + { + if (child) + return 1; + + assert (false); + } + + if (child) + { + // Child process. Check if the working directory argument matches the + // current directory if specified. Read input data if requested, optionally + // write it to cout and/or cerr. + // + + if (!wd.empty () && wd.realize () != dir_path::current ()) + return 1; + + if (!in) + return 0; // Nothing to read, so nothing to write. + + try + { + if (bin) + { + stdin_fdmode (fdtranslate::binary); + stdout_fdmode (fdtranslate::binary); + stderr_fdmode (fdtranslate::binary); + } + + vector<char> data + ((istreambuf_iterator<char> (cin)), istreambuf_iterator<char> ()); + + if (out) + { + cout.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator<char> (cout)); + } + + if (err) + { + cerr.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator<char> (cerr)); + } + } + catch (const ios_base::failure&) + { + return 1; + } + + return 0; + } + + const char* s ("ABC\nXYZ"); + + assert (exec (p)); + assert (exec (p, s)); + assert (exec (p, s, true)); + assert (exec (p, s, true, false, true)); // Same but with piping. + assert (exec (p, s, false, true)); + assert (exec (p, s, true, true)); + assert (exec (p, s, true, true, true)); // Same but with piping. + + // Transmit large binary data through the child. + // + vector<char> v; + v.reserve (5000 * 256); + for (size_t i (0); i < 5000; ++i) + { + for (size_t c (0); c < 256; ++c) + v.push_back (c); + } + + assert (exec (p, v, true, true)); + assert (exec (p, v, true, true, true)); // Same as above but with piping. + + // Execute the child using the full path. + // + path fp (p); + fp.complete (); + assert (exec (fp)); + + // Execute the child using the relative path. + // + dir_path::current (fp.directory ()); + + assert (exec (path (".") / fp.leaf ())); + + // Fail for unexistent file path. + // + assert (!exec (path ("./dr"))); + + // Execute the child using file name having PATH variable being properly set. + // + string paths (fp.directory ().string ()); + + if (char const* s = getenv ("PATH")) + paths += string (1, path::traits::path_separator) + s; + +#ifndef _WIN32 + assert (setenv ("PATH", paths.c_str (), 1) == 0); +#else + assert (_putenv_s ("PATH", paths.c_str ()) == 0); +#endif + + dir_path::current (fp.directory () / dir_path ("..")); + + assert (exec (fp.leaf ())); + + // Same as above but also with changing the child current directory. + // + assert (exec ( + fp.leaf (), vector<char> (), false, false, false, true, fp.directory ())); +} +catch (const system_error&) +{ + assert (false); +} |