aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/test/script/script.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/test/script/script.cxx')
-rw-r--r--libbuild2/test/script/script.cxx279
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);
+ }
}
}
}