From f41599c8e9435f3dfec60b872c2b4ae31177efdd Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 10 Oct 2020 17:22:46 +0300 Subject: Add support for test timeouts --- libbuild2/test/script/parser+env.test.testscript | 20 +++-- libbuild2/test/script/script.cxx | 106 ++++++++++++++++++++++- libbuild2/test/script/script.hxx | 61 ++++++++++++- 3 files changed, 176 insertions(+), 11 deletions(-) (limited to 'libbuild2/test/script') diff --git a/libbuild2/test/script/parser+env.test.testscript b/libbuild2/test/script/parser+env.test.testscript index b1e864c..b6fb305 100644 --- a/libbuild2/test/script/parser+env.test.testscript +++ b/libbuild2/test/script/parser+env.test.testscript @@ -48,10 +48,10 @@ : set : { - $* <'env a=b -- cmd' >'env a=b -- cmd' : var - $* <'env -u a b=c -- cmd' >'env -u a - b=c -- cmd' : opt-var - $* <'env a="b c" -- cmd' >"env a='b c' -- cmd" : quote - $* <'env "a b"=c -- cmd' >"env 'a b=c' -- cmd" : quote-name + $* <'env a=b -- cmd' >'env a=b -- cmd' : var + $* <'env -u a b=c -- cmd' >'env -u a b=c -- cmd' : opt-var + $* <'env a="b c" -- cmd' >"env a='b c' -- cmd" : quote + $* <'env "a b"=c -- cmd' >"env 'a b=c' -- cmd" : quote-name : double-quote : @@ -66,9 +66,19 @@ EOE } +: timeout +: +{ + $* <'env -t 5 -- cmd' >'env -t 5 -- cmd' : short-opt + $* <'env --timeout 5 -- cmd' >'env -t 5 -- cmd' : long-opt + $* <'env --timeout=5 -- cmd' >'env -t 5 -- cmd' : long-opt-eq + $* <'env -u a -t 5 -- cmd' >'env -t 5 -u a -- cmd' : mult-opt + $* <'env -t 5 a=b -- cmd' >'env -t 5 a=b -- cmd' : args +} + : non-first : -$* <'cmd1 && env -u a b=c -- cmd2' >'cmd1 && env -u a - b=c -- cmd2' +$* <'cmd1 && env -u a b=c -- cmd2' >'cmd1 && env -u a b=c -- cmd2' : no-cmd : diff --git a/libbuild2/test/script/script.cxx b/libbuild2/test/script/script.cxx index 34d4723..3f615ee 100644 --- a/libbuild2/test/script/script.cxx +++ b/libbuild2/test/script/script.cxx @@ -8,6 +8,10 @@ #include #include +#include + +#include // operation_deadline(), + // test_timeout() #include using namespace std; @@ -18,6 +22,9 @@ namespace build2 { namespace script { + using build2::script::to_deadline; + using build2::script::to_timeout; + // scope_base // scope_base:: @@ -188,11 +195,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_path // for root is just the id which is empty if st is 'testscript'). @@ -282,6 +292,14 @@ namespace build2 reset_special (); } + optional script:: + effective_deadline () + { + return earlier (operation_deadline, group_deadline); + } + + // scope + // lookup scope:: lookup (const variable& var) const { @@ -409,6 +427,86 @@ namespace build2 // 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"); + + 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, l), + success); + + if (p != t.size () - 1) + test_timeout = + to_timeout (parse_timeout (string (t, p + 1), tt, l), success); + } + else + group_deadline = to_deadline (parse_deadline (t, gt, l), success); + } + + optional 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", l), + success); + } + + optional 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 t (root.test_timeout); // config.test.timeout + for (const scope* p (parent); p != nullptr; p = p->parent) + { + const group* g (dynamic_cast (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); + } } } } diff --git a/libbuild2/test/script/script.hxx b/libbuild2/test/script/script.hxx index 6356501..2789cab 100644 --- a/libbuild2/test/script/script.hxx +++ b/libbuild2/test/script/script.hxx @@ -28,6 +28,8 @@ namespace build2 using build2::script::redirect_type; using build2::script::line_type; using build2::script::command_expr; + using build2::script::deadline; + using build2::script::timeout; class parser; // Required by VC for 'friend class parser' declaration. @@ -168,10 +170,29 @@ namespace build2 class group: public scope { public: - vector> scopes; + group (const string& id, group& p): scope (id, &p, p.root) {} public: - group (const string& id, group& p): scope (id, &p, p.root) {} + vector> scopes; + + // The test group execution deadline and the individual test timeout. + // + optional group_deadline; + optional test_timeout; + + // Parse the argument having the '[]/[]' + // form, where the values are expressed in seconds and either of them + // (but not both) can be omitted, and set the group deadline and test + // timeout respectively, if specified. Reset them to nullopt on zero. + // + virtual void + set_timeout (const string&, bool success, const location&) override; + + // Return the nearest of the own deadline and the enclosing groups + // deadlines. + // + virtual optional + effective_deadline () override; protected: group (const string& id, script& r): scope (id, nullptr, r) {} @@ -207,6 +228,29 @@ namespace build2 public: test (const string& id, group& p): scope (id, &p, p.root) {} + public: + // The whole test and the remaining test fragment execution deadlines. + // + // The former is based on the minimum of the test timeouts set for the + // enclosing scopes and is calculated on the first deadline() call. + // The later is set by set_timeout() from the timeout builtin call + // during the test execution. + // + optional> test_deadline; // calculated> + optional fragment_deadline; + + // Parse the specified in seconds timeout and set the remaining test + // fragment execution deadline. Reset it to nullopt on zero. + // + virtual void + set_timeout (const string&, bool success, const location&) override; + + // Return the nearest of the test and fragment execution deadlines, + // calculating the former on the first call. + // + virtual optional + effective_deadline () override; + // Pre-parse data. // public: @@ -254,6 +298,13 @@ namespace build2 class script: public script_base, public group { public: + // The test operation deadline and the individual test timeout (see + // the config.test.timeout variable for details). + // + optional operation_deadline; + optional test_timeout; + + public: script (const target& test_target, const testscript& script_target, const dir_path& root_wd); @@ -263,6 +314,12 @@ namespace build2 script& operator= (script&&) = delete; script& operator= (const script&) = delete; + // Return the nearest of the test operation and group execution + // deadlines. + // + virtual optional + effective_deadline () override; + // Pre-parse data. // private: -- cgit v1.1