aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-10-22 11:59:20 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-11-04 09:26:34 +0200
commit31e16a7413813293e3cccb6799eaa08b7af5af4e (patch)
tree45f06abbcf44652b58c3a2ad7485f64108994abd
parent8e3a8ffa6579a51f5a9351e1b99c07d3e1fbd234 (diff)
Implement support for compound tests
-rw-r--r--build2/test/script/lexer.cxx26
-rw-r--r--build2/test/script/parser8
-rw-r--r--build2/test/script/parser.cxx146
-rw-r--r--build2/test/script/script10
-rw-r--r--build2/test/script/token4
-rw-r--r--build2/test/script/token.cxx2
-rw-r--r--doc/testscript.cli12
-rw-r--r--unit-tests/test/script/lexer/assign-line.test8
-rw-r--r--unit-tests/test/script/lexer/buildfile3
-rw-r--r--unit-tests/test/script/lexer/driver.cxx7
-rw-r--r--unit-tests/test/script/lexer/script-line.test18
-rw-r--r--unit-tests/test/script/lexer/variable-line.test18
-rw-r--r--unit-tests/test/script/parser/driver.cxx54
-rw-r--r--unit-tests/test/script/parser/scope.test107
14 files changed, 347 insertions, 76 deletions
diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx
index 5061a1d..2192f35 100644
--- a/build2/test/script/lexer.cxx
+++ b/build2/test/script/lexer.cxx
@@ -26,24 +26,24 @@ namespace build2
{
case lexer_mode::script_line:
{
- s1 = "=!|&<> $(#\t\n";
- s2 = "== ";
+ s1 = ";=!|&<> $(#\t\n";
+ s2 = " == ";
break;
}
case lexer_mode::assign_line:
{
// As script_line but with variable assignments.
//
- s1 = "=+!|&<> $(#\t\n";
- s2 = " == ";
+ s1 = ";=+!|&<> $(#\t\n";
+ s2 = " == ";
break;
}
case lexer_mode::variable_line:
{
- // Like value except we don't recognize {.
+ // Like value except we recognize ';' and don't recognize '{'.
//
- s1 = " $([]#\t\n";
- s2 = " ";
+ s1 = "; $([]#\t\n";
+ s2 = " ";
break;
}
@@ -159,6 +159,18 @@ namespace build2
}
}
+ // Line separators.
+ //
+ if (m == lexer_mode::script_line ||
+ m == lexer_mode::assign_line ||
+ m == lexer_mode::variable_line)
+ {
+ switch (c)
+ {
+ case ';': return make_token (type::semi);
+ }
+ }
+
// Command line operator/separators.
//
if (m == lexer_mode::script_line || m == lexer_mode::assign_line)
diff --git a/build2/test/script/parser b/build2/test/script/parser
index df79cf6..39ec4f8 100644
--- a/build2/test/script/parser
+++ b/build2/test/script/parser
@@ -54,16 +54,16 @@ namespace build2
void
parse_script ();
- line_type
+ pair<line_type, bool>
pre_parse_script_line (token&, token_type&);
void
parse_script_line (token&, token_type&, line_type, size_t);
- void
+ bool
parse_variable_line (token&, token_type&);
- void
+ bool
parse_test_line (token&, token_type&, size_t);
command_exit
@@ -100,7 +100,7 @@ namespace build2
// Pre-parse state.
//
group* group_;
- test* test_;
+ lines* lines_;
// Parse state.
//
diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx
index 1448e7a..510b8cc 100644
--- a/build2/test/script/parser.cxx
+++ b/build2/test/script/parser.cxx
@@ -22,6 +22,8 @@ namespace build2
{
path_ = &p;
+ pre_parse_ = true;
+
lexer l (is, *path_, lexer_mode::script_line);
lexer_ = &l;
base_parser::lexer_ = &l;
@@ -30,10 +32,9 @@ namespace build2
runner_ = nullptr;
group_ = script_;
- test_ = nullptr;
- scope_ = nullptr;
+ lines_ = nullptr;
- pre_parse_ = true;
+ scope_ = nullptr;
pre_parse_script ();
}
@@ -43,6 +44,8 @@ namespace build2
{
path_ = &p;
+ pre_parse_ = false;
+
lexer_ = nullptr;
base_parser::lexer_ = nullptr;
@@ -50,10 +53,9 @@ namespace build2
runner_ = &r;
group_ = nullptr;
- test_ = nullptr;
- scope_ = &sc;
+ lines_ = nullptr;
- pre_parse_ = false;
+ scope_ = &sc;
parse_script ();
}
@@ -81,47 +83,98 @@ namespace build2
if (tt == type::eos)
{
+ // Check that we don't expect more lines.
+ //
+ // @@ Also for explicit scope close.
+ //
+ if (lines_ != nullptr)
+ fail (t) << "expected another line after semicolon";
+
group_->end_loc_ = get_location (t);
replay_stop (); // Discard replay of eos.
break;
}
const location ll (get_location (t));
- line_type lt (pre_parse_script_line (t, tt));
+ pair<line_type, bool> lt (pre_parse_script_line (t, tt));
assert (tt == type::newline);
// Stop saving and get the tokens.
//
- line l {lt, replay_data ()};
+ line l {lt.first, replay_data ()};
// Decide where it goes.
//
lines* ls (nullptr);
- switch (lt)
+
+ // If lines_ is not NULL then the previous command ended with a
+ // semicolon and we should add this one to the same place.
+ //
+ if (lines_ != nullptr)
+ ls = lines_;
+ else
{
- case line_type::variable:
+ switch (lt.first)
{
- ls = &group_->setup_;
- break;
- }
- case line_type::test:
- {
- // Create implicit test scope. Use line number as the scope id.
- //
- group_->scopes.push_back (
- unique_ptr<test> (
- (test_ = new test (to_string (ll.line), *group_))));
+ case line_type::variable:
+ {
+ // If there is a semicolon after the variable then we assume
+ // it is part of a test (there is no reason to use semicolons
+ // after variables in the group scope).
+ //
+ if (!lt.second)
+ {
+ // If we don't have any nested scopes or teardown commands,
+ // then we assume this is a setup, otherwise -- teardown.
+ //
+ ls = group_->scopes.empty () && group_->tdown_.empty ()
+ ? &group_->setup_
+ : &group_->tdown_;
+
+ break;
+ }
- test_->start_loc_ = ll;
- test_->end_loc_ = get_location (t);
+ // Fall through.
+ }
+ case line_type::test:
+ {
+ // First check that we don't have any teardown commands yet.
+ // This will detect things like variable assignments between
+ // tests.
+ //
+ // @@ Can the teardown line be from a different file?
+ //
+ if (!group_->tdown_.empty ())
+ {
+ location tl (
+ get_location (
+ group_->tdown_.back ().tokens.front ().token));
+
+ fail (ll) << "test after teardown" <<
+ info (tl) << "last teardown line appears here";
+ }
- ls = &test_->tests_;
+ // Create implicit test scope. Use line number as the scope id.
+ //
+ unique_ptr<test> p (new test (to_string (ll.line), *group_));
+
+ p->start_loc_ = ll;
+ p->end_loc_ = get_location (t);
- test_ = nullptr;
+ ls = &p->tests_;
+
+ group_->scopes.push_back (move (p));
+ break;
+ }
}
}
ls->push_back (move (l));
+
+ // If this command ended with a semicolon, then the next one should
+ // go to the same place.
+ //
+ lines_ = lt.second ? ls : nullptr;
}
}
@@ -144,6 +197,9 @@ namespace build2
//
next (t, tt);
+ // @@ The index counts variable assignment lines. This is probably
+ // not what we want.
+ //
parse_script_line (t, tt, l.type, n == 1 ? 0 : i + 1);
assert (tt == type::newline);
@@ -153,14 +209,14 @@ namespace build2
runner_->enter (*scope_, scope_->start_loc_);
- play (scope_->setup_);
-
if (test* t = dynamic_cast<test*> (scope_))
{
play (t->tests_);
}
else if (group* g = dynamic_cast<group*> (scope_))
{
+ play (g->setup_);
+
for (const unique_ptr<scope>& s: g->scopes)
{
// Hand it off to a sub-parser potentially in another thread. But
@@ -174,16 +230,16 @@ namespace build2
parser p;
p.parse (*s, *path_, *script_, *runner_);
}
+
+ play (g->tdown_);
}
else
assert (false);
- play (scope_->tdown_);
-
runner_->leave (*scope_, scope_->end_loc_);
}
- line_type parser::
+ pair<line_type, bool> parser::
pre_parse_script_line (token& t, type& tt)
{
// Decide whether this is a variable assignment or a command. It is a
@@ -202,13 +258,12 @@ namespace build2
if (p == type::assign || p == type::prepend || p == type::append)
{
- parse_variable_line (t, tt);
- return line_type::variable;
+ return make_pair (line_type::variable,
+ parse_variable_line (t, tt));
}
}
- parse_test_line (t, tt, 0);
- return line_type::test;
+ return make_pair (line_type::test, parse_test_line (t, tt, 0));
}
void parser::
@@ -234,7 +289,7 @@ namespace build2
return !s.empty ();
}
- void parser::
+ bool parser::
parse_variable_line (token& t, type& tt)
{
string name (move (t.value));
@@ -262,12 +317,17 @@ namespace build2
//
attributes_push (t, tt, true);
- value rhs (tt != type::newline && tt != type::eos
+ value rhs (tt != type::newline && tt != type::semi
? parse_names_value (t, tt, "variable value", nullptr)
: value (names ()));
+ bool semi (tt == type::semi);
+
+ if (semi)
+ next (t, tt); // Get newline.
+
if (tt != type::newline)
- fail (t) << "unexpected " << t;
+ fail (t) << "expected newline instead of " << t;
if (!pre_parse_)
{
@@ -298,9 +358,11 @@ namespace build2
scope_->assign (script_->cmd_var) = nullptr;
}
}
+
+ return semi;
}
- void parser::
+ bool parser::
parse_test_line (token& t, type& tt, size_t li)
{
command c;
@@ -506,6 +568,7 @@ namespace build2
{
case type::equal:
case type::not_equal:
+ case type::semi:
case type::newline:
{
done = true;
@@ -724,8 +787,13 @@ namespace build2
if (tt == type::equal || tt == type::not_equal)
c.exit = parse_command_exit (t, tt);
+ bool semi (tt == type::semi);
+
+ if (semi)
+ next (t, tt); // Get newline.
+
if (tt != type::newline)
- fail (t) << "unexpected " << t;
+ fail (t) << "expected newline instead of " << t;
// Parse here-document fragments in the order they were mentioned on
// the command line.
@@ -754,6 +822,8 @@ namespace build2
//
if (!pre_parse_)
runner_->run (*scope_, c, li, ll);
+
+ return semi;
}
command_exit parser::
diff --git a/build2/test/script/script b/build2/test/script/script
index ff9fa46..a7bdd7b 100644
--- a/build2/test/script/script
+++ b/build2/test/script/script
@@ -161,8 +161,6 @@ namespace build2
friend class parser;
location start_loc_;
- lines setup_;
- lines tdown_;
location end_loc_;
};
@@ -176,6 +174,14 @@ namespace build2
protected:
group (const string& id): scope (id, nullptr) {} // For root.
+
+ // Pre-parse data.
+ //
+ private:
+ friend class parser;
+
+ lines setup_;
+ lines tdown_;
};
class test: public scope
diff --git a/build2/test/script/token b/build2/test/script/token
index c39811a..da468af 100644
--- a/build2/test/script/token
+++ b/build2/test/script/token
@@ -24,7 +24,9 @@ namespace build2
{
// NOTE: remember to update token_printer()!
- pipe = base_type::value_next, // |
+ semi = base_type::value_next, // ;
+
+ pipe, // |
clean, // &
log_and, // &&
log_or, // ||
diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx
index 27e86d6..6ed0443 100644
--- a/build2/test/script/token.cxx
+++ b/build2/test/script/token.cxx
@@ -21,6 +21,8 @@ namespace build2
switch (t.type)
{
+ case token_type::semi: os << q << ';' << q; break;
+
case token_type::pipe: os << q << '|' << q; break;
case token_type::clean: os << q << '&' << q; break;
case token_type::log_and: os << q << "&&" << q; break;
diff --git a/doc/testscript.cli b/doc/testscript.cli
index 027fb64..b12ca98 100644
--- a/doc/testscript.cli
+++ b/doc/testscript.cli
@@ -713,12 +713,14 @@ variable-line: <variable> ('='|'+='|'=+') value-attributes? <value>
value-attributes: '[' <key-value-pairs> ']'
test-line:
- command command-exit?
- *here-document
+ *((variable-line|command-line) ';')
+ command-line
-command-exit: ('=='|'!=') <exit-status>
+command-line:
+ command
-command: <path>(' '+(<arg>|stdin|stdout|stderr))*
+command: <path>(' '+(<arg>|stdin|stdout|stderr))* command-exit?
+ *here-document
stdin: '0'?('<!'|\
'<' <text>|\
@@ -732,6 +734,8 @@ stderr: '2'('>!'|\
'>' <text>|\
'>>' <here-end>)
+command-exit: ('=='|'!=') <exit-status>
+
here-document:
*<text>
<here-end>
diff --git a/unit-tests/test/script/lexer/assign-line.test b/unit-tests/test/script/lexer/assign-line.test
new file mode 100644
index 0000000..ce3e8a1
--- /dev/null
+++ b/unit-tests/test/script/lexer/assign-line.test
@@ -0,0 +1,8 @@
+# Note: this mode auto-expires after each token.
+#
+test.arguments += assign-line
+
+$* <";" >>EOO # semi-only
+;
+<newline>
+EOO
diff --git a/unit-tests/test/script/lexer/buildfile b/unit-tests/test/script/lexer/buildfile
index a9f6be9..70be793 100644
--- a/unit-tests/test/script/lexer/buildfile
+++ b/unit-tests/test/script/lexer/buildfile
@@ -7,6 +7,7 @@
import libs = libbutl%lib{butl}
src = token lexer diagnostics utility variable name test/script/{token lexer}
-exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs test{variable.test}
+exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs \
+test{script-line assign-line variable-line variable}
include ../../../../build2/
diff --git a/unit-tests/test/script/lexer/driver.cxx b/unit-tests/test/script/lexer/driver.cxx
index cd7110f..e37e29d 100644
--- a/unit-tests/test/script/lexer/driver.cxx
+++ b/unit-tests/test/script/lexer/driver.cxx
@@ -42,9 +42,12 @@ namespace build2
{
cin.exceptions (istream::failbit | istream::badbit);
- // The variable mode auto-expires so we need something underneath.
+ // Some modes auto-expire so we need something underneath.
//
- bool u (m == lexer_mode::variable);
+ bool u (m == lexer_mode::assign_line ||
+ m == lexer_mode::variable_line ||
+ m == lexer_mode::variable);
+
lexer l (cin, path ("stdin"), u ? lexer_mode::script_line : m);
if (u)
l.mode (m);
diff --git a/unit-tests/test/script/lexer/script-line.test b/unit-tests/test/script/lexer/script-line.test
new file mode 100644
index 0000000..b4fe3ef
--- /dev/null
+++ b/unit-tests/test/script/lexer/script-line.test
@@ -0,0 +1,18 @@
+test.arguments += script-line
+
+$* <"cmd;" >>EOO # semi
+'cmd'
+;
+<newline>
+EOO
+
+$* <"cmd ;" >>EOO # semi-separated
+'cmd'
+;
+<newline>
+EOO
+
+$* <";" >>EOO # semi-only
+;
+<newline>
+EOO
diff --git a/unit-tests/test/script/lexer/variable-line.test b/unit-tests/test/script/lexer/variable-line.test
new file mode 100644
index 0000000..543c6f9
--- /dev/null
+++ b/unit-tests/test/script/lexer/variable-line.test
@@ -0,0 +1,18 @@
+test.arguments += variable-line
+
+$* <"cmd;" >>EOO # semi
+'cmd'
+;
+<newline>
+EOO
+
+$* <"cmd ;" >>EOO # semi-separated
+'cmd'
+;
+<newline>
+EOO
+
+$* <";" >>EOO # semi-only
+;
+<newline>
+EOO
diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx
index 4d71082..09fd6f5 100644
--- a/unit-tests/test/script/parser/driver.cxx
+++ b/unit-tests/test/script/parser/driver.cxx
@@ -25,25 +25,45 @@ namespace build2
{
namespace script
{
+ // Here we assume we are running serially.
+ //
class print_runner: public runner
{
public:
+ print_runner (bool scope): scope_ (scope) {}
+
virtual void
- enter (scope&, const location&) override {}
+ enter (scope&, const location&) override
+ {
+ if (scope_)
+ {
+ cout << ind_ << "{" << endl;
+ ind_ += " ";
+ }
+ }
virtual void
run (scope&, const command& t, size_t, const location&) override
{
- // Here we assume we are running serially.
- //
- cout << t << endl;
+ cout << ind_ << t << endl;
}
virtual void
- leave (scope&, const location&) override {}
+ leave (scope&, const location&) override
+ {
+ if (scope_)
+ {
+ ind_.resize (ind_.size () - 2);
+ cout << ind_ << "}" << endl;
+ }
+ }
+
+ private:
+ bool scope_;
+ string ind_;
};
- // Usage: argv[0] [<testscript-name>]
+ // Usage: argv[0] [-s] [<testscript-name>]
//
int
main (int argc, char* argv[])
@@ -53,9 +73,27 @@ namespace build2
init (1); // Default verbosity.
reset (strings ()); // No command line variables.
+ bool scope (false);
+ path name;
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-s")
+ scope = true;
+ else
+ {
+ name = path (move (a));
+ break;
+ }
+ }
+
+ if (name.empty ())
+ name = path ("testscript");
+
try
{
- path name (argc > 1 ? argv[1] : "testscript");
cin.exceptions (istream::failbit | istream::badbit);
// Enter mock targets. Use fixed names and paths so that we can use
@@ -83,7 +121,7 @@ namespace build2
// Parse and run.
//
script s (tt, st);
- print_runner r;
+ print_runner r (scope);
parser p;
p.pre_parse (cin, name, s);
diff --git a/unit-tests/test/script/parser/scope.test b/unit-tests/test/script/parser/scope.test
index a2c6d9f..7517022 100644
--- a/unit-tests/test/script/parser/scope.test
+++ b/unit-tests/test/script/parser/scope.test
@@ -1,15 +1,104 @@
$* testscript <'cmd $@' >"cmd 1" # id-testscript
$* foo.test <'cmd $@' >"cmd foo/1" # id
-wd = [dir_path] $build.work
-wd += test
-wd += 1
+wd = [dir_path] $~;
+wd += test;
+wd += 1;
$* testscript <'cmd $~' >"cmd $wd" # wd-testscript
-# @@ TMP wd1
+wd = [dir_path] $~;
+wd += test;
+wd += foo;
+wd += 1;
+$* foo.test <'cmd $~' >"cmd $wd" # wd
+
+$* -s <<EOI >>EOO # compound-2
+cmd1;
+cmd2
+EOI
+{
+ {
+ cmd1
+ cmd2
+ }
+}
+EOO
+
+$* -s <<EOI >>EOO # compound-3
+cmd1;
+cmd2;
+cmd3
+EOI
+{
+ {
+ cmd1
+ cmd2
+ cmd3
+ }
+}
+EOO
+
+$* -s <<EOI >>EOO # compound-var
+cmd1;
+x = abc;
+cmd2 \$x
+EOI
+{
+ {
+ cmd1
+ cmd2 abc
+ }
+}
+EOO
+
+$* -s <<EOI >>EOO # compound-var-first
+x = abc;
+cmd \$x
+EOI
+{
+ {
+ cmd abc
+ }
+}
+EOO
+
+$* -s <<EOI >>EOO # var-setup-tdown
+x = abc
+cmd \$x
+y = 123
+EOI
+{
+ {
+ cmd abc
+ }
+}
+EOO
+
+$* <<EOI 2>>EOE != 0 # test-after-tdown
+cmd1
+x = abc
+cmd2
+EOI
+testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+EOE
+
+$* <<EOI 2>>EOE != 0 # expected-line
+cmd;
+EOI
+testscript:2:1: error: expected another line after semicolon
+EOE
+
+# @@ Need newline-less support.
#
-wd1 = [dir_path] $build.work
-wd1 += test
-wd1 += foo
-wd1 += 1
-$* foo.test <'cmd $~' >"cmd $wd1" # wd
+#$* <<EOI 2>>EOE != 0 # expected-newline-cmd
+#cmd ;\
+#EOI
+#testscript:2:1: error: expected newline instead of <end of file>
+#EOE
+
+#$* <<EOI 2>>EOE != 0 # expected-newline-var
+#x =abc;\
+#EOI
+#testscript:2:1: error: expected newline instead of <end of file>
+#EOE