diff options
Diffstat (limited to 'libbuild2/test/script/script.cxx')
-rw-r--r-- | libbuild2/test/script/script.cxx | 279 |
1 files changed, 255 insertions, 24 deletions
diff --git a/libbuild2/test/script/script.cxx b/libbuild2/test/script/script.cxx index 34d4723..f7827f6 100644 --- a/libbuild2/test/script/script.cxx +++ b/libbuild2/test/script/script.cxx @@ -8,6 +8,10 @@ #include <libbuild2/target.hxx> #include <libbuild2/algorithm.hxx> +#include <libbuild2/script/timeout.hxx> + +#include <libbuild2/test/common.hxx> // operation_deadline(), + // test_timeout() #include <libbuild2/test/script/parser.hxx> using namespace std; @@ -18,12 +22,15 @@ namespace build2 { namespace script { + using build2::script::to_deadline; + using build2::script::to_timeout; + // scope_base // scope_base:: scope_base (script& s) : root (s), - vars (s.test_target.ctx, false /* global */) + vars (s.test_target.ctx, false /* shared */) // Note: managed. { vars.assign (root.wd_var) = dir_path (); } @@ -95,8 +102,20 @@ namespace build2 dir_path (*p->work_dir.path) /= id; } + bool scope:: + test_program (const path& p) + { + assert (!test_programs.empty ()); + + return find_if (test_programs.begin (), test_programs.end (), + [&p] (const path* tp) + { + return tp != nullptr ? *tp == p : false; + }) != test_programs.end (); + } + void scope:: - set_variable (string&& nm, + set_variable (string nm, names&& val, const string& attrs, const location& ll) @@ -149,6 +168,17 @@ namespace build2 token_type::assign, path_name ("<attributes>")); } + + if (root.test_command_var (var.name)) + reset_special (); + } + + const environment_vars& scope:: + exported_variables (environment_vars& storage) + { + return parent != nullptr + ? parent->merge_exported_variables (exported_vars, storage) + : exported_vars; } // script_base @@ -167,12 +197,12 @@ namespace build2 test_var (var_pool.insert<path> ("test")), options_var (var_pool.insert<strings> ("test.options")), arguments_var (var_pool.insert<strings> ("test.arguments")), - redirects_var (var_pool.insert<strings> ("test.redirects")), - cleanups_var (var_pool.insert<strings> ("test.cleanups")), + redirects_var (var_pool.insert<cmdline> ("test.redirects")), + cleanups_var (var_pool.insert<cmdline> ("test.cleanups")), wd_var (var_pool.insert<dir_path> ("~")), id_var (var_pool.insert<path> ("@")), - cmd_var (var_pool.insert<strings> ("*")), + cmd_var (var_pool.insert<cmdline> ("*")), cmdN_var { &var_pool.insert<path> ("0"), &var_pool.insert<string> ("1"), @@ -188,11 +218,14 @@ namespace build2 // script // script:: - script (const target& tt, - const testscript& st, - const dir_path& rwd) + script (const target& tt, const testscript& st, const dir_path& rwd) : script_base (tt, st), - group (st.name == "testscript" ? string () : st.name, *this) + group (st.name == "testscript" ? string () : st.name, *this), + operation_deadline ( + to_deadline (build2::test::operation_deadline (tt), + false /* success */)), + test_timeout (to_timeout (build2::test::test_timeout (tt), + false /* success */)) { // Set the script working dir ($~) to $out_base/test/<id> (id_path // for root is just the id which is empty if st is 'testscript'). @@ -235,7 +268,7 @@ namespace build2 v = path (n->dir); else { - // Must be a target name. + // Must be a target name. Could be from src (e.g., a script). // // @@ OUT: what if this is a @-qualified pair of names? // @@ -277,11 +310,25 @@ namespace build2 } } + // Reserve the entry for the test program specified via the test + // variable. Note that the value will be assigned by the below + // reset_special() call. + // + test_programs.push_back (nullptr); + // Set the special $*, $N variables. // reset_special (); } + optional<deadline> script:: + effective_deadline () + { + return earlier (operation_deadline, group_deadline); + } + + // scope + // lookup scope:: lookup (const variable& var) const { @@ -308,7 +355,7 @@ namespace build2 // in parallel). Plus, if there is no such variable, then we cannot // possibly find any value. // - const variable* pvar (context.var_pool.find (n)); + const variable* pvar (root.target_scope.var_pool ().find (n)); if (pvar == nullptr) return lookup_type (); @@ -360,33 +407,51 @@ namespace build2 void scope:: reset_special () { - // First assemble the $* value. + // First assemble the $* value and save the test variable value into + // the test program set. // - strings s; + cmdline s; - auto append = [&s] (const strings& v) + auto append = [&s] (const strings& vs) { - s.insert (s.end (), v.begin (), v.end ()); + for (const string& v: vs) + s.push_back (name (v)); // Simple name. }; + // If the test variable can't be looked up for any reason (is NULL, + // etc), then keep $* empty. + // if (auto l = lookup (root.test_var)) - s.push_back (cast<path> (l).representation ()); + { + const path& p (cast<path> (l)); + s.push_back (name (p.representation ())); + + test_programs[0] = &p; - if (auto l = lookup (root.options_var)) - append (cast<strings> (l)); + if (auto l = lookup (root.options_var)) + append (cast<strings> (l)); - if (auto l = lookup (root.arguments_var)) - append (cast<strings> (l)); + if (auto l = lookup (root.arguments_var)) + append (cast<strings> (l)); + } + else + test_programs[0] = nullptr; // Keep redirects/cleanups out of $N. // size_t n (s.size ()); if (auto l = lookup (root.redirects_var)) - append (cast<strings> (l)); + { + const auto& v (cast<cmdline> (l)); + s.insert (s.end (), v.begin (), v.end ()); + } if (auto l = lookup (root.cleanups_var)) - append (cast<strings> (l)); + { + const auto& v (cast<cmdline> (l)); + s.insert (s.end (), v.begin (), v.end ()); + } // Set the $N values if present. // @@ -397,9 +462,9 @@ namespace build2 if (i < n) { if (i == 0) - v = path (s[i]); + v = path (s[i].value); else - v = s[i]; + v = s[i].value; } else v = nullptr; // Clear any old values. @@ -407,8 +472,174 @@ namespace build2 // Set $*. // + // We need to effective-quote the $test $test.options, $test.arguments + // part of it since they will be re-lexed. See the Testscript manual + // for details on quoting semantics. In particular, we cannot escape + // the special character (|<>&) so we have to rely on quoting. We can + // use single-quoting for everything except if the value contains a + // single quote. In which case we should probably just do separately- + // quoted regions (similar to shell), for example: + // + // <''> + // + // Can be quoted as: + // + // '<'"''"'>' + // + for (size_t i (0); i != n; ++i) + { + string& v (s[i].value); + + // Check if the quoting is required for this value. + // + if (!parser::need_cmdline_relex (v)) + continue; + + // If the value doesn't contain the single-quote character, then + // single-quote it. + // + size_t p (v.find ('\'')); + + if (p == string::npos) + { + v = '\'' + v + '\''; + continue; + } + + // Otherwise quote the regions. + // + // Note that we double-quote the single-quote character sequences + // and single-quote all the other regions. + // + string r; + char q (p == 0 ? '"' : '\''); // Current region quoting mode. + + r += q; // Open the first region. + + for (char c: v) + { + // If we are in the double-quoting mode, then switch to the + // single-quoting mode if a non-single-quote character is + // encountered. + // + if (q == '"') + { + if (c != '\'') + { + r += q; // Close the double-quoted region. + q = '\''; // Set the single-quoting mode. + r += q; // Open the single-quoted region. + } + } + // + // If we are in the single-quoting mode, then switch to the + // double-quoting mode if the single-quote character is + // encountered. + // + else + { + if (c == '\'') + { + r += q; // Close the single-quoted region. + q = '"'; // Set the double-quoting mode. + r += q; // Open the double-quoted region. + } + } + + r += c; + } + + r += q; // Close the last region. + + v = move (r); + } + assign (root.cmd_var) = move (s); } + + // group + // + void group:: + set_timeout (const string& t, bool success, const location& l) + { + const char* gt (parent != nullptr + ? "test group timeout" + : "testscript timeout"); + + const char* tt ("test timeout"); + const char* pf ("timeout: "); + + size_t p (t.find ('/')); + if (p != string::npos) + { + // Note: either of the timeouts can be omitted but not both. + // + if (t.size () == 1) + fail (l) << "invalid timeout '" << t << "'"; + + if (p != 0) + group_deadline = + to_deadline (parse_deadline (string (t, 0, p), gt, pf, l), + success); + + if (p != t.size () - 1) + test_timeout = + to_timeout (parse_timeout (string (t, p + 1), tt, pf, l), + success); + } + else + group_deadline = to_deadline (parse_deadline (t, gt, pf, l), + success); + } + + optional<deadline> group:: + effective_deadline () + { + return parent != nullptr + ? earlier (parent->effective_deadline (), group_deadline) + : group_deadline; + } + + // test + // + void test:: + set_timeout (const string& t, bool success, const location& l) + { + fragment_deadline = + to_deadline ( + parse_deadline (t, "test fragment timeout", "timeout: ", l), + success); + } + + optional<deadline> test:: + effective_deadline () + { + if (!test_deadline) + { + assert (parent != nullptr); // Test is always inside a group scope. + + test_deadline = parent->effective_deadline (); + + // Calculate the minimum timeout and factor it into the resulting + // deadline. + // + optional<timeout> t (root.test_timeout); // config.test.timeout + for (const scope* p (parent); p != nullptr; p = p->parent) + { + const group* g (dynamic_cast<const group*> (p)); + assert (g != nullptr); + + t = earlier (t, g->test_timeout); + } + + if (t) + test_deadline = + earlier (*test_deadline, + deadline (system_clock::now () + t->value, t->success)); + } + + return earlier (*test_deadline, fragment_deadline); + } } } } |