From ef7cb7ea3e6fcb21a4fcf38602b3f43f03232ace Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 1 Dec 2016 17:08:31 +0200 Subject: Implement testscript variable-if Now a variable-only if is treated the same as a single variable when deciding whether it is part of a test or setup/teardown. --- build2/test/script/parser.cxx | 188 +++++++++++++++---------- build2/test/script/script | 4 +- doc/testscript.cli | 22 ++- tests/common.test | 2 +- tests/function/path/testscript | 2 +- tests/test/common.test | 2 +- tests/test/script/runner/status.test | 2 +- unit-tests/test/script/parser/description.test | 17 +++ 8 files changed, 159 insertions(+), 80 deletions(-) diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 6ee1429..6054da9 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -111,10 +111,10 @@ namespace build2 (t = dynamic_cast (sc.back ().get ())) != nullptr && find_if ( su.begin (), su.end (), - [] (const line& l) - { + [] (const line& l) { return l.type != line_type::var; }) == su.end () && + td.empty () && !t->desc && !t->if_cond_) @@ -491,15 +491,54 @@ namespace build2 // Stop saving and get the tokens. // - line l {lt, replay_data ()}; + lines ls_data; - // Decide where it goes. - // - lines tests; if (ls == nullptr) + ls = &ls_data; + + ls->push_back (line {lt, replay_data ()}); + + if (lt == line_type::cmd_if || lt == line_type::cmd_ifn) + { + semi = pre_parse_if_else (t, tt, d, *ls); + + // If this turned out to be scope-if, then ls is empty, semi is + // false, and none of the below logic applies. + // + if (ls->empty ()) + return semi; + } + + // Unless we were told where to put it, decide where it actually goes. + // + if (ls == &ls_data) { + // First pre-check variable and variable-if: by themselves (i.e., + // without a trailing semicolon) they are treated as either setup or + // teardown without plus/minus. Also handle illegal line types. + // switch (lt) { + case line_type::cmd_elif: + case line_type::cmd_elifn: + case line_type::cmd_else: + case line_type::cmd_end: + { + fail (ll) << lt << " without preceding 'if'"; + } + case line_type::cmd_if: + case line_type::cmd_ifn: + { + // See if this is a variable-only command-if. + // + if (find_if (ls_data.begin (), ls_data.end (), + [] (const line& l) { + return l.type == line_type::cmd; + }) != ls_data.end ()) + break; + + // Fall through. + } case line_type::var: { // If there is a semicolon after the variable then we assume @@ -510,7 +549,13 @@ namespace build2 if (!semi) { if (d) - fail (ll) << "description before setup/teardown variable"; + { + if (lt == line_type::var) + fail (ll) << "description before setup/teardown variable"; + else + fail (ll) << "description before/after setup/teardown " + << "variable-if"; + } // If we don't have any nested scopes or teardown commands, // then we assume this is a setup, otherwise -- teardown. @@ -518,81 +563,75 @@ namespace build2 ls = group_->scopes.empty () && group_->tdown_.empty () ? &group_->setup_ : &group_->tdown_; - - break; } - - // Fall through. + break; } - case line_type::cmd: - case line_type::cmd_if: - case line_type::cmd_ifn: + default: + break; + } + + // If pre-check didn't change the destination, then it's a test. + // + if (ls == &ls_data) + { + switch (st) { - switch (st) + // Setup. + // + case type::plus: { - // Setup. - // - case type::plus: - { - if (d) - fail (ll) << "description before setup command"; + if (d) + fail (ll) << "description before setup command"; - if (!group_->scopes.empty ()) - fail (ll) << "setup command after tests"; + if (!group_->scopes.empty ()) + fail (ll) << "setup command after tests"; - if (!group_->tdown_.empty ()) - fail (ll) << "setup command after teardown"; + if (!group_->tdown_.empty ()) + fail (ll) << "setup command after teardown"; - ls = &group_->setup_; - break; - } - // Teardown. - // - case type::minus: - { - if (d) - fail (ll) << "description before teardown command"; + ls = &group_->setup_; + break; + } - ls = &group_->tdown_; - break; - } - // Test command or variable. + // Teardown. + // + case type::minus: + { + if (d) + fail (ll) << "description before teardown command"; + + ls = &group_->tdown_; + break; + } + + // Test command or variable. + // + default: + { + // First check that we don't have any teardown commands yet. + // This will detect things like variable assignments between + // tests. // - default: + if (!group_->tdown_.empty ()) { - // First check that we don't have any teardown commands yet. - // This will detect things like variable assignments between - // tests. - // - if (!group_->tdown_.empty ()) - { - location tl ( - group_->tdown_.back ().tokens.front ().location ()); - - fail (ll) << "test after teardown" << - info (tl) << "last teardown line appears here"; - } + location tl ( + group_->tdown_.back ().tokens.front ().location ()); - ls = &tests; - break; + fail (ll) << "test after teardown" << + info (tl) << "last teardown line appears here"; } + break; } - break; - } - case line_type::cmd_elif: - case line_type::cmd_elifn: - case line_type::cmd_else: - case line_type::cmd_end: - { - fail (ll) << lt << " without preceding 'if'"; } } - } - - ls->push_back (move (l)); - if (lt == line_type::cmd_if || lt == line_type::cmd_ifn) - semi = pre_parse_if_else (t, tt, d, *ls); + // If the destination changed, then move the data over. + // + if (ls != &ls_data) + ls->insert (ls->end (), + make_move_iterator (ls_data.begin ()), + make_move_iterator (ls_data.end ())); + } // If this command ended with a semicolon, then the next one should // go to the same place. @@ -620,10 +659,9 @@ namespace build2 } } - // Create implicit test scope unless someone (e.g., scope if-else) - // used them for something else. + // If this is a test then create implicit test scope. // - if (ls == &tests && !tests.empty ()) + if (ls == &ls_data) { // If there is no user-supplied id, use the line number (prefixed // with include id) as the scope id. @@ -638,7 +676,7 @@ namespace build2 p->desc = move (d); p->start_loc_ = ll; - p->tests_ = move (tests); + p->tests_ = move (ls_data); p->end_loc_ = get_location (t); group_->scopes.push_back (move (p)); @@ -829,10 +867,14 @@ namespace build2 // if (lt == line_type::cmd_end) { - if (d && td) - fail (ll) << "both leading and trailing descriptions"; + if (td) + { + if (d) + fail (ll) << "both leading and trailing descriptions"; + + d = move (td); + } - d = move (td); return semi; } diff --git a/build2/test/script/script b/build2/test/script/script index 241f148..1be33bb 100644 --- a/build2/test/script/script +++ b/build2/test/script/script @@ -50,7 +50,9 @@ namespace build2 replay_tokens tokens; }; - using lines = vector; + // Most of the time we will have just one line (test command). + // + using lines = small_vector; // Parse object model. // diff --git a/doc/testscript.cli b/doc/testscript.cli index c09e69c..eaccd0a 100644 --- a/doc/testscript.cli +++ b/doc/testscript.cli @@ -740,10 +740,10 @@ description: +(':' ) setup: - variable-line|setup-line + variable-like|setup-line tdown: - variable-line|tdown-line + variable-like|tdown-line setup-line: '+' command-like tdown-line: '-' command-like @@ -752,6 +752,24 @@ test: ?description +(variable-line|command-like) +variable-like: + variable-line|variable-if + +variable-if: ('if'|'if!') command-line + variable-if-body + *variable-elif + ?variable-else + 'end' + +variable-elif: ('elif'|'elif!') command-line + variable-if-body + +variable-else: 'else' + variable-if-body + +variable-if-body: + *variable-like + variable-line: ('='|'+='|'=+') value-attributes? value-attributes: '[' ']' diff --git a/tests/common.test b/tests/common.test index 4d4520f..a35d0f8 100644 --- a/tests/common.test +++ b/tests/common.test @@ -16,6 +16,6 @@ test.options += -q --buildfile - # By default just load the buildfile. # -+if ($empty($test.arguments)) +if ($empty($test.arguments)) test.arguments = noop end diff --git a/tests/function/path/testscript b/tests/function/path/testscript index e1f0a36..066a6c8 100644 --- a/tests/function/path/testscript +++ b/tests/function/path/testscript @@ -4,7 +4,7 @@ .include ../../common.test -+if ($cxx.target.class != windows) # @@ TMP ternarry +if ($cxx.target.class != windows) # @@ TMP ternarry s = '/' else s = '\' diff --git a/tests/test/common.test b/tests/test/common.test index 9477359..386b16c 100644 --- a/tests/test/common.test +++ b/tests/test/common.test @@ -19,6 +19,6 @@ test.options += -q --buildfile - # By default perform test. # -+if ($empty($test.arguments)) +if ($empty($test.arguments)) test.arguments = test end diff --git a/tests/test/script/runner/status.test b/tests/test/script/runner/status.test index cf4ce1d..f743518 100644 --- a/tests/test/script/runner/status.test +++ b/tests/test/script/runner/status.test @@ -6,7 +6,7 @@ b += --no-column -+if ($cxx.target.class == "windows") +if ($cxx.target.class == "windows") ext = ".exe" end diff --git a/unit-tests/test/script/parser/description.test b/unit-tests/test/script/parser/description.test index b4ab435..16ba287 100644 --- a/unit-tests/test/script/parser/description.test +++ b/unit-tests/test/script/parser/description.test @@ -222,6 +222,23 @@ EOI testscript:2:1: error: description before setup/teardown variable EOE +$* <>EOE != 0 # illegal-var-if +: foo +if true + x = y +end +EOI +testscript:2:1: error: description before/after setup/teardown variable-if +EOE + +$* <>EOE != 0 # illegal-var-if-after +if true + x = y +end : foo +EOI +testscript:1:1: error: description before/after setup/teardown variable-if +EOE + $* <>EOE != 0 # illegal-test cmd1; : foo -- cgit v1.1