diff options
Diffstat (limited to 'libbuild2/test/script/runner.cxx')
-rw-r--r-- | libbuild2/test/script/runner.cxx | 250 |
1 files changed, 241 insertions, 9 deletions
diff --git a/libbuild2/test/script/runner.cxx b/libbuild2/test/script/runner.cxx index 53f6741..630d4c2 100644 --- a/libbuild2/test/script/runner.cxx +++ b/libbuild2/test/script/runner.cxx @@ -4,10 +4,10 @@ #include <libbuild2/test/script/runner.hxx> -#include <set> #include <ios> // streamsize #include <libbutl/regex.mxx> +#include <libbutl/builtin.mxx> #include <libbutl/fdstream.mxx> // fdopen_mode, fdnull(), fddup() #include <libbuild2/variable.hxx> @@ -18,7 +18,6 @@ #include <libbuild2/test/script/regex.hxx> #include <libbuild2/test/script/parser.hxx> -#include <libbuild2/test/script/builtin.hxx> #include <libbuild2/test/script/builtin-options.hxx> using namespace std; @@ -928,7 +927,8 @@ namespace build2 ? rmdir_buildignore ( ctx, d, - sp.root.target_scope.root_scope ()->root_extra->buildignore_file, + sp.root.target_scope.root_scope ()->root_extra-> + buildignore_file, v) : rmdir (ctx, d, v))); @@ -1145,6 +1145,21 @@ namespace build2 } } + // Sorted array of builtins that support filesystem entries cleanup. + // + static const char* cleanup_builtins[] = { + "cp", "ln", "mkdir", "mv", "touch"}; + + static inline bool + cleanup_builtin (const string& name) + { + return binary_search ( + cleanup_builtins, + cleanup_builtins + + sizeof (cleanup_builtins) / sizeof (*cleanup_builtins), + name); + } + static bool run_pipe (scope& sp, command_pipe::const_iterator bc, @@ -1205,6 +1220,8 @@ namespace build2 command_pipe::const_iterator nc (bc + 1); bool last (nc == ec); + const string& program (c.program.string ()); + // Prior to opening file descriptors for command input/output // redirects let's check if the command is the exit builtin. Being a // builtin syntactically it differs from the regular ones in a number @@ -1217,7 +1234,7 @@ namespace build2 // specify any redirects or exit code check sounds like a right thing // to do. // - if (c.program.string () == "exit") + if (program == "exit") { // In case the builtin is erroneously pipelined from the other // command, we will close stdin gracefully (reading out the stream @@ -1240,7 +1257,7 @@ namespace build2 if (err.type != redirect_type::none) fail (ll) << "exit builtin stderr cannot be redirected"; - // We can't make sure that there is not exit code check. Let's, at + // We can't make sure that there is no exit code check. Let's, at // least, check that non-zero code is not expected. // if (eq != (c.exit.code == 0)) @@ -1391,7 +1408,7 @@ namespace build2 // that. Checking that the user didn't specify any meaningless // redirects or exit code check sounds as a right thing to do. // - if (c.program.string () == "set") + if (program == "set") { if (!last) fail (ll) << "set builtin must be the last pipe command"; @@ -1583,7 +1600,7 @@ namespace build2 assert (ofd.out.get () != -1 && efd.get () != -1); optional<process_exit> exit; - builtin_func* bf (builtins.find (c.program.string ())); + builtin_function* bf (builtins.find (program)); bool success; @@ -1605,11 +1622,226 @@ namespace build2 if (verb >= 2) print_process (process_args ()); + // Some of the testscript builtins (cp, mkdir, etc) extend libbutl + // builtins (via callbacks) registering/moving cleanups for the + // filesystem entries they create/move, unless explicitly requested + // not to do so via the --no-cleanup option. + // + // Let's "wrap up" the cleanup-related flags into the single object + // to rely on "small function object" optimization. + // + struct cleanup + { + // Whether the cleanups are enabled for the builtin. Can be set to + // false by the parse_option callback if --no-cleanup is + // encountered. + // + bool enabled = true; + + // Whether to register cleanup for a filesystem entry being + // created/updated depending on its existence. Calculated by the + // create pre-hook and used by the subsequent post-hook. + // + bool add; + + // Whether to move existing cleanups for the filesystem entry + // being moved, rather than to erase them. Calculated by the move + // pre-hook and used by the subsequent post-hook. + // + bool move; + }; + + // nullopt if the builtin doesn't support cleanups. + // + optional<cleanup> cln; + + if (cleanup_builtin (program)) + cln = cleanup (); + + builtin_callbacks bcs { + + // create + // + // Unless cleanups are suppressed, test that the filesystem entry + // doesn't exist (pre-hook) and, if that's the case, register the + // cleanup for the newly created filesystem entry (post-hook). + // + [&sp, &cln] (const path& p, bool pre) + { + // Cleanups must be supported by a filesystem entry-creating + // builtin. + // + assert (cln); + + if (cln->enabled) + { + if (pre) + cln->add = !butl::entry_exists (p); + else if (cln->add) + sp.clean ({cleanup_type::always, p}, true /* implicit */); + } + }, + + // move + // + // Validate the source and destination paths (pre-hook) and, + // unless suppressed, adjust the cleanups that are sub-paths of + // the source path (post-hook). + // + [&sp, &cln] + (const path& from, const path& to, bool force, bool pre) + { + // Cleanups must be supported by a filesystem entry-moving + // builtin. + // + assert (cln); + + if (pre) + { + const dir_path& wd (sp.wd_path); + const dir_path& rwd (sp.root.wd_path); + + auto fail = [] (const string& d) {throw runtime_error (d);}; + + if (!from.sub (rwd) && !force) + fail ("'" + from.representation () + + "' is out of working directory '" + rwd.string () + + "'"); + + auto check_wd = [&wd, fail] (const path& p) + { + if (wd.sub (path_cast<dir_path> (p))) + fail ("'" + p.string () + + "' contains test working directory '" + + wd.string () + "'"); + }; + + check_wd (from); + check_wd (to); + + // Unless cleanups are disabled, "move" the matching cleanups + // if the destination path doesn't exist and it is a sub-path + // of the working directory and just remove them otherwise. + // + if (cln->enabled) + cln->move = !butl::entry_exists (to) && to.sub (rwd); + } + else if (cln->enabled) + { + // Move or remove the matching cleanups (see above). + // + // Note that it's not enough to just change the cleanup paths. + // We also need to make sure that these cleanups happen before + // the destination directory (or any of its parents) cleanup, + // that is potentially registered. To achieve that we can just + // relocate these cleanup entries to the end of the list, + // preserving their mutual order. Remember that cleanups in + // the list are executed in the reversed order. + // + cleanups cs; + + // Remove the source path sub-path cleanups from the list, + // adjusting/caching them if required (see above). + // + for (auto i (sp.cleanups.begin ()); i != sp.cleanups.end (); ) + { + build2::test::script::cleanup& c (*i); + path& p (c.path); + + if (p.sub (from)) + { + if (cln->move) + { + // Note that we need to preserve the cleanup path + // trailing separator which indicates the removal + // method. Also note that leaf(), in particular, does + // that. + // + p = p != from + ? to / p.leaf (path_cast<dir_path> (from)) + : p.to_directory () + ? path_cast<dir_path> (to) + : to; + + cs.push_back (move (c)); + } + + i = sp.cleanups.erase (i); + } + else + ++i; + } + + // Re-insert the adjusted cleanups at the end of the list. + // + sp.cleanups.insert (sp.cleanups.end (), + make_move_iterator (cs.begin ()), + make_move_iterator (cs.end ())); + + } + }, + + // remove + // + // Validate the filesystem entry path (pre-hook). + // + [&sp] (const path& p, bool force, bool pre) + { + if (pre) + { + const dir_path& wd (sp.wd_path); + const dir_path& rwd (sp.root.wd_path); + + auto fail = [] (const string& d) {throw runtime_error (d);}; + + if (!p.sub (rwd) && !force) + fail ("'" + p.representation () + + "' is out of working directory '" + rwd.string () + + "'"); + + if (wd.sub (path_cast<dir_path> (p))) + fail ("'" + p.string () + + "' contains test working directory '" + wd.string () + + "'"); + } + }, + + // parse_option + // + [&cln] (const strings& args, size_t i) + { + // Parse --no-cleanup, if it is supported by the builtin. + // + if (cln && args[i] == "--no-cleanup") + { + cln->enabled = false; + return 1; + } + + return 0; + }, + + // sleep + // + // Deactivate the thread before going to sleep. + // + [&sp] (const duration& d) + { + // If/when required we could probably support the precise sleep + // mode (e.g., via an option). + // + sp.root.test_target.ctx.sched.sleep (d); + } + }; + try { uint8_t r; // Storage. - builtin b ( - bf (sp, r, c.arguments, move (ifd), move (ofd.out), move (efd))); + builtin b (bf (r, + c.arguments, + move (ifd), move (ofd.out), move (efd), + sp.wd_path, + bcs)); success = run_pipe (sp, nc, |