aboutsummaryrefslogtreecommitdiff
path: root/build2/test
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-03-06 23:06:30 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-03-08 16:42:31 +0300
commit77410b0cdde47219d6c6a36533fcb9354f17c3dd (patch)
tree703c39f3bc81792fabaf81769035f01a08cf6a2f /build2/test
parent8ca10194e206a181797ffb7a73dd2deee12ac753 (diff)
Use new setup for unit tests
Diffstat (limited to 'build2/test')
-rw-r--r--build2/test/script/lexer+command-expansion.test.testscript248
-rw-r--r--build2/test/script/lexer+command-line.test.testscript208
-rw-r--r--build2/test/script/lexer+description-line.test.testscript33
-rw-r--r--build2/test/script/lexer+first-token.test.testscript97
-rw-r--r--build2/test/script/lexer+second-token.test.testscript68
-rw-r--r--build2/test/script/lexer+variable-line.test.testscript28
-rw-r--r--build2/test/script/lexer+variable.test.testscript70
-rw-r--r--build2/test/script/lexer.test.cxx85
-rw-r--r--build2/test/script/parser+cleanup.test.testscript58
-rw-r--r--build2/test/script/parser+command-if.test.testscript548
-rw-r--r--build2/test/script/parser+command-re-parse.test.testscript12
-rw-r--r--build2/test/script/parser+description.test.testscript486
-rw-r--r--build2/test/script/parser+directive.test.testscript74
-rw-r--r--build2/test/script/parser+exit.test.testscript27
-rw-r--r--build2/test/script/parser+expansion.test.testscript36
-rw-r--r--build2/test/script/parser+here-document.test.testscript213
-rw-r--r--build2/test/script/parser+here-string.test.testscript19
-rw-r--r--build2/test/script/parser+include.test.testscript104
-rw-r--r--build2/test/script/parser+pipe-expr.test.testscript133
-rw-r--r--build2/test/script/parser+pre-parse.test.testscript23
-rw-r--r--build2/test/script/parser+redirect.test.testscript356
-rw-r--r--build2/test/script/parser+regex.test.testscript223
-rw-r--r--build2/test/script/parser+scope-if.test.testscript554
-rw-r--r--build2/test/script/parser+scope.test.testscript280
-rw-r--r--build2/test/script/parser+setup-teardown.test.testscript151
-rw-r--r--build2/test/script/parser.test.cxx242
-rw-r--r--build2/test/script/regex.test.cxx301
27 files changed, 4677 insertions, 0 deletions
diff --git a/build2/test/script/lexer+command-expansion.test.testscript b/build2/test/script/lexer+command-expansion.test.testscript
new file mode 100644
index 0000000..03e3366
--- /dev/null
+++ b/build2/test/script/lexer+command-expansion.test.testscript
@@ -0,0 +1,248 @@
+# file : build2/test/script/lexer+command-expansion.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = command-expansion
+
+: pass-redirect
+:
+{
+ : in
+ :
+ $* <:"0<|" >>EOO
+ '0'
+ <|
+ EOO
+
+ : arg-in
+ :
+ $* <:"0 <|" >>EOO
+ '0 '
+ <|
+ EOO
+
+ : out
+ :
+ $* <:"1>|" >>EOO
+ '1'
+ >|
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >|" >>EOO
+ '1 '
+ >|
+ EOO
+}
+
+: null-redirect
+:
+{
+ : in
+ :
+ $* <:"0<-" >>EOO
+ '0'
+ <-
+ EOO
+
+ : arg-in
+ :
+ $* <:"0 <-" >>EOO
+ '0 '
+ <-
+ EOO
+
+ : out
+ :
+ $* <:"1>-" >>EOO
+ '1'
+ >-
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >-" >>EOO
+ '1 '
+ >-
+ EOO
+}
+
+: trace-redirect
+:
+{
+ : out
+ :
+ $* <:"1>!" >>EOO
+ '1'
+ >!
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >!" >>EOO
+ '1 '
+ >!
+ EOO
+}
+
+: merge-redirect
+:
+{
+ : out
+ :
+ $* <:"1>&2" >>EOO
+ '1'
+ >&
+ '2'
+ EOO
+
+ : arg-out
+ :
+ $* <:"1 >&2" >>EOO
+ '1 '
+ >&
+ '2'
+ EOO
+}
+
+: str-redirect
+:
+{
+ : in
+ :
+ {
+ : newline
+ :
+ $* <:"0<a b" >>EOO
+ '0'
+ <
+ 'a b'
+ EOO
+
+ : no-newline
+ :
+ $* <:"0<:a b" >>EOO
+ '0'
+ <:
+ 'a b'
+ EOO
+ }
+
+ : out
+ :
+ {
+ : newline
+ :
+ $* <:"1>a b" >>EOO
+ '1'
+ >
+ 'a b'
+ EOO
+
+ : no-newline
+ :
+ $* <:"1>:a b" >>EOO
+ '1'
+ >:
+ 'a b'
+ EOO
+ }
+}
+
+: doc-redirect
+:
+{
+ : in
+ :
+ {
+ : newline
+ :
+ $* <:"0<<E O I" >>EOO
+ '0'
+ <<
+ 'E O I'
+ EOO
+
+ : no-newline
+ :
+ $* <:"0<<:E O I" >>EOO
+ '0'
+ <<:
+ 'E O I'
+ EOO
+ }
+
+ : out
+ :
+ {
+ : newline
+ :
+ $* <:"1>>E O O" >>EOO
+ '1'
+ >>
+ 'E O O'
+ EOO
+
+ : no-newline
+ :
+ $* <:"1>>:E O O" >>EOO
+ '1'
+ >>:
+ 'E O O'
+ EOO
+ }
+}
+
+: file-redirect
+:
+{
+ : in
+ :
+ $* <:"0<<<a b" >>EOO
+ '0'
+ <<<
+ 'a b'
+ EOO
+
+ : out
+ :
+ $* <:"1>=a b" >>EOO
+ '1'
+ >=
+ 'a b'
+ EOO
+
+ : out-app
+ :
+ $* <:"1>+a b" >>EOO
+ '1'
+ >+
+ 'a b'
+ EOO
+}
+
+: cleanup
+:
+{
+ : always
+ :
+ $* <:"&file" >>EOO
+ &
+ 'file'
+ EOO
+
+ : maybe
+ :
+ $* <:"&?file" >>EOO
+ &?
+ 'file'
+ EOO
+
+ : never
+ :
+ $* <:"&!file" >>EOO
+ &!
+ 'file'
+ EOO
+}
diff --git a/build2/test/script/lexer+command-line.test.testscript b/build2/test/script/lexer+command-line.test.testscript
new file mode 100644
index 0000000..65be837
--- /dev/null
+++ b/build2/test/script/lexer+command-line.test.testscript
@@ -0,0 +1,208 @@
+# file : build2/test/script/lexer+command-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = command-line
+
+: semi
+{
+ : immediate
+ :
+ $* <"cmd;" >>EOO
+ 'cmd'
+ ;
+ <newline>
+ EOO
+
+ : separated
+ :
+ $* <"cmd ;" >>EOO
+ 'cmd'
+ ;
+ <newline>
+ EOO
+
+ : only
+ :
+ $* <";" >>EOO
+ ;
+ <newline>
+ EOO
+}
+
+: colon
+:
+{
+ : immediate
+ :
+ $* <"cmd: dsc" >>EOO
+ 'cmd'
+ :
+ 'dsc'
+ <newline>
+ EOO
+
+ : separated
+ :
+ $* <"cmd :dsc" >>EOO
+ 'cmd'
+ :
+ 'dsc'
+ <newline>
+ EOO
+
+ : only
+ :
+ $* <":" >>EOO
+ :
+ <newline>
+ EOO
+}
+
+: redirect
+:
+{
+ : pass
+ :
+ $* <"cmd <| 1>|" >>EOO
+ 'cmd'
+ <|
+ '1'
+ >|
+ <newline>
+ EOO
+
+ : null
+ :
+ $* <"cmd <- 1>-" >>EOO
+ 'cmd'
+ <-
+ '1'
+ >-
+ <newline>
+ EOO
+
+ : trace
+ :
+ $* <"cmd 1>!" >>EOO
+ 'cmd'
+ '1'
+ >!
+ <newline>
+ EOO
+
+ : merge
+ :
+ $* <"cmd 1>&2" >>EOO
+ 'cmd'
+ '1'
+ >&
+ '2'
+ <newline>
+ EOO
+
+ : str
+ :
+ $* <"cmd <a 1>b" >>EOO
+ 'cmd'
+ <
+ 'a'
+ '1'
+ >
+ 'b'
+ <newline>
+ EOO
+
+ : str-nn
+ :
+ $* <"cmd <:a 1>:b" >>EOO
+ 'cmd'
+ <:
+ 'a'
+ '1'
+ >:
+ 'b'
+ <newline>
+ EOO
+
+ : doc
+ :
+ $* <"cmd <<EOI 1>>EOO" >>EOO
+ 'cmd'
+ <<
+ 'EOI'
+ '1'
+ >>
+ 'EOO'
+ <newline>
+ EOO
+
+ : doc-nn
+ :
+ $* <"cmd <<:EOI 1>>:EOO" >>EOO
+ 'cmd'
+ <<:
+ 'EOI'
+ '1'
+ >>:
+ 'EOO'
+ <newline>
+ EOO
+
+ : file-cmp
+ :
+ $* <"cmd <<<in >>>out 2>>>err" >>EOO
+ 'cmd'
+ <<<
+ 'in'
+ >>>
+ 'out'
+ '2'
+ >>>
+ 'err'
+ <newline>
+ EOO
+
+ : file-write
+ :
+ $* <"cmd >=out 2>+err" >>EOO
+ 'cmd'
+ >=
+ 'out'
+ '2'
+ >+
+ 'err'
+ <newline>
+ EOO
+}
+
+: cleanup
+:
+{
+ : always
+ :
+ $* <"cmd &file" >>EOO
+ 'cmd'
+ &
+ 'file'
+ <newline>
+ EOO
+
+ : maybe
+ :
+ $* <"cmd &?file" >>EOO
+ 'cmd'
+ &?
+ 'file'
+ <newline>
+ EOO
+
+ : never
+ :
+ $* <"cmd &!file" >>EOO
+ 'cmd'
+ &!
+ 'file'
+ <newline>
+ EOO
+}
diff --git a/build2/test/script/lexer+description-line.test.testscript b/build2/test/script/lexer+description-line.test.testscript
new file mode 100644
index 0000000..2d87d24
--- /dev/null
+++ b/build2/test/script/lexer+description-line.test.testscript
@@ -0,0 +1,33 @@
+# file : build2/test/script/lexer+description-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = description-line
+
+: full
+:
+$* <" foo bar " >>EOO
+' foo bar '
+<newline>
+EOO
+
+: space
+:
+$* <" " >>EOO
+' '
+<newline>
+EOO
+
+: empty
+:
+$* <"" >>EOO
+<newline>
+EOO
+
+: eof
+:
+$* <:"foo" >>EOO 2>>EOE != 0
+'foo'
+EOO
+stdin:1:4: error: expected newline at the end of description line
+EOE
diff --git a/build2/test/script/lexer+first-token.test.testscript b/build2/test/script/lexer+first-token.test.testscript
new file mode 100644
index 0000000..f20f261
--- /dev/null
+++ b/build2/test/script/lexer+first-token.test.testscript
@@ -0,0 +1,97 @@
+# file : build2/test/script/lexer+first-token.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Note: this mode auto-expires after each token.
+#
+test.arguments = first-token
+
+: dot
+:
+$* <"." >>EOO
+.
+<newline>
+EOO
+
+: semi
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
+
+: colon
+:
+$* <":" >>EOO
+:
+<newline>
+EOO
+
+: lcbrace
+:
+$* <"{" >>EOO
+{
+<newline>
+EOO
+
+: rcbrace
+:
+$* <"}" >>EOO
+}
+<newline>
+EOO
+
+: setup
+:
+$* <"+foo" >>EOO
++
+'foo'
+<newline>
+EOO
+
+: tdown
+:
+$* <"- foo" >>EOO
+-
+'foo'
+<newline>
+EOO
+
+: plus-leading
+:
+$* <"foo+bar" >>EOO
+'foo+bar'
+<newline>
+EOO
+
+: minus-leading
+:
+$* <"foo- x" >>EOO
+'foo-'
+'x'
+<newline>
+EOO
+
+: assign
+:
+$* <"foo=" >>EOO
+'foo'
+'='
+<newline>
+EOO
+
+: append
+:
+$* <"foo+=" >>EOO
+'foo'
+'+='
+<newline>
+EOO
+
+: prepend
+:
+$* <"foo=+" >>EOO
+'foo'
+'=+'
+<newline>
+EOO
diff --git a/build2/test/script/lexer+second-token.test.testscript b/build2/test/script/lexer+second-token.test.testscript
new file mode 100644
index 0000000..8fdee23
--- /dev/null
+++ b/build2/test/script/lexer+second-token.test.testscript
@@ -0,0 +1,68 @@
+# file : build2/test/script/lexer+second-token.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Note: this mode auto-expires after each token.
+#
+test.arguments = second-token
+
+: semi
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
+
+: colon
+:
+$* <":" >>EOO
+:
+<newline>
+EOO
+
+: assign
+:
+$* <"=foo" >>EOO
+=
+'foo'
+<newline>
+EOO
+
+: append
+:
+$* <"+= foo" >>EOO
++=
+'foo'
+<newline>
+EOO
+
+: prepend
+:
+$* <" =+ foo" >>EOO
+=+
+'foo'
+<newline>
+EOO
+
+: assign-leading
+:
+$* <"foo=bar" >>EOO
+'foo=bar'
+<newline>
+EOO
+
+: append-leading
+:
+$* <"foo+= bar" >>EOO
+'foo+='
+'bar'
+<newline>
+EOO
+
+: prepend-leading
+:
+$* <"foo =+bar" >>EOO
+'foo'
+'=+bar'
+<newline>
+EOO
diff --git a/build2/test/script/lexer+variable-line.test.testscript b/build2/test/script/lexer+variable-line.test.testscript
new file mode 100644
index 0000000..b9c558d
--- /dev/null
+++ b/build2/test/script/lexer+variable-line.test.testscript
@@ -0,0 +1,28 @@
+# file : build2/test/script/lexer+variable-line.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = variable-line
+
+: semi
+:
+$* <"cmd;" >>EOO
+'cmd'
+;
+<newline>
+EOO
+
+: semi-separated
+:
+$* <"cmd ;" >>EOO
+'cmd'
+;
+<newline>
+EOO
+
+: semi-only
+:
+$* <";" >>EOO
+;
+<newline>
+EOO
diff --git a/build2/test/script/lexer+variable.test.testscript b/build2/test/script/lexer+variable.test.testscript
new file mode 100644
index 0000000..0ec323b
--- /dev/null
+++ b/build2/test/script/lexer+variable.test.testscript
@@ -0,0 +1,70 @@
+# file : build2/test/script/lexer+variable.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Test handling custom variable names ($*, $~, $NN).
+#
+test.arguments = variable
+
+: command
+:
+{
+ : only
+ :
+ $* <"*" >>EOO
+ '*'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"*abc" >>EOO
+ '*'
+ 'abc'
+ <newline>
+ EOO
+}
+
+: working-dir
+:
+{
+ : only
+ :
+ $* <"~" >>EOO
+ '~'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"~123" >>EOO
+ '~'
+ '123'
+ <newline>
+ EOO
+}
+
+: arg
+:
+{
+ : only
+ :
+ $* <"0" >>EOO
+ '0'
+ <newline>
+ EOO
+
+ : followed
+ :
+ $* <"1abc" >>EOO
+ '1'
+ 'abc'
+ <newline>
+ EOO
+
+ : multi-digit
+ :
+ $* <"10" 2>>EOE != 0
+ stdin:1:1: error: multi-digit special variable name
+ EOE
+}
diff --git a/build2/test/script/lexer.test.cxx b/build2/test/script/lexer.test.cxx
new file mode 100644
index 0000000..56418b7
--- /dev/null
+++ b/build2/test/script/lexer.test.cxx
@@ -0,0 +1,85 @@
+// file : build2/test/script/lexer.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/test/script/token.hxx>
+#include <build2/test/script/lexer.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ // Usage: argv[0] <lexer-mode>
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ lexer_mode m;
+ {
+ assert (argc == 2);
+ string s (argv[1]);
+
+ if (s == "command-line") m = lexer_mode::command_line;
+ else if (s == "first-token") m = lexer_mode::first_token;
+ else if (s == "second-token") m = lexer_mode::second_token;
+ else if (s == "variable-line") m = lexer_mode::variable_line;
+ else if (s == "command-expansion") m = lexer_mode::command_expansion;
+ else if (s == "here-line-single") m = lexer_mode::here_line_single;
+ else if (s == "here-line-double") m = lexer_mode::here_line_double;
+ else if (s == "description-line") m = lexer_mode::description_line;
+ else if (s == "variable") m = lexer_mode::variable;
+ else assert (false);
+ }
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Some modes auto-expire so we need something underneath.
+ //
+ bool u (m == lexer_mode::first_token ||
+ m == lexer_mode::second_token ||
+ m == lexer_mode::variable_line ||
+ m == lexer_mode::description_line ||
+ m == lexer_mode::variable);
+
+ lexer l (cin, path ("stdin"), u ? lexer_mode::command_line : m);
+ if (u)
+ l.mode (m);
+
+ // No use printing eos since we will either get it or loop forever.
+ //
+ for (token t (l.next ()); t.type != token_type::eos; t = l.next ())
+ {
+ // Print each token on a separate line without quoting operators.
+ //
+ t.printer (cout, t, false);
+ cout << endl;
+ }
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::test::script::main (argc, argv);
+}
diff --git a/build2/test/script/parser+cleanup.test.testscript b/build2/test/script/parser+cleanup.test.testscript
new file mode 100644
index 0000000..2c94afc
--- /dev/null
+++ b/build2/test/script/parser+cleanup.test.testscript
@@ -0,0 +1,58 @@
+# file : build2/test/script/parser+cleanup.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: always
+:
+$* <<EOI >>EOO
+cmd &file
+EOI
+cmd &file
+EOO
+
+: maybe
+:
+$* <<EOI >>EOO
+cmd &?file
+EOI
+cmd &?file
+EOO
+
+: never
+:
+$* <<EOI >>EOO
+cmd &!file
+EOI
+cmd &!file
+EOO
+
+: empty
+:
+$* <<EOI 2>>EOE != 0
+cmd &""
+EOI
+testscript:1:6: error: empty cleanup path
+EOE
+
+: missed-before
+:
+{
+ : token
+ :
+ : Path missed before command next token
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd & >file
+ EOI
+ testscript:1:7: error: missing cleanup path
+ EOE
+
+ : end
+ : Test path missed before end of command
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd &
+ EOI
+ testscript:1:6: error: missing cleanup path
+ EOE
+}
diff --git a/build2/test/script/parser+command-if.test.testscript b/build2/test/script/parser+command-if.test.testscript
new file mode 100644
index 0000000..ab6e6d5
--- /dev/null
+++ b/build2/test/script/parser+command-if.test.testscript
@@ -0,0 +1,548 @@
+# file : build2/test/script/parser+command-if.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: if
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if true foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? true foo
+ cmd1
+ cmd2
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if false foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? false foo
+ EOO
+
+ : not-true
+ :
+ $* <<EOI >>EOO
+ if! true foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? true foo
+ EOO
+
+ : not-false
+ :
+ $* <<EOI >>EOO
+ if! false foo
+ cmd1
+ cmd2
+ end
+ EOI
+ ? false foo
+ cmd1
+ cmd2
+ EOO
+
+ : without-command
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd
+ end
+ EOI
+ testscript:1:3: error: missing program
+ EOE
+
+ : after-semi
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ if true
+ cmd2
+ end
+ EOI
+ {
+ {
+ cmd1
+ ? true
+ cmd2
+ }
+ }
+ EOO
+
+ : setup
+ :
+ $* -s <<EOI >>EOO
+ +if true
+ cmd
+ end
+ EOI
+ {
+ ? true
+ +cmd
+ }
+ EOO
+
+ : tdown
+ :
+ $* -s <<EOI >>EOO
+ -if true
+ cmd
+ end
+ EOI
+ {
+ ? true
+ -cmd
+ }
+ EOO
+}
+
+: elif
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif true
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? true
+ cmd3
+ cmd4
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif false
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? false
+ EOO
+
+ : not-true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif! true
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? true
+ EOO
+
+ : not-false
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ elif! false
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ ? false
+ cmd3
+ cmd4
+ EOO
+
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ elif true
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'elif' without preceding 'if'
+ EOE
+
+ : not-without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ elif! true
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'elif!' without preceding 'if'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ cmd
+ else
+ cmd
+ elif true
+ cmd
+ end
+ EOI
+ testscript:5:1: error: 'elif' after 'else'
+ EOE
+}
+
+: else
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ cmd2
+ else
+ cmd3
+ cmd4
+ end
+ EOI
+ ? false
+ cmd3
+ cmd4
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ if true
+ cmd1
+ cmd2
+ else
+ cmd3
+ cmd4
+ end
+ EOI
+ ? true
+ cmd1
+ cmd2
+ EOO
+
+ : chain
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd
+ cmd
+ elif false
+ cmd
+ cmd
+ elif false
+ cmd
+ cmd
+ elif true
+ cmd1
+ cmd2
+ elif false
+ cmd
+ cmd
+ else
+ cmd
+ cmd
+ end
+ EOI
+ ? false
+ ? false
+ ? false
+ ? true
+ cmd1
+ cmd2
+ EOO
+
+ : command-after
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ cmd
+ else cmd
+ cmd
+ end
+ EOI
+ testscript:3:6: error: expected newline instead of 'cmd'
+ EOE
+
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ else
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'else' without preceding 'if'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ cmd
+ else
+ cmd
+ else
+ cmd
+ end
+ EOI
+ testscript:5:1: error: 'else' after 'else'
+ EOE
+}
+
+: end
+{
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ end
+ EOI
+ testscript:2:1: error: 'end' without preceding 'if'
+ EOE
+
+ : before
+ {
+ : semi
+ :
+ $* -s <<EOI >>EOO
+ if true
+ cmd1
+ end;
+ cmd2
+ EOI
+ {
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+
+ : command
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ cmd
+ end cmd
+ EOI
+ testscript:3:5: error: expected newline instead of 'cmd'
+ EOE
+
+ : colon
+ :
+ $* -s <<EOI >>EOO
+ if true
+ cmd1
+ cmd2
+ end : test
+ EOI
+ {
+ : id:test
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+ }
+}
+
+: nested
+:
+{
+ : take
+ :
+ $* <<EOI >>EOO
+ if true
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+ end
+ EOI
+ ? true
+ cmd1
+ ? false
+ ? false
+ cmd2
+ cmd3
+ EOO
+
+ : skip
+ :
+ $* <<EOI >>EOO
+ if false
+ cmd1
+ if false
+ cmd
+ elif false
+ if true
+ cmd
+ end
+ else
+ cmd2
+ end
+ cmd3
+ else
+ cmd
+ end
+ EOI
+ ? false
+ cmd
+ EOO
+}
+
+: contained
+{
+ : semi
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd;
+ cmd
+ end
+ EOI
+ testscript:2:3: error: ';' inside 'if'
+ EOE
+
+ : colon-leading
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ : foo
+ cmd
+ end
+ EOI
+ testscript:2:3: error: description inside 'if'
+ EOE
+
+ : colon-trailing
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd : foo
+ end
+ EOI
+ testscript:2:3: error: description inside 'if'
+ EOE
+
+ : eos
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ EOI
+ testscript:2:1: error: expected closing 'end'
+ EOE
+
+ : scope
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ cmd
+ {
+ }
+ end
+ EOI
+ testscript:3:3: error: expected closing 'end'
+ EOE
+
+ : setup
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ +cmd
+ end
+ EOI
+ testscript:2:3: error: setup command inside 'if'
+ EOE
+
+ : tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ -cmd
+ end
+ EOI
+ testscript:2:3: error: teardown command inside 'if'
+ EOE
+}
+
+: line-index
+:
+$* -l <<EOI >>EOO
+if false
+ cmd
+ if true
+ cmd
+ end
+ cmd
+elif false
+ cmd
+else
+ cmd
+end
+EOI
+? false # 1
+? false # 6
+cmd # 8
+EOO
+
+: var
+:
+$* <<EOI >>EOO
+if true
+ x = foo
+else
+ x = bar
+end;
+cmd $x
+EOI
+? true
+cmd foo
+EOO
+
+: leading-and-trailing-description
+:
+$* <<EOI 2>>EOE != 0
+: foo
+if true
+ cmd
+end : bar
+EOI
+testscript:4:1: error: both leading and trailing descriptions
+EOE
diff --git a/build2/test/script/parser+command-re-parse.test.testscript b/build2/test/script/parser+command-re-parse.test.testscript
new file mode 100644
index 0000000..ef030de
--- /dev/null
+++ b/build2/test/script/parser+command-re-parse.test.testscript
@@ -0,0 +1,12 @@
+# file : build2/test/script/parser+command-re-parse.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: double-quote
+:
+$* <<EOI >>EOO
+x = cmd \">-\" "'<-'"
+$x
+EOI
+cmd '>-' '<-'
+EOO
diff --git a/build2/test/script/parser+description.test.testscript b/build2/test/script/parser+description.test.testscript
new file mode 100644
index 0000000..7d840c3
--- /dev/null
+++ b/build2/test/script/parser+description.test.testscript
@@ -0,0 +1,486 @@
+# file : build2/test/script/parser+description.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: id
+:
+{
+ : lead
+ :
+ $* <<EOI >>EOO
+ : foo
+ cmd
+ EOI
+ : id:foo
+ cmd
+ EOO
+
+ : trail
+ :
+ $* <<EOI >>EOO
+ cmd : foo
+ EOI
+ : id:foo
+ cmd
+ EOO
+
+ : dup
+ : Id uniqueness
+ :
+ {
+ : test
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ cmd
+ : foo
+ cmd
+ EOI
+ testscript:3:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : group
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ cmd
+ : foo
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:3:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : derived
+ :
+ $* <<EOI 2>>EOE != 0
+ : 3
+ cmd
+ cmd
+ EOI
+ testscript:3:1: error: duplicate id 3
+ testscript:1:1: info: previously used here
+ EOE
+ }
+
+ : group
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ {
+ cmd
+ cmd
+ }
+ : foo
+ cmd
+ EOI
+ testscript:6:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : group
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ {
+ cmd
+ cmd
+ }
+ : foo
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:6:1: error: duplicate id foo
+ testscript:1:1: info: previously used here
+ EOE
+
+ : derived
+ :
+ $* <<EOI 2>>EOE != 0
+ : 3
+ cmd
+ {
+ cmd
+ cmd
+ }
+ EOI
+ testscript:3:1: error: duplicate id 3
+ testscript:1:1: info: previously used here
+ EOE
+ }
+ }
+}
+
+: summary
+{
+ : lead
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ cmd
+ EOI
+ : sm:foo bar
+ cmd
+ EOO
+
+ : trail
+ :
+ $* <<EOI >>EOO
+ cmd: foo bar
+ EOI
+ : sm:foo bar
+ cmd
+ EOO
+
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ cmd
+ EOI
+ : id:foo-bar
+ : sm:foo bar
+ cmd
+ EOO
+}
+
+: details
+{
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : id:foo-bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : summary
+ :
+ {
+ : only
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : sm:foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : assumed
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : id
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOI
+ : id:foo-bar
+ : sm:foo bar
+ :
+ : foo bar
+ : bar baz
+ cmd
+ EOO
+
+ : id-assumed
+ :
+ $* <<EOI >>EOO
+ : foo-bar
+ : bar baz
+ : baz fox
+ cmd
+ EOI
+ : foo-bar
+ : bar baz
+ : baz fox
+ cmd
+ EOO
+ }
+}
+
+: legal
+:
+: Legal places for description.
+:
+{
+ : var
+ :
+ $* <<EOI >>EOO
+ : foo bar
+ x = y;
+ cmd $x
+ EOI
+ : sm:foo bar
+ cmd y
+ EOO
+}
+
+: illegal
+:
+: Illegal places for description.
+:
+{
+ : eof
+ :
+ $* <": foo" 2>>EOE != 0
+ testscript:2:1: error: description before <end of file>
+ EOE
+
+ : rcbrace
+ :
+ $* <<EOI 2>>EOE != 0
+ {
+ cmd
+ : foo
+ }
+ EOI
+ testscript:4:1: error: description before '}'
+ EOE
+
+ : setup
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ +cmd
+ EOI
+ testscript:2:1: error: description before setup command
+ EOE
+
+ : tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ -cmd
+ EOI
+ testscript:2:1: error: description before teardown command
+ EOE
+
+ : var
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ x = y
+ EOI
+ testscript:2:1: error: description before setup/teardown variable
+ EOE
+
+ : var-if
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ if true
+ x = y
+ end
+ EOI
+ testscript:2:1: error: description before/after setup/teardown variable-if
+ EOE
+
+ : var-if-after
+ :
+ $* <<EOI 2>>EOE != 0
+ if true
+ x = y
+ end : foo
+ EOI
+ testscript:1:1: error: description before/after setup/teardown variable-if
+ EOE
+
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1;
+ : foo
+ cmd2
+ EOI
+ testscript:2:1: error: description inside test
+ EOE
+}
+
+: test-scope
+:
+: Interaction with test scope merging.
+:
+{
+ : both
+ :
+ : No merge since both have description.
+ :
+ $* -s -i <<EOI >>EOO
+ : foo
+ {
+ : bar
+ cmd
+ }
+ EOI
+ {
+ : id:foo
+ { # foo
+ : id:bar
+ { # foo/bar
+ cmd
+ }
+ }
+ }
+ EOO
+
+ : test
+ :
+ : No merge since test has description.
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ : foo-bar
+ : foo bar
+ cmd
+ }
+ EOI
+ {
+ { # 1
+ : id:foo-bar
+ : sm:foo bar
+ { # 1/foo-bar
+ cmd
+ }
+ }
+ }
+ EOO
+
+ : group
+ :
+ $* -s -i <<EOI >>EOO
+ : foo-bar
+ : foo bar
+ {
+ cmd
+ }
+ EOI
+ {
+ : id:foo-bar
+ : sm:foo bar
+ { # foo-bar
+ cmd
+ }
+ }
+ EOO
+}
+
+: blanks
+:
+$* <<EOI >>EOO
+:
+:
+: foo bar
+: bar baz
+:
+: baz fox
+:
+:
+cmd
+EOI
+: foo bar
+: bar baz
+:
+: baz fox
+cmd
+EOO
+
+: strip
+:
+$* <<EOI >>EOO
+: foo-bar
+: bar baz
+:
+: baz fox
+: fox biz
+:biz buz
+:
+cmd
+EOI
+: id:foo-bar
+: sm:bar baz
+:
+: baz fox
+: fox biz
+: biz buz
+cmd
+EOO
+
+: trail-compound
+:
+$* <<EOI >>EOO
+cmd1;
+cmd2: foo
+EOI
+: id:foo
+cmd1
+cmd2
+EOO
+
+: empty
+:
+$* <<EOI 2>>EOE != 0
+:
+:
+cmd
+EOI
+testscript:1:1: error: empty description
+EOE
+
+: trail-empty
+:
+$* <<EOI 2>>EOE != 0
+cmd:
+EOI
+testscript:1:4: error: empty description
+EOE
+
+: both
+:
+$* <<EOI 2>>EOE != 0
+: foo
+cmd : bar
+EOI
+testscript:2:1: error: both leading and trailing descriptions
+EOE
diff --git a/build2/test/script/parser+directive.test.testscript b/build2/test/script/parser+directive.test.testscript
new file mode 100644
index 0000000..addd874
--- /dev/null
+++ b/build2/test/script/parser+directive.test.testscript
@@ -0,0 +1,74 @@
+# file : build2/test/script/parser+directive.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: not-directive
+:
+$* <<EOI >>EOO
+x = x
+".include" foo.testscript
+\.include foo.testscript
+EOI
+.include foo.testscript
+.include foo.testscript
+EOO
+
+: expected-name
+:
+$* <<EOI 2>>EOE != 0
+.$
+EOI
+testscript:1:2: error: expected directive name instead of '$'
+EOE
+
+: unknown-name
+:
+$* <<EOI 2>>EOE != 0
+.bogus
+EOI
+testscript:1:2: error: unknown directive 'bogus'
+EOE
+
+: separated
+:
+touch foo.testscript;
+$* <<EOI
+. include foo.testscript
+EOI
+
+: not-separated
+:
+touch foo.testscript;
+$* <<EOI
+x = foo.testscript
+.include$x
+EOI
+
+: var-expansion
+:
+cat <<EOI >="foo-$(build.verson.project).testscript";
+cmd
+EOI
+$* <<EOI >>EOO
+.include "foo-$(build.verson.project).testscript"
+EOI
+cmd
+EOO
+
+: after-semi
+:
+$* <<EOI 2>>EOE != 0
+cmd;
+.include foo.testscript
+EOI
+testscript:2:1: error: directive after ';'
+EOE
+
+: semi-after
+:
+$* <<EOI 2>>EOE != 0
+.include foo.testscript;
+cmd
+EOI
+testscript:1:24: error: ';' after directive
+EOE
diff --git a/build2/test/script/parser+exit.test.testscript b/build2/test/script/parser+exit.test.testscript
new file mode 100644
index 0000000..014afa4
--- /dev/null
+++ b/build2/test/script/parser+exit.test.testscript
@@ -0,0 +1,27 @@
+# file : build2/test/script/parser+exit.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: eq
+:
+$* <<EOI >>EOO
+cmd == 1
+EOI
+cmd == 1
+EOO
+
+: ne
+:
+$* <<EOI >>EOO
+cmd!=1
+EOI
+cmd != 1
+EOO
+
+: end
+:
+$* <<EOI 2>>EOE != 0
+cmd != 1 <"foo"
+EOI
+testscript:1:10: error: unexpected '<' after command exit status
+EOE
diff --git a/build2/test/script/parser+expansion.test.testscript b/build2/test/script/parser+expansion.test.testscript
new file mode 100644
index 0000000..71a21b3
--- /dev/null
+++ b/build2/test/script/parser+expansion.test.testscript
@@ -0,0 +1,36 @@
+# file : build2/test/script/parser+expansion.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: quote
+:
+: Make sure everything expanded as strings.
+:
+$* <<EOI >>EOO
+x = dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd $x
+EOI
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+cmd dir/ proj% proj%name proj%proj%dir/type{name name {name}}
+EOO
+
+: unterm-quoted-seq
+:
+$* <<EOI 2>>EOE != 0
+x = "'a bc"
+cmd xy$x
+EOI
+<string>:1:8: error: unterminated single-quoted sequence
+ testscript:2:5: info: while parsing string 'xy'a bc'
+EOE
+
+: invalid-redirect
+:
+$* <<EOI 2>>EOE != 0
+x = "1>&a"
+cmd $x
+EOI
+<string>:1:4: error: stdout merge redirect file descriptor must be 2
+ testscript:2:5: info: while parsing string '1>&a'
+EOE
diff --git a/build2/test/script/parser+here-document.test.testscript b/build2/test/script/parser+here-document.test.testscript
new file mode 100644
index 0000000..5e99a26
--- /dev/null
+++ b/build2/test/script/parser+here-document.test.testscript
@@ -0,0 +1,213 @@
+# file : build2/test/script/parser+here-document.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: end-marker
+:
+{
+ : missing-newline
+ :
+ $* <'cmd <<' 2>>EOE != 0
+ testscript:1:7: error: expected here-document end marker
+ EOE
+
+ : missing-exit
+ :
+ $* <'cmd << != 0' 2>>EOE != 0
+ testscript:1:8: error: expected here-document end marker
+ EOE
+
+ : missing-empty
+ :
+ $* <'cmd <<""' 2>>EOE != 0
+ testscript:1:7: error: expected here-document end marker
+ EOE
+
+ : unseparated-expansion
+ :
+ $* <'cmd <<FOO$foo' 2>>EOE != 0
+ testscript:1:10: error: here-document end marker must be literal
+ EOE
+
+ : quoted-single-partial
+ :
+ $* <"cmd <<F'O'O" 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : quoted-double-partial
+ :
+ $* <'cmd <<"FO"O' 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : quoted-mixed
+ :
+ $* <"cmd <<\"FO\"'O'" 2>>EOE != 0
+ testscript:1:7: error: partially-quoted here-document end marker
+ EOE
+
+ : unseparated
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF!=0
+ foo
+ EOF
+ EOI
+ cmd <<EOF != 0
+ foo
+ EOF
+ EOO
+
+ : quoted-single
+ :
+ $* <<EOI >>EOO
+ cmd <<'EOF'
+ foo
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ EOF
+ EOO
+
+ : quoted-double
+ :
+ $* <<EOI >>EOO
+ cmd <<"EOF"
+ foo
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ EOF
+ EOO
+}
+
+: indent
+:
+{
+ : basic
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ foo
+ bar
+ baz
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+ bar
+ baz
+ EOF
+ EOO
+
+ : blank
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ foo
+
+
+ bar
+ EOF
+ EOI
+ cmd <<EOF
+ foo
+
+
+ bar
+ EOF
+ EOO
+
+ : non-ws-prefix
+ :
+ $* <<EOI >>EOO
+ cmd <<EOF
+ x EOF
+ EOF
+ EOI
+ cmd <<EOF
+ x EOF
+ EOF
+ EOO
+
+ : whole-token
+ : Test the case where the indentation is a whole token
+ :
+ $* <<EOI >>EOO
+ x = foo bar
+ cmd <<"EOF"
+ $x
+ EOF
+ EOI
+ cmd <<EOF
+ foo bar
+ EOF
+ EOO
+
+ : long-line
+ : Test the case where the line contains multiple tokens
+ :
+ $* <<EOI >>EOO
+ x = foo
+ cmd <<"EOF"
+ $x bar $x
+ EOF
+ EOI
+ cmd <<EOF
+ foo bar foo
+ EOF
+ EOO
+
+ : unindented
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<EOF
+ bar
+ EOF
+ EOI
+ testscript:2:1: error: unindented here-document line
+ EOE
+}
+
+: blank
+:
+$* <<EOI >>EOO
+cmd <<EOF
+
+foo
+
+bar
+
+EOF
+EOI
+cmd <<EOF
+
+foo
+
+bar
+
+EOF
+EOO
+
+: quote
+:
+: Note: they are still recognized in eval contexts.
+:
+$* <<EOI >>EOO
+cmd <<"EOF"
+'single'
+"double"
+b'o't"h"
+('single' "double")
+EOF
+EOI
+cmd <<EOF
+'single'
+"double"
+b'o't"h"
+single double
+EOF
+EOO
diff --git a/build2/test/script/parser+here-string.test.testscript b/build2/test/script/parser+here-string.test.testscript
new file mode 100644
index 0000000..16544df
--- /dev/null
+++ b/build2/test/script/parser+here-string.test.testscript
@@ -0,0 +1,19 @@
+# file : build2/test/script/parser+here-string.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: empty
+:
+$* <<EOI >>EOO
+cmd <""
+EOI
+cmd <''
+EOO
+
+: empty-nn
+:
+$* <<EOI >>EOO
+cmd <:""
+EOI
+cmd <:''
+EOO
diff --git a/build2/test/script/parser+include.test.testscript b/build2/test/script/parser+include.test.testscript
new file mode 100644
index 0000000..65be149
--- /dev/null
+++ b/build2/test/script/parser+include.test.testscript
@@ -0,0 +1,104 @@
+# file : build2/test/script/parser+include.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: none
+:
+$* <<EOI
+.include
+.include --once
+EOI
+
+: empty
+:
+touch foo.testscript;
+$* <<EOI
+.include foo.testscript
+.include --once foo.testscript
+EOI
+
+: one
+:
+cat <"cmd" >=foo.testscript;
+$* <<EOI >>EOO
+.include foo.testscript
+EOI
+cmd
+EOO
+
+: multiple
+:
+cat <"cmd foo" >=foo.testscript;
+cat <"cmd bar" >=bar.testscript;
+$* <<EOI >>EOO
+.include foo.testscript bar.testscript
+EOI
+cmd foo
+cmd bar
+EOO
+
+: once
+:
+cat <"cmd" >=foo.testscript;
+$* <<EOI >>EOO
+.include foo.testscript
+x
+.include --once foo.testscript
+.include --once bar/../foo.testscript
+y
+.include ../once/foo.testscript
+EOI
+cmd
+x
+y
+cmd
+EOO
+
+: group-id
+:
+cat <<EOI >=foo.testscript;
+{
+ x = b
+}
+EOI
+$* -s -i <<EOI >>EOO
+x = a
+.include foo.testscript
+EOI
+{
+ { # 2-foo-1
+ }
+}
+EOO
+
+: test-id
+:
+cat <<EOI >=foo.testscript;
+cmd
+EOI
+$* -s -i <<EOI >>EOO
+x = a
+.include foo.testscript
+EOI
+{
+ { # 2-foo-1
+ cmd
+ }
+}
+EOO
+
+: invalid-path
+:
+$* <<EOI 2>>EOE != 0
+.include ""
+EOI
+testscript:1:2: error: invalid testscript include path ''
+EOE
+
+: unable-open
+:
+$* <<EOI 2>>~/EOE/ != 0
+.include foo.testscript
+EOI
+/testscript:1:2: error: unable to read testscript foo.testscript: .+/
+EOE
diff --git a/build2/test/script/parser+pipe-expr.test.testscript b/build2/test/script/parser+pipe-expr.test.testscript
new file mode 100644
index 0000000..18eb660
--- /dev/null
+++ b/build2/test/script/parser+pipe-expr.test.testscript
@@ -0,0 +1,133 @@
+# file : build2/test/script/parser+pipe-expr.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: pipe
+:
+$* <<EOI >>EOO
+cmd1 | cmd2|cmd3
+EOI
+cmd1 | cmd2 | cmd3
+EOO
+
+: log
+:
+$* <<EOI >>EOO
+cmd1 || cmd2&&cmd3
+EOI
+cmd1 || cmd2 && cmd3
+EOO
+
+: pipe-log
+:
+$* <<EOI >>EOO
+cmd1 | cmd2 && cmd3 | cmd4
+EOI
+cmd1 | cmd2 && cmd3 | cmd4
+EOO
+
+: exit
+:
+$* <<EOI >>EOO
+cmd1|cmd2==1&&cmd3!=0|cmd4
+EOI
+cmd1 | cmd2 == 1 && cmd3 != 0 | cmd4
+EOO
+
+: here-doc
+:
+$* <<EOI >>EOO
+cmd1 <<EOI1 | cmd2 >>EOO2 && cmd3 <<EOI3 2>&1 | cmd4 2>>EOE4 >>EOO4
+input
+one
+EOI1
+ouput
+two
+EOO2
+input
+three
+EOI3
+error
+four
+EOE4
+output
+four
+EOO4
+EOI
+cmd1 <<EOI1 | cmd2 >>EOO2 && cmd3 <<EOI3 2>&1 | cmd4 >>EOO4 2>>EOE4
+input
+one
+EOI1
+ouput
+two
+EOO2
+input
+three
+EOI3
+output
+four
+EOO4
+error
+four
+EOE4
+EOO
+
+: leading
+:
+$* <<EOI 2>>EOE != 0
+| cmd
+EOI
+testscript:1:1: error: missing program
+EOE
+
+: trailing
+:
+$* <<EOI 2>>EOE != 0
+cmd &&
+EOI
+testscript:1:7: error: missing program
+EOE
+
+: redirected
+:
+{
+ : input
+ :
+ {
+ : first
+ :
+ $* <<EOI >>EOO
+ cmd1 <foo | cmd2
+ EOI
+ cmd1 <foo | cmd2
+ EOO
+
+ : non-first
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1 | cmd2 <foo
+ EOI
+ testscript:1:13: error: stdin is both piped and redirected
+ EOE
+ }
+
+ : output
+ :
+ {
+ : last
+ :
+ $* <<EOI >>EOO
+ cmd1 | cmd2 >foo
+ EOI
+ cmd1 | cmd2 >foo
+ EOO
+
+ : non-last
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1 >foo | cmd2
+ EOI
+ testscript:1:11: error: stdout is both redirected and piped
+ EOE
+ }
+}
diff --git a/build2/test/script/parser+pre-parse.test.testscript b/build2/test/script/parser+pre-parse.test.testscript
new file mode 100644
index 0000000..7d9eb6c
--- /dev/null
+++ b/build2/test/script/parser+pre-parse.test.testscript
@@ -0,0 +1,23 @@
+# file : build2/test/script/parser+pre-parse.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: attribute
+:
+{
+ : pair
+ :
+ $* <<EOI 2>>EOE != 0
+ x = [foo=bar]
+ EOI
+ testscript:1:5: error: unknown value attribute foo=bar
+ EOE
+
+ : pair-empty
+ :
+ $* <<EOI 2>>EOE != 0
+ x = [foo=]
+ EOI
+ testscript:1:5: error: unknown value attribute foo
+ EOE
+}
diff --git a/build2/test/script/parser+redirect.test.testscript b/build2/test/script/parser+redirect.test.testscript
new file mode 100644
index 0000000..b0b967a
--- /dev/null
+++ b/build2/test/script/parser+redirect.test.testscript
@@ -0,0 +1,356 @@
+# file : build2/test/script/parser+redirect.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# @@ Add tests for redirects other than trace, here-*, file and merge.
+# @@ Does it make sense to split into separate files - one per redirect type?
+#
+
+: trace
+:
+{
+ $* <'cmd >!' >'cmd >!' : out
+ $* <'cmd 2>!' >'cmd 2>!' : err
+}
+
+: str
+:
+{
+ : literal
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd </foo >/bar 2>/baz
+ EOI
+ cmd </foo >/bar 2>/baz
+ EOO
+ }
+
+ : regex
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd >/~%foo% 2>/~%bar%
+ EOI
+ cmd >/~%foo% 2>/~%bar%
+ EOO
+ }
+}
+
+: doc
+:
+{
+ : literal
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd <</EOI_ >/EOO_ 2>/EOE_
+ foo
+ EOI_
+ bar
+ EOO_
+ baz
+ EOE_
+ EOI
+ cmd <</EOI_ >/EOO_ 2>/EOE_
+ foo
+ EOI_
+ bar
+ EOO_
+ baz
+ EOE_
+ EOO
+
+ : sharing
+ :
+ {
+ : in-out
+ :
+ $* <<EOI >>EOO
+ cmd <<:/EOF >>:/EOF
+ foo
+ EOF
+ EOI
+ cmd <<:/EOF >>:/EOF
+ foo
+ EOF
+ EOO
+
+ : different
+ :
+ {
+ : modifiers
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<:/EOF >>:EOF
+ foo
+ EOF
+ EOI
+ testscript:1:16: error: different modifiers for shared here-document 'EOF'
+ EOE
+
+ : quoting
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd <<EOF >>"EOF"
+ foo
+ EOF
+ EOI
+ testscript:1:13: error: different quoting for shared here-document 'EOF'
+ EOE
+ }
+ }
+ }
+
+ : regex
+ :
+ {
+ : portable-path
+ :
+ $* <<EOI >>EOO
+ cmd >/~%EOF% 2>/~%EOE%
+ foo
+ EOF
+ bar
+ EOE
+ EOI
+ cmd >/~%EOF% 2>/~%EOE%
+ foo
+ EOF
+ bar
+ EOE
+ EOO
+
+ : sharing
+ :
+ {
+ : in-out
+ :
+ $* <<EOI >>EOO
+ cmd >>~/EOF/ 2>>~/EOF/
+ foo
+ EOF
+ EOI
+ cmd >>~/EOF/ 2>>~/EOF/
+ foo
+ EOF
+ EOO
+
+ : different
+ :
+ {
+ : introducers
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOF/ 2>>~%EOF%
+ foo
+ EOF
+ EOI
+ testscript:1:18: error: different introducers for shared here-document regex 'EOF'
+ EOE
+
+ : flags
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOF/ 2>>~/EOF/i
+ foo
+ EOF
+ EOI
+ testscript:1:18: error: different global flags for shared here-document regex 'EOF'
+ EOE
+ }
+ }
+ }
+}
+
+: file
+:
+{
+ : cmp
+ :
+ $* <<EOI >>EOO
+ cmd 0<<<a 1>>>b 2>>>c
+ EOI
+ cmd <<<a >>>b 2>>>c
+ EOO
+
+ : write
+ :
+ $* <<EOI >>EOO
+ cmd 1>=b 2>+c
+ EOI
+ cmd >=b 2>+c
+ EOO
+
+ : quote
+ :
+ $* <<EOI >>EOO
+ cmd 0<<<"a f" 1>="b f" 2>+"c f"
+ EOI
+ cmd <<<'a f' >='b f' 2>+'c f'
+ EOO
+
+ : in
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd <<<
+ EOI
+ testscript:1:8: error: missing stdin file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd <<<""
+ EOI
+ testscript:1:8: error: empty stdin redirect path
+ EOE
+ }
+
+ : out
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd >=
+ EOI
+ testscript:1:7: error: missing stdout file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd >=""
+ EOI
+ testscript:1:7: error: empty stdout redirect path
+ EOE
+ }
+
+ : err
+ :
+ {
+ : missed
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd 2>=
+ EOI
+ testscript:1:8: error: missing stderr file
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE !=0
+ cmd 2>=""
+ EOI
+ testscript:1:8: error: empty stderr redirect path
+ EOE
+ }
+}
+
+: merge
+{
+ : out
+ :
+ {
+ : err
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2
+ EOI
+ cmd >&2
+ EOO
+
+ : no-mutual
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2 2>&1 2>a
+ EOI
+ cmd >&2 2>a
+ EOO
+
+ : not-descriptor
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&a
+ EOI
+ testscript:1:8: error: stdout merge redirect file descriptor must be 2
+ EOE
+
+ : self
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&1
+ EOI
+ testscript:1:8: error: stdout merge redirect file descriptor must be 2
+ EOE
+
+ : missed
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&
+ EOI
+ testscript:1:8: error: missing stdout file descriptor
+ EOE
+ }
+
+ : err
+ {
+ : out
+ :
+ $* <<EOI >>EOO
+ cmd 2>&1
+ EOI
+ cmd 2>&1
+ EOO
+
+ : no-mutual
+ :
+ $* <<EOI >>EOO
+ cmd 1>&2 2>&1 >a
+ EOI
+ cmd >a 2>&1
+ EOO
+
+ : not-descriptor
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&a
+ EOI
+ testscript:1:8: error: stderr merge redirect file descriptor must be 1
+ EOE
+
+ : self
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&2
+ EOI
+ testscript:1:8: error: stderr merge redirect file descriptor must be 1
+ EOE
+
+ : missed
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 2>&
+ EOI
+ testscript:1:8: error: missing stderr file descriptor
+ EOE
+ }
+
+ : mutual
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd 1>&2 2>&1
+ EOI
+ testscript:1:14: error: stdout and stderr redirected to each other
+ EOE
+}
diff --git a/build2/test/script/parser+regex.test.testscript b/build2/test/script/parser+regex.test.testscript
new file mode 100644
index 0000000..031492e
--- /dev/null
+++ b/build2/test/script/parser+regex.test.testscript
@@ -0,0 +1,223 @@
+# file : build2/test/script/parser+regex.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: here-string
+:
+{
+ : stdout
+ :
+ {
+ : missed
+ :
+ $* <'cmd >~' 2>>EOE != 0
+ testscript:1:7: error: missing stdout here-string regex
+ EOE
+
+ : no-introducer
+ :
+ $* <'cmd >~""' 2>>EOE != 0
+ testscript:1:7: error: no introducer character in stdout regex redirect
+ EOE
+
+ : no-term-introducer
+ :
+ $* <'cmd >~/' 2>>EOE != 0
+ testscript:1:7: error: no closing introducer character in stdout regex redirect
+ EOE
+
+ : portable-path-introducer
+ :
+ $* <'cmd >/~/foo/' 2>>EOE != 0
+ testscript:1:8: error: portable path modifier and '/' introducer in stdout regex redirect
+ EOE
+
+ : empty
+ :
+ $* <'cmd >~//' 2>>EOE != 0
+ testscript:1:7: error: stdout regex redirect is empty
+ EOE
+
+ : no-flags
+ :
+ $* <'cmd >~/fo*/' >'cmd >~/fo*/'
+
+ : idot
+ :
+ $* <'cmd >~/fo*/d' >'cmd >~/fo*/d'
+
+ : icase
+ :
+ $* <'cmd >~/fo*/i' >'cmd >~/fo*/i'
+
+ : invalid-flags1
+ :
+ $* <'cmd >~/foo/z' 2>>EOE != 0
+ testscript:1:7: error: junk at the end of stdout regex redirect
+ EOE
+
+ : invalid-flags2
+ :
+ $* <'cmd >~/foo/iz' 2>>EOE != 0
+ testscript:1:7: error: junk at the end of stdout regex redirect
+ EOE
+
+ : no-newline
+ :
+ $* <'cmd >:~/fo*/' >'cmd >:~/fo*/'
+ }
+
+ : stderr
+ :
+ {
+ : missed
+ :
+ $* <'cmd 2>~' 2>>EOE != 0
+ testscript:1:8: error: missing stderr here-string regex
+ EOE
+
+ : no-introducer
+ :
+ : Note that there is no need to reproduce all the errors as for stdout.
+ : All we need is to make sure that the proper description is passed to
+ : the parse_regex() function.
+ :
+ $* <'cmd 2>~""' 2>>EOE != 0
+ testscript:1:8: error: no introducer character in stderr regex redirect
+ EOE
+ }
+
+ : modifier-last
+ :
+ $* <'cmd >~/x' 2>>EOE != 0
+ testscript:1:7: error: no closing introducer character in stdout regex redirect
+ EOE
+}
+
+: here-doc
+:
+{
+ : stdout
+ :
+ {
+ : missed
+ :
+ $* <'cmd >>~' 2>>EOE != 0
+ testscript:1:8: error: expected here-document regex end marker
+ EOE
+
+ : portable-path-introducer
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>/~/EOO/
+ foo
+ EOO
+ EOI
+ testscript:1:5: error: portable path modifier and '/' introducer in here-document regex end marker
+ EOE
+
+ : unterminated-line-char
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>~/EOO/
+ /
+ EOO
+ EOI
+ testscript:2:1: error: no syntax line characters
+ EOE
+
+ : empty
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd >>:~/EOO/
+ EOO
+ EOI
+ testscript:2:1: error: empty here-document regex
+ EOE
+
+ : no-flags
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/
+ foo
+ /?
+ /foo/
+ /foo/*
+ /foo/i
+ /foo/i*
+
+ //
+ //*
+ EOE
+ EOI
+ cmd 2>>~/EOE/
+ foo
+ /?
+ /foo/
+ /foo/*
+ /foo/i
+ /foo/i*
+
+ //
+ //*
+ EOE
+ EOO
+
+ : no-newline
+ :
+ $* <'cmd >:~/fo*/' >'cmd >:~/fo*/'
+ $* <<EOI >>EOO
+ cmd 2>>:~/EOE/
+ foo
+ EOE
+ EOI
+ cmd 2>>:~/EOE/
+ foo
+ EOE
+ EOO
+
+ : end-marker-restore
+ :
+ {
+ : idot
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/d
+ foo
+ EOE
+ EOI
+ cmd 2>>~/EOE/d
+ foo
+ EOE
+ EOO
+
+ : icase
+ :
+ $* <<EOI >>EOO
+ cmd 2>>~/EOE/i
+ foo
+ EOE
+ EOI
+ cmd 2>>~/EOE/i
+ foo
+ EOE
+ EOO
+ }
+ }
+
+ : stderr
+ :
+ {
+ : missed
+ :
+ $* <'cmd 2>>~' 2>>EOE != 0
+ testscript:1:9: error: expected here-document regex end marker
+ EOE
+ }
+
+ : modifier-last
+ :
+ $* <'cmd >>~:/FOO/' 2>>EOE != 0
+ testscript:1:8: error: expected here-document regex end marker
+ EOE
+}
diff --git a/build2/test/script/parser+scope-if.test.testscript b/build2/test/script/parser+scope-if.test.testscript
new file mode 100644
index 0000000..faae297
--- /dev/null
+++ b/build2/test/script/parser+scope-if.test.testscript
@@ -0,0 +1,554 @@
+# file : build2/test/script/parser+scope-if.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: if
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if true foo
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true foo
+ {
+ cmd
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if false foo
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false foo
+ }
+ EOO
+
+ : not-true
+ :
+ $* -s <<EOI >>EOO
+ if! true
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true
+ }
+ EOO
+
+ : not-false
+ :
+ $* -s <<EOI >>EOO
+ if! false
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ {
+ cmd
+ }
+ }
+ EOO
+
+ : eos-inside
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ {
+ EOI
+ testscript:3:1: error: expected '}' at the end of the scope
+ EOE
+
+}
+
+: elif
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? false
+ }
+ EOO
+
+ : not-false
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif! false
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ ? false
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : not-true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif! true
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ }
+ EOO
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd
+ }
+ EOI
+ testscript:9:1: error: 'elif' after 'else'
+ EOE
+}
+
+: else
+:
+{
+ : true
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd1
+ }
+ EOI
+ {
+ ? false
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : false
+ :
+ $* -s <<EOI >>EOO
+ if true
+ {
+ cmd1
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : chain
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ cmd
+ cmd
+ }
+ elif false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ cmd2
+ }
+ elif false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? false
+ ? false
+ ? true
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+
+ : scope-expected
+ :
+ $* <<EOI 2>>EOE != 0
+ if
+ {
+ cmd
+ }
+ else
+ cmd
+ EOI
+ testscript:5:1: error: expected scope after 'else'
+ EOE
+
+ : after-else
+ :
+ $* <<EOI 2>>EOE != 0
+ if false
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ testscript:9:1: error: 'else' after 'else'
+ EOE
+}
+
+: nested
+:
+{
+ : take
+ :
+ $* -s <<EOI >>EOO
+ if true
+ {
+ cmd1
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ if true
+ {
+ cmd
+ }
+ }
+ else
+ {
+ cmd2
+ }
+ cmd3
+ }
+ EOI
+ {
+ ? true
+ {
+ {
+ cmd1
+ }
+ ? false
+ ? false
+ {
+ {
+ cmd2
+ }
+ }
+ {
+ cmd3
+ }
+ }
+ }
+ EOO
+
+ : skip
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd1
+ if false
+ {
+ cmd
+ }
+ elif false
+ {
+ if true
+ {
+ cmd
+ }
+ }
+ else
+ {
+ cmd2
+ }
+ cmd3
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ {
+ {
+ cmd
+ }
+ }
+ }
+ EOO
+}
+
+: demote
+:
+{
+ : group
+ : Chain remains a group
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ cmd2
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+
+ : test
+ : Chain demoted to test
+ :
+ $* -s <<EOI >>EOO
+ if false
+ {
+ cmd
+ }
+ elif true
+ {
+ cmd1
+ }
+ else
+ {
+ cmd
+ }
+ EOI
+ {
+ ? false
+ ? true
+ {
+ cmd1
+ }
+ }
+ EOO
+}
+
+: line-index
+: Make sure command line index spans setup/if/teardown
+:
+$* -s -l <<EOI >>EOO
++setup # 1
+
+if false one # 2
+{
+ cmd
+}
+elif false two # 3
+{
+ cmd
+}
+elif true # 4
+{
+ cmd1
+}
+elif false # 5
+{
+ cmd
+}
+else
+{
+ cmd
+}
+
+if false one # 6
+{
+ cmd
+}
+elif false two # 7
+{
+ cmd
+}
+else
+{
+ cmd2
+}
+
+-tdown # 8
+EOI
+{
+ +setup # 1
+ ? false one # 2
+ ? false two # 3
+ ? true # 4
+ {
+ cmd1 # 0
+ }
+ ? false one # 6
+ ? false two # 7
+ {
+ cmd2 # 0
+ }
+ -tdown # 8
+}
+EOO
+
+: scope-comman-if
+:
+$* -s <<EOI >>EOO
+if true
+{
+ cmd
+}
+if true
+ cmd1
+ cmd2
+end
+EOI
+{
+ ? true
+ {
+ cmd
+ }
+ {
+ ? true
+ cmd1
+ cmd2
+ }
+}
+EOO
+
+: shared-id-desc
+:
+$* -s -i <<EOI >>EOO
+: test summary
+:
+if false
+{
+ cmd
+}
+else
+{
+ cmd1
+}
+EOI
+{
+ ? false
+ : sm:test summary
+ { # 3
+ cmd1
+ }
+}
+EOO
diff --git a/build2/test/script/parser+scope.test.testscript b/build2/test/script/parser+scope.test.testscript
new file mode 100644
index 0000000..9147161
--- /dev/null
+++ b/build2/test/script/parser+scope.test.testscript
@@ -0,0 +1,280 @@
+# file : build2/test/script/parser+scope.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+$* testscript <'cmd $@' >"cmd 1" : id-testscript
+$* foo.testscript <'cmd $@' >"cmd foo/1" : id
+
+: wd-testscript
+:
+$* testscript <'cmd "$~"' >~"%cmd '?.+[/\\\\]test-driver[/\\\\]1'?%"
+
+: wd
+:
+$* foo.testscript <'cmd "$~"' >~"%cmd '?.+[/\\\\]test-driver[/\\\\]foo[/\\\\]1'?%"
+
+: group
+:
+{
+ : empty
+ :
+ $* -s <<EOI
+ {
+ }
+ EOI
+
+ : empty-empty
+ :
+ $* -s <<EOI
+ {
+ {
+ }
+ }
+ EOI
+
+ : non-empty
+ :
+ $* -s <<EOI >>EOO
+ {
+ cmd1
+ cmd2
+ }
+ EOI
+ {
+ {
+ {
+ cmd1
+ }
+ {
+ cmd2
+ }
+ }
+ }
+ EOO
+}
+
+: test
+:
+{
+ : explicit
+ :
+ {
+ : one-level
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ cmd
+ }
+ EOI
+ {
+ { # 1
+ cmd
+ }
+ }
+ EOO
+
+ : nested
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ {
+ cmd
+ }
+ }
+ EOI
+ {
+ { # 1
+ cmd
+ }
+ }
+ EOO
+
+ : var
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ x = abc
+ cmd $x
+ }
+ EOI
+ {
+ { # 1
+ cmd abc
+ }
+ }
+ EOO
+
+ : setup
+ :
+ $* -s -i <<EOI >>EOO
+ {
+ x = abc
+ +setup
+ cmd $x
+ }
+ EOI
+ {
+ { # 1
+ +setup
+ { # 1/4
+ cmd abc
+ }
+ }
+ }
+ EOO
+ }
+
+ : implicit
+ {
+ : one-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1
+ EOI
+ {
+ {
+ cmd1
+ }
+ }
+ EOO
+
+ : two-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ cmd2
+ EOI
+ {
+ {
+ cmd1
+ cmd2
+ }
+ }
+ EOO
+
+ : three-cmd
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ cmd2;
+ cmd3
+ EOI
+ {
+ {
+ cmd1
+ cmd2
+ cmd3
+ }
+ }
+ EOO
+
+ : var
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ x = abc;
+ cmd2 $x
+ EOI
+ {
+ {
+ cmd1
+ cmd2 abc
+ }
+ }
+ EOO
+
+ : var-first
+ :
+ $* -s <<EOI >>EOO
+ x = abc;
+ cmd $x
+ EOI
+ {
+ {
+ cmd abc
+ }
+ }
+ EOO
+
+ : var-setup-tdown
+ :
+ $* -s <<EOI >>EOO
+ x = abc
+ cmd $x
+ y = 123
+ EOI
+ {
+ {
+ cmd abc
+ }
+ }
+ EOO
+
+ : after-tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd1
+ x = abc
+ cmd2
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+ }
+}
+
+: expected
+{
+ : newline-lcbrace
+ :
+ $* <:"{x" 2>>EOE != 0
+ testscript:1:2: error: expected newline after '{'
+ EOE
+
+ : rcbrace
+ :
+ $* <"{" 2>>EOE != 0
+ testscript:2:1: error: expected '}' at the end of the scope
+ EOE
+
+ : line-rcbrace
+ :
+ $* <<EOI 2>>EOE != 0
+ {
+ cmd;
+ }
+ EOI
+ testscript:3:1: error: expected another line after ';'
+ EOE
+
+ : newline-rcbrace
+ :
+ $* <<:EOI 2>>EOE != 0
+ {
+ }
+ EOI
+ testscript:2:2: error: expected newline after '}'
+ EOE
+
+ : line-eof
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ EOI
+ testscript:2:1: error: expected another line after ';'
+ EOE
+
+ : newline-cmd
+ :
+ $* <<:EOI 2>>EOE != 0
+ cmd;
+ EOI
+ testscript:1:5: error: expected newline instead of <end of file>
+ EOE
+
+ : newline-var
+ :
+ $* <:"x = abc;" 2>>EOE != 0
+ testscript:1:9: error: expected newline instead of <end of file>
+ EOE
+}
diff --git a/build2/test/script/parser+setup-teardown.test.testscript b/build2/test/script/parser+setup-teardown.test.testscript
new file mode 100644
index 0000000..9d67309
--- /dev/null
+++ b/build2/test/script/parser+setup-teardown.test.testscript
@@ -0,0 +1,151 @@
+# file : build2/test/script/parser+setup-teardown.test.testscript
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+: setup
+:
+{
+ : followed
+ :
+ {
+ : semi
+ :
+ $* <"+cmd;" 2>>EOE != 0
+ testscript:1:5: error: ';' after setup command
+ EOE
+
+ : colon
+ :
+ $* <"+cmd:" 2>>EOE != 0
+ testscript:1:5: error: ':' after setup command
+ EOE
+ }
+
+ : after
+ :
+ {
+ : test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ +cmd
+ EOI
+ testscript:2:1: error: setup command after tests
+ EOE
+
+ : after-tdownt
+ :
+ $* <<EOI 2>>EOE != 0
+ -cmd
+ +cmd
+ EOI
+ testscript:2:1: error: setup command after teardown
+ EOE
+ }
+
+ : in-test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ +cmd
+ EOI
+ testscript:2:1: error: setup command in test
+ EOE
+}
+
+: tdown
+:
+{
+ : followed
+ :
+ {
+ : semi
+ :
+ $* <"-cmd;" 2>>EOE != 0
+ testscript:1:5: error: ';' after teardown command
+ EOE
+
+ : colon
+ :
+ $* <"-cmd:" 2>>EOE != 0
+ testscript:1:5: error: ':' after teardown command
+ EOE
+ }
+
+ : in-test
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd;
+ -cmd
+ EOI
+ testscript:2:1: error: teardown command in test
+ EOE
+}
+
+: var
+:
+{
+ : between-tests
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ cmd
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-scope
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ {
+ cmd
+ }
+ EOI
+ testscript:3:1: error: scope after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-command-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ if true
+ cmd
+ end
+ EOI
+ testscript:3:1: error: test after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-scope-if
+ :
+ $* <<EOI 2>>EOE != 0
+ cmd
+ x = y
+ if true
+ {
+ cmd
+ }
+ EOI
+ testscript:3:1: error: scope after teardown
+ testscript:2:1: info: last teardown line appears here
+ EOE
+
+ : between-tests-variable-if
+ :
+ $* <<EOI >>EOO
+ cmd
+ x = y
+ if true
+ y = x
+ end
+ EOI
+ cmd
+ ? true
+ EOO
+}
diff --git a/build2/test/script/parser.test.cxx b/build2/test/script/parser.test.cxx
new file mode 100644
index 0000000..1862f98
--- /dev/null
+++ b/build2/test/script/parser.test.cxx
@@ -0,0 +1,242 @@
+// file : build2/test/script/parser.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+#include <iostream>
+
+#include <build2/types.hxx>
+#include <build2/utility.hxx>
+
+#include <build2/target.hxx>
+#include <build2/context.hxx> // reset()
+#include <build2/scheduler.hxx>
+
+#include <build2/test/target.hxx>
+
+#include <build2/test/script/token.hxx>
+#include <build2/test/script/parser.hxx>
+#include <build2/test/script/runner.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ // Here we assume we are running serially.
+ //
+ class print_runner: public runner
+ {
+ public:
+ print_runner (bool scope, bool id, bool line)
+ : scope_ (scope), id_ (id), line_ (line) {}
+
+ virtual bool
+ test (scope&) const override
+ {
+ return true;
+ }
+
+ virtual void
+ enter (scope& s, const location&) override
+ {
+ if (s.desc)
+ {
+ const auto& d (*s.desc);
+
+ if (!d.id.empty ())
+ cout << ind_ << ": id:" << d.id << endl;
+
+ if (!d.summary.empty ())
+ cout << ind_ << ": sm:" << d.summary << endl;
+
+ if (!d.details.empty ())
+ {
+ if (!d.id.empty () || !d.summary.empty ())
+ cout << ind_ << ":" << endl; // Blank.
+
+ const auto& s (d.details);
+ for (size_t b (0), e (0), n; e != string::npos; b = e + 1)
+ {
+ e = s.find ('\n', b);
+ n = ((e != string::npos ? e : s.size ()) - b);
+
+ cout << ind_ << ':';
+ if (n != 0)
+ {
+ cout << ' ';
+ cout.write (s.c_str () + b, static_cast<streamsize> (n));
+ }
+ cout << endl;
+ }
+ }
+ }
+
+ if (scope_)
+ {
+ cout << ind_ << "{";
+
+ if (id_ && !s.id_path.empty ()) // Skip empty root scope id.
+ cout << " # " << s.id_path.string ();
+
+ cout << endl;
+
+ ind_ += " ";
+ }
+ }
+
+ virtual void
+ run (scope&,
+ const command_expr& e, command_type t,
+ size_t i,
+ const location&) override
+ {
+ const char* s (nullptr);
+
+ switch (t)
+ {
+ case command_type::test: s = ""; break;
+ case command_type::setup: s = "+"; break;
+ case command_type::teardown: s = "-"; break;
+ }
+
+ cout << ind_ << s << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+ }
+
+ virtual bool
+ run_if (scope&,
+ const command_expr& e,
+ size_t i,
+ const location&) override
+ {
+ cout << ind_ << "? " << e;
+
+ if (line_)
+ cout << " # " << i;
+
+ cout << endl;
+
+ return e.back ().pipe.back ().program.string () == "true";
+ }
+
+ virtual void
+ leave (scope&, const location&) override
+ {
+ if (scope_)
+ {
+ ind_.resize (ind_.size () - 2);
+ cout << ind_ << "}" << endl;
+ }
+ }
+
+ private:
+ bool scope_;
+ bool id_;
+ bool line_;
+ string ind_;
+ };
+
+ // Usage: argv[0] [-s] [-i] [-l] [<testscript-name>]
+ //
+ int
+ main (int argc, char* argv[])
+ {
+ tracer trace ("main");
+
+ init (argv[0], 1); // Fake build system driver, default verbosity.
+ sched.startup (1); // Serial execution.
+ reset (strings ()); // No command line variables.
+
+ bool scope (false);
+ bool id (false);
+ bool line (false);
+ path name;
+
+ for (int i (1); i != argc; ++i)
+ {
+ string a (argv[i]);
+
+ if (a == "-s")
+ scope = true;
+ else if (a == "-i")
+ id = true;
+ else if (a == "-l")
+ line = true;
+ else
+ {
+ name = path (move (a));
+ break;
+ }
+ }
+
+ if (name.empty ())
+ name = path ("testscript");
+
+ assert (!id || scope); // Id can only be printed with scope.
+
+ try
+ {
+ cin.exceptions (istream::failbit | istream::badbit);
+
+ // Enter mock targets. Use fixed names and paths so that we can use
+ // them in expected results. Strictly speaking target paths should
+ // be absolute. However, the testscript implementation doesn't
+ // really care.
+ //
+ file& tt (
+ targets.insert<file> (work,
+ dir_path (),
+ "driver",
+ string (),
+ trace));
+
+ value& v (
+ tt.assign (
+ var_pool.rw ().insert<target_triplet> (
+ "test.target", variable_visibility::project)));
+
+ v = cast<target_triplet> ((*global_scope)["build.host"]);
+
+ testscript& st (
+ targets.insert<testscript> (work,
+ dir_path (),
+ name.leaf ().base ().string (),
+ name.leaf ().extension (),
+ trace));
+
+ tt.path (path ("driver"));
+ st.path (name);
+
+ // Parse and run.
+ //
+ parser p;
+ script s (tt, st, dir_path (work) /= "test-driver");
+ p.pre_parse (cin, s);
+
+ print_runner r (scope, id, line);
+ p.execute (s, r);
+ }
+ catch (const failed&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
+
+int
+main (int argc, char* argv[])
+{
+ return build2::test::script::main (argc, argv);
+}
diff --git a/build2/test/script/regex.test.cxx b/build2/test/script/regex.test.cxx
new file mode 100644
index 0000000..7b89e4d
--- /dev/null
+++ b/build2/test/script/regex.test.cxx
@@ -0,0 +1,301 @@
+// file : build2/test/script/regex.test.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <regex>
+#include <type_traits> // is_pod, is_array
+
+#include <build2/test/script/regex.hxx>
+
+using namespace std;
+using namespace build2::test::script::regex;
+
+int
+main ()
+{
+ using lc = line_char;
+ using ls = line_string;
+ using lr = line_regex;
+ using cf = char_flags;
+ using cr = char_regex;
+
+ // Test line_char.
+ //
+ {
+ static_assert (is_pod<lc>::value && !is_array<lc>::value,
+ "line_char must be char-like");
+
+ // Zero-initialed line_char should be the null-char as required by
+ // char_traits<>::length() specification.
+ //
+ assert (lc () == lc::nul);
+
+ line_pool p;
+
+ assert (lc::eof == -1);
+ assert (lc::nul == 0);
+
+ enum meta {mn = 'n', mp = 'p'};
+
+ // Special roundtrip.
+ //
+ assert (lc ('0').special () == '0');
+ assert (lc (0).special () == 0);
+ assert (lc (-1).special () == -1);
+ assert (lc ('p').special () == 'p');
+ assert (lc (u'\u2028').special () == u'\u2028');
+
+ // Special comparison.
+ //
+ assert (lc ('0') == lc ('0'));
+ assert (lc ('0') == '0');
+ assert (lc ('n') == mn);
+ assert (mn == static_cast<meta> (lc ('n')));
+
+ assert (lc ('0') != lc ('1'));
+ assert (lc ('0') != '1');
+ assert (lc ('n') != mp);
+ assert (lc ('0') != lc ("0", p));
+ assert (lc ('0') != lc (cr ("0"), p));
+
+ assert (lc ('0') < lc ('1'));
+ assert (lc ('0') < '1');
+ assert (lc ('1') < lc ("0", p));
+ assert (lc ('n') < mp);
+
+ assert (lc ('0') <= '1');
+ assert (lc ('0') <= lc ('1'));
+ assert (lc ('n') <= mn);
+ assert (lc ('1') <= lc ("0", p));
+
+ // Literal roundtrip.
+ //
+ assert (*lc ("abc", p).literal () == "abc");
+
+ // Literal comparison.
+ //
+ assert (lc ("a", p) == lc ("a", p));
+ assert (lc ("a", p).literal () == lc ("a", p).literal ());
+ assert (char (lc ("a", p)) == '\a');
+
+ assert (lc ("a", p) != lc ("b", p));
+ assert (!(lc ("a", p) != lc (cr ("a"), p)));
+ assert (lc ("a", p) != lc (cr ("b"), p));
+
+ assert (lc ("a", p) < lc ("b", p));
+ assert (!(lc ("a", p) < lc (cr ("a"), p)));
+
+ assert (lc ("a", p) <= lc ("b", p));
+ assert (lc ("a", p) <= lc (cr ("a"), p));
+ assert (lc ("a", p) < lc (cr ("c"), p));
+
+ // Regex roundtrip.
+ //
+ assert (regex_match ("abc", *lc (cr ("abc"), p).regex ()));
+
+ // Regex flags.
+ //
+ // icase
+ //
+ assert (regex_match ("ABC", cr ("abc", cf::icase)));
+
+ // idot
+ //
+ assert (!regex_match ("a", cr ("[.]", cf::idot)));
+ assert (!regex_match ("a", cr ("[\\.]", cf::idot)));
+
+ assert (regex_match ("a", cr (".")));
+ assert (!regex_match ("a", cr (".", cf::idot)));
+ assert (regex_match ("a", cr ("\\.", cf::idot)));
+ assert (!regex_match ("a", cr ("\\.")));
+
+ // regex::transform()
+ //
+ // The function is static and we can't test it directly. So we will test
+ // it indirectly via regex matches.
+ //
+ // @@ Would be nice to somehow address the inability to test internals (not
+ // exposed via headers). As a part of utility library support?
+ //
+ assert (regex_match (".a[.", cr (".\\.\\[[.]", cf::idot)));
+ assert (regex_match (".a[.", cr (".\\.\\[[\\.]", cf::idot)));
+ assert (!regex_match ("ba[.", cr (".\\.\\[[.]", cf::idot)));
+ assert (!regex_match (".a[b", cr (".\\.\\[[.]", cf::idot)));
+ assert (!regex_match (".a[b", cr (".\\.\\[[\\.]", cf::idot)));
+
+ // Regex comparison.
+ //
+ assert (lc ("a", p) == lc (cr ("a|b"), p));
+ assert (lc (cr ("a|b"), p) == lc ("a", p));
+ }
+
+ // Test char_traits<line_char>.
+ //
+ {
+ using ct = char_traits<lc>;
+ using vc = vector<lc>;
+
+ lc c;
+ ct::assign (c, '0');
+ assert (c == ct::char_type ('0'));
+
+ assert (ct::to_char_type (c) == c);
+ assert (ct::to_int_type (c) == c);
+
+ assert (ct::eq_int_type (c, c));
+ assert (!ct::eq_int_type (c, lc::eof));
+
+ assert (ct::eof () == lc::eof);
+
+ assert (ct::not_eof (c) == c);
+ assert (ct::not_eof (lc::eof) != lc::eof);
+
+ ct::assign (&c, 1, '1');
+ assert (c == ct::int_type ('1'));
+
+ assert (ct::eq (lc ('0'), lc ('0')));
+ assert (ct::lt (lc ('0'), lc ('1')));
+
+ vc v1 ({'0', '1', '2'});
+ vc v2 (3, lc::nul);
+
+ assert (ct::find (v1.data (), 3, '1') == v1.data () + 1);
+
+ ct::copy (v2.data (), v1.data (), 3);
+ assert (v2 == v1);
+
+ v2.push_back (lc::nul);
+ assert (ct::length (v2.data ()) == 3);
+
+ // Overlaping ranges.
+ //
+ ct::move (v1.data () + 1, v1.data (), 2);
+ assert (v1 == vc ({'0', '0', '1'}));
+
+ v1 = vc ({'0', '1', '2'});
+ ct::move (v1.data (), v1.data () + 1, 2);
+ assert (v1 == vc ({'1', '2', '2'}));
+ }
+
+ // Test line_char_locale and ctype<line_char> (only non-trivial functions).
+ //
+ {
+ using ct = ctype<lc>;
+
+ line_char_locale l;
+ assert (has_facet<ct> (l));
+
+ // It is better not to create q facet on stack as it is
+ // reference-countable.
+ //
+ const ct& t (use_facet<ct> (l));
+ line_pool p;
+
+ assert (t.is (ct::digit, '0'));
+ assert (!t.is (ct::digit, '?'));
+ assert (!t.is (ct::digit, lc ("0", p)));
+
+ const lc chars[] = { '0', '?' };
+ ct::mask m[2];
+
+ const lc* b (chars);
+ const lc* e (chars + 2);
+
+ // Cast flag value to mask type and compare to mask.
+ //
+ auto fl = [] (ct::mask m, ct::mask f) {return m == f;};
+
+ t.is (b, e, m);
+ assert (fl (m[0], ct::digit) && fl (m[1], 0));
+
+ assert (t.scan_is (ct::digit, b, e) == b);
+ assert (t.scan_is (0, b, e) == b + 1);
+
+ assert (t.scan_not (ct::digit, b, e) == b + 1);
+ assert (t.scan_not (0, b, e) == b);
+
+ {
+ char nr[] = "0?";
+ lc wd[2];
+ t.widen (nr, nr + 2, wd);
+ assert (wd[0] == b[0] && wd[1] == b[1]);
+ }
+
+ {
+ lc wd[] = {'0', lc ("a", p)};
+ char nr[2];
+ t.narrow (wd, wd + 2, '-', nr);
+ assert (nr[0] == '0' && nr[1] == '-');
+ }
+ }
+
+ // Test regex_traits<line_char>. Functions other that value() are trivial.
+ //
+ {
+ regex_traits<lc> t;
+
+ const int radix[] = {8, 10}; // Radix 16 is not supported by line_char.
+ const char digits[] = "0123456789ABCDEF";
+
+ for (size_t r (0); r < 2; ++r)
+ {
+ for (int i (0); i < radix[r]; ++i)
+ assert (t.value (digits[i], radix[r]) == i);
+ }
+ }
+
+ // Test line_regex construction.
+ //
+ {
+ line_pool p;
+ lr r1 ({lc ("foo", p), lc (cr ("ba(r|z)"), p)}, move (p));
+
+ lr r2 (move (r1));
+ assert (regex_match (ls ({lc ("foo", r2.pool), lc ("bar", r2.pool)}), r2));
+ assert (!regex_match (ls ({lc ("foo", r2.pool), lc ("ba", r2.pool)}), r2));
+ }
+
+ // Test line_regex match.
+ //
+ {
+ line_pool p;
+
+ const lc foo ("foo", p);
+ const lc bar ("bar", p);
+ const lc baz ("baz", p);
+ const lc blank ("", p);
+
+ assert (regex_match (ls ({foo, bar}), lr ({foo, bar})));
+ assert (!regex_match (ls ({foo, baz}), lr ({foo, bar})));
+
+ assert (regex_match (ls ({bar, foo}),
+ lr ({'(', foo, '|', bar, ')', '+'})));
+
+ assert (regex_match (ls ({foo, foo, bar}),
+ lr ({'(', foo, ')', '\\', '1', bar})));
+
+ assert (regex_match (ls ({foo}), lr ({lc (cr ("fo+"), p)})));
+ assert (regex_match (ls ({foo}), lr ({lc (cr (".*"), p)})));
+ assert (regex_match (ls ({blank}), lr ({lc (cr (".*"), p)})));
+
+ assert (regex_match (ls ({blank, blank, foo}),
+ lr ({blank, '*', foo, blank, '*'})));
+
+ assert (regex_match (ls ({blank, blank, foo}), lr ({'.', '*'})));
+
+ assert (regex_match (ls ({blank, blank}),
+ lr ({blank, '*', foo, '?', blank, '*'})));
+
+ assert (regex_match (ls ({foo}), lr ({foo, '{', '1', '}'})));
+ assert (regex_match (ls ({foo, foo}), lr ({foo, '{', '1', ',', '}'})));
+
+ assert (regex_match (ls ({foo, foo}),
+ lr ({foo, '{', '1', ',', '2', '}'})));
+
+ assert (!regex_match (ls ({foo, foo}),
+ lr ({foo, '{', '3', ',', '4', '}'})));
+
+ assert (regex_match (ls ({foo}), lr ({'(', '?', '=', foo, ')', foo})));
+ assert (regex_match (ls ({foo}), lr ({'(', '?', '!', bar, ')', foo})));
+ }
+}