diff options
Diffstat (limited to 'build2')
49 files changed, 6537 insertions, 3 deletions
diff --git a/build2/.gitignore b/build2/.gitignore index 5ea3913..51b4c16 100644 --- a/build2/.gitignore +++ b/build2/.gitignore @@ -1,6 +1,11 @@ b b-boot +*.test #*-options #*-options.?xx config.hxx version.hxx + +# Testscript output directory (can be symlink). +# +test-*.test diff --git a/build2/buildfile b/build2/buildfile index bd730c3..64935f3 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -5,15 +5,33 @@ import libs = libbutl%lib{butl} import libs += libpkgconf%lib{pkgconf} -exe{b}: cxx{b} libue{b} +./: exe{b}: cxx{b} libue{b} -libue{b}: {hxx ixx txx cxx}{** -b -b-options -config -version} \ - {hxx ixx cxx}{b-options} {hxx}{config version} \ +libue{b}: {hxx ixx txx cxx}{** -b -b-options -config -version -**.test...} \ + {hxx ixx cxx}{b-options} {hxx}{config version} \ $libs hxx{config}: in{config} hxx{version}: in{version} $src_root/manifest +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + b = $path.base($name($t)) + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n +$b+*.test...} + $d/exe{$n}: libue{b}: bin.whole = false +} + # Build options. # # Pass our compiler target to be used as build2 host. diff --git a/build2/cc/lexer+char-literal.test.testscript b/build2/cc/lexer+char-literal.test.testscript new file mode 100644 index 0000000..6a0a036 --- /dev/null +++ b/build2/cc/lexer+char-literal.test.testscript @@ -0,0 +1,67 @@ +# file : build2/cc/lexer+char-literal.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test character literals. +# + +: normal +: +$* <<EOI >>EOO +'a' +'aa' +'"' +EOI +<char literal> +<char literal> +<char literal> +EOO + +: prefix +: +$* <<EOI >>EOO +L'a' +U'a' +u'a' +u8'a' +u8R'a' +EOI +<char literal> +<char literal> +<char literal> +<char literal> +'u8R' +<char literal> +EOO + +: suffix +: +$* <<EOI >>EOO +'a'x +'a'_X123 +EOI +<char literal> +<char literal> +EOO + +: escape +: +$* <<EOI >>EOO +'\'' +'\\' +'\\\'' +'\n' +U'\U0001f34c' +EOI +<char literal> +<char literal> +<char literal> +<char literal> +<char literal> +EOO + +: unterminated +: +$* <"'a" 2>>EOE != 0 +stdin:1:1: error: unterminated character literal +EOE diff --git a/build2/cc/lexer+comment.test.testscript b/build2/cc/lexer+comment.test.testscript new file mode 100644 index 0000000..493c295 --- /dev/null +++ b/build2/cc/lexer+comment.test.testscript @@ -0,0 +1,88 @@ +# file : build2/cc/lexer+comment.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test C and C++ comments. +# + +: c-comment +: +$* <<EOI +/* 'one' */ +/* "two" // three +*/ +/** +four +// five */ +/** +six /* +*/ +EOI + +: cxx-comment +: +$* <<EOI +// 'one' +// "two" // three +// four /* five */ +EOI + +: commented-out +: +$* <<EOI >"';'" +// /* +; +// */ +EOI + +: c-unterminated +: +$* <<EOI 2>>EOE != 0 +/* +comment +EOI +stdin:1:2: error: unterminated comment +EOE + +: cxx-unterminated +: +$* <<:EOI +// comment +EOI + +: in-char-literal +: +$* <<EOI >>EOO +'//' +'/*'*/ +EOI +<char literal> +<char literal> +<punctuation> +<punctuation> +EOO + +: in-string-literal +: +$* <<EOI >>EOO +"//foo" +"/*"*/ +EOI +<string literal> +<string literal> +<punctuation> +<punctuation> +EOO + +: in-raw-string-literal +: +$* <<EOI >>EOO +R"X( +// foo +/* bar +)X"*/ +EOI +<string literal> +<punctuation> +<punctuation> +EOO diff --git a/build2/cc/lexer+line.test.testscript b/build2/cc/lexer+line.test.testscript new file mode 100644 index 0000000..abcc587 --- /dev/null +++ b/build2/cc/lexer+line.test.testscript @@ -0,0 +1,67 @@ +# file : build2/cc/lexer+line.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test line continuations. +# + +: identifier +: +$* <<EOI >"'foo123'" +fo\ +o\ +1\ +2\ +3 +EOI + +: punctuation +: +$* <<EOI >'<punctuation>' +.\ +.\ +. +EOI + +: c-comment +: +$* <<EOI +/\ +* +comment +*\ +/\ + +EOI + +: cxx-comment +: +$* <<EOI +/\ +/ comment\ +more\ +more +EOI + +: other +: +$* <<EOI >>EOO +\abc +EOI +<punctuation> +'abc' +EOO + +: multiple +: +$* <<EOI >>EOO +\\ +EOI +<punctuation> +EOO + +: unterminated +: +$* <<:EOI >'<punctuation>' +\ +EOI diff --git a/build2/cc/lexer+number.test.testscript b/build2/cc/lexer+number.test.testscript new file mode 100644 index 0000000..c342818 --- /dev/null +++ b/build2/cc/lexer+number.test.testscript @@ -0,0 +1,48 @@ +# file : build2/cc/lexer+number.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test numbers. +# + +$* <'1' >'<number literal>' +$* <'.1' >'<number literal>' +$* <'1.' >'<number literal>' + +$* <'0b101' >'<number literal>' +$* <'0123' >'<number literal>' +$* <'0X12AB' >'<number literal>' + +$* <'1e10' >'<number literal>' +$* <'1E+10' >'<number literal>' +$* <'0x1.p10' >'<number literal>' +$* <'0x1.P-10' >'<number literal>' + +$* <"123'456" >'<number literal>' +$* <"0xff00'00ff" >'<number literal>' + +$* <'123f' >'<number literal>' +$* <'123UL' >'<number literal>' +$* <'123_X' >'<number literal>' + +: separate-punctuation +: +$* <'123;' >>EOO +<number literal> +';' +EOO + +: separate-plus-minus +: +$* <'1.0_a+2.0' >>EOO +<number literal> +<punctuation> +<number literal> +EOO + +: separate-whitespace +: +$* <'123 abc' >>EOO +<number literal> +'abc' +EOO diff --git a/build2/cc/lexer+preprocessor.test.testscript b/build2/cc/lexer+preprocessor.test.testscript new file mode 100644 index 0000000..fc061cb --- /dev/null +++ b/build2/cc/lexer+preprocessor.test.testscript @@ -0,0 +1,73 @@ +# file : build2/cc/lexer+preprocessor.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test preprocessor lines. +# + +: normal +: +$* <<EOI +#pragma message("abc") +EOI + +: multiline +: +$* <<EOI +#pragma message \ +( \ +"abc" \ +) +EOI + +: comment +: +$* <<EOI +#pragma foo /* +bar +baz +*/ +#pragma foo // bar baz +EOI + +: line +: +$* -l <<EOI >>EOO +; +# 1 "test.cxx" 2 +; + ; +# 4 +; +#line 8 "z:\\tmp\\test.hxx" +; +#line 10 +; +# 5 "test.cxx" +; +EOI +';' stdin:1:1 +';' test.cxx:1:1 +';' test.cxx:2:3 +';' test.cxx:4:1 +';' z:\tmp\test.hxx:8:1 +';' z:\tmp\test.hxx:10:1 +';' test.cxx:5:1 +EOO + +: include +: +$* <<EOI 2>>EOE != 0 +#include <foo/bar> +EOI +stdin:1:1: error: unexpected #include directive +EOE + +: nested +: +$* <<EOI >>EOO +#define FOO(x) #y +; +EOI +';' +EOO diff --git a/build2/cc/lexer+raw-string-literal.test.testscript b/build2/cc/lexer+raw-string-literal.test.testscript new file mode 100644 index 0000000..e72d77b --- /dev/null +++ b/build2/cc/lexer+raw-string-literal.test.testscript @@ -0,0 +1,90 @@ +# file : build2/cc/lexer+raw-string-literal.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test raw string literals. +# + +: normal +: +$* <<EOI >>EOO +R"()" +R"(ab)" +R"(a"b)" +R"(a)b)" +R"%(a%)b)%" +R"X(a + b)X" +R"X(a\ + b)X" +EOI +<string literal> +<string literal> +<string literal> +<string literal> +<string literal> +<string literal> +<string literal> +EOO + +: prefix +: +$* <<EOI >>EOO +LR"(ab)" +UR"(ab)" +uR"(ab)" +u8R"(ab)" +EOI +<string literal> +<string literal> +<string literal> +<string literal> +EOO + +: suffix +: +$* <<EOI >>EOO +R"(ab)"x +R"(ab)"_X123 +EOI +<string literal> +<string literal> +EOO + +: escape +: +$* <<EOI >>EOO +R"(\)" +EOI +<string literal> +EOO + +: invalid-no-paren +: +$* <'R"a"' 2>>EOE != 0 +stdin:1:2: error: invalid raw string literal +EOE + +: invalid-paren +: +$* <'R")()("' 2>>EOE != 0 +stdin:1:2: error: invalid raw string literal +EOE + +: invalid-unterminated-paren +: +$* <'R"(abc"' 2>>EOE != 0 +stdin:1:2: error: invalid raw string literal +EOE + +: invalid-unterminated-delimiter +: +$* <'R"X(abc)"' 2>>EOE != 0 +stdin:1:2: error: invalid raw string literal +EOE + +: invalid-unterminated-quote +: +$* <'R"X(abc)X' 2>>EOE != 0 +stdin:1:2: error: invalid raw string literal +EOE diff --git a/build2/cc/lexer+string-literal.test.testscript b/build2/cc/lexer+string-literal.test.testscript new file mode 100644 index 0000000..c486aa1 --- /dev/null +++ b/build2/cc/lexer+string-literal.test.testscript @@ -0,0 +1,65 @@ +# file : build2/cc/lexer+string-literal.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test string literals (except raw). +# + +: normal +: +$* <<EOI >>EOO +"aa" +"'" +"a""b" +EOI +<string literal> +<string literal> +<string literal> +<string literal> +EOO + +: prefix +: +$* <<EOI >>EOO +L"ab" +U"ab" +u"ab" +u8"ab" +EOI +<string literal> +<string literal> +<string literal> +<string literal> +EOO + +: suffix +: +$* <<EOI >>EOO +"ab"x +"ab"_X123 +EOI +<string literal> +<string literal> +EOO + +: escape +: +$* <<EOI >>EOO +"\"\"" +"\\\\" +"\\\"\\" +"\n\t" +U"a\U0001f34c" +EOI +<string literal> +<string literal> +<string literal> +<string literal> +<string literal> +EOO + +: unterminated +: +$* <'"ab' 2>>EOE != 0 +stdin:1:1: error: unterminated string literal +EOE diff --git a/build2/cc/lexer.test.cxx b/build2/cc/lexer.test.cxx new file mode 100644 index 0000000..a2e33b7 --- /dev/null +++ b/build2/cc/lexer.test.cxx @@ -0,0 +1,80 @@ +// file : build2/cc/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/cc/lexer.hxx> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + // Usage: argv[0] [-l] [<file>] + // + int + main (int argc, char* argv[]) + { + bool loc (false); + const char* file (nullptr); + + for (int i (1); i != argc; ++i) + { + string a (argv[i]); + + if (a == "-l") + loc = true; + else + { + file = argv[i]; + break; + } + } + + try + { + ifdstream is; + if (file != nullptr) + is.open (file); + else + { + file = "stdin"; + is.open (fddup (stdin_fd ())); + } + + lexer l (is, path (file)); + + // No use printing eos since we will either get it or loop forever. + // + for (token t; l.next (t) != token_type::eos; ) + { + cout << t; + + if (loc) + cout << ' ' << t.file << ':' << t.line << ':' << t.column; + + cout << endl; + } + } + catch (const failed&) + { + return 1; + } + + return 0; + } + } +} + +int +main (int argc, char* argv[]) +{ + return build2::cc::main (argc, argv); +} diff --git a/build2/cc/parser+module.test.testscript b/build2/cc/parser+module.test.testscript new file mode 100644 index 0000000..0e2e52e --- /dev/null +++ b/build2/cc/parser+module.test.testscript @@ -0,0 +1,149 @@ +# file : build2/cc/parser+module.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +# Test C++ module constructs. +# + +: import +: +$* <<EOI >>EOI +import foo; +import foo.bar; +import foo.bar.baz; +EOI + +: module-implementation +: +$* <<EOI >>EOI +module foo; +EOI + +: module-interface +: +$* <<EOI >>EOI +export module foo; +EOI + +: export-imported +: +$* <<EOI >>EOO +export import foo; +EOI +export import foo; +EOO + +: export-imported-block +: +$* <<EOI >>EOO +import bar; + +export {import foo;} + +export +{ + namespace foo + { + class c {}; + } + + template <typename T> int f (); + + import bar; +} +EOI +export import bar; +export import foo; +EOO + +: non-module +: +$* <<EOI +#pragma import module foo; +#pragma export module foo; +#pragma module foo; +extern module foo: int foo (); +export namespace bar {int fox ();} +EOI + +: attribute +: +$* <<EOI >>EOO +import foo [[export({import})]]; +module bar [[module({module})]]; +EOI +import foo; +module bar; +EOO + +: import-duplicate +: +$* <<EOI >>EOO +import foo; +import bar.baz; +import foo; +import bar . baz; +EOI +import foo; +import bar.baz; +EOO + +: brace-missing +: +$* <<EOI 2>>EOE != 0 +export +{ + class foo + { + //}; + module foo; +} +EOI +stdin:8:1: error: {}-imbalance detected +EOE + +: brace-stray +: +$* <<EOI 2>>EOE != 0 +export +{ + class foo + { + };} +} +module foo; +EOI +stdin:6:1: error: {}-imbalance detected +EOE + +: import-missing-name +: +$* <<EOI 2>>EOE != 0 +import ; +EOI +stdin:1:8: error: module name expected instead of ';' +EOE + +: module-missing-name +: +$* <<EOI 2>>EOE != 0 +module ; +EOI +stdin:1:1: error: module declaration expected after leading module marker +EOE + +: import-missing-semi +: +$* <<EOI 2>>EOE != 0 +import foo +EOI +stdin:2:1: error: ';' expected instead of <end of file> +EOE + +: module-missing-semi +: +$* <<EOI 2>>EOE != 0 +export module foo +EOI +stdin:2:1: error: ';' expected instead of <end of file> +EOE diff --git a/build2/cc/parser.test.cxx b/build2/cc/parser.test.cxx new file mode 100644 index 0000000..ab42e31 --- /dev/null +++ b/build2/cc/parser.test.cxx @@ -0,0 +1,66 @@ +// file : build2/cc/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/cc/parser.hxx> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + // Usage: argv[0] [<file>] + // + int + main (int argc, char* argv[]) + { + try + { + const char* file; + + ifdstream is; + if (argc > 1) + { + file = argv[1]; + is.open (file); + } + else + { + file = "stdin"; + is.open (fddup (stdin_fd ())); + } + + parser p; + translation_unit u (p.parse (is, path (file))); + + for (const module_import& m: u.mod.imports) + cout << (m.exported ? "export " : "") + << "import " << m.name << ';' << endl; + + if (!u.mod.name.empty ()) + cout << (u.mod.iface ? "export " : "") + << "module " << u.mod.name << ';' << endl; + } + catch (const failed&) + { + return 1; + } + + return 0; + } + } +} + +int +main (int argc, char* argv[]) +{ + return build2::cc::main (argc, argv); +} diff --git a/build2/function+call.test.testscript b/build2/function+call.test.testscript new file mode 100644 index 0000000..1678c28 --- /dev/null +++ b/build2/function+call.test.testscript @@ -0,0 +1,161 @@ +# file : build2/function+call.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +: qual-implicit +: +$* <'print $dummy.dummy0()' >'abc' + +: qual-explicit +: +$* <'print $dummy.qual()' >'abc' + +: qual-fail +: +$* <'print $qual()' 2>>EOE != 0 +buildfile:1:8: error: unmatched call to qual() + info: candidate: dummy.qual() +EOE + +: derived-base +: Test derived-to-base overload resolution +: +$* <'print $dummy.abs([dir_path] .)' >'false'; +$* <'print $dummy.abs([abs_dir_path] .)' >'true' + +: variadic +: +$* <'print $variadic([bool] true, foo, bar)' >'3' + +: fail +: +$* <'$fail()' 2>>EOE != 0 +error: failed +buildfile:1:2: info: while calling fail() +EOE + +: fail-invalid-arg +: +$* <'$fail_arg(abc)' 2>>EOE != 0 +error: invalid argument: invalid uint64 value: 'abc' +buildfile:1:2: info: while calling fail_arg(<untyped>) +EOE + +: no-match-name +: +$* <'$bogus()' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to bogus() +EOE + +: no-match-count +: +$* <'$dummy0(abc)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to dummy0(<untyped>) + info: candidate: dummy0(), qualified name dummy.dummy0 +EOE + +: no-match-type +: +$* <'$dummy1([uint64] 123)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to dummy1(uint64) + info: candidate: dummy1(string), qualified name dummy.dummy1 +EOE + +: ambig +: +$* <'$ambig(abc)' 2>>~/EOE/ != 0 +buildfile:1:2: error: ambiguous call to ambig(<untyped>) +/(( + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig +/)|( + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig +/)) +EOE + +: unmatched +: +$* <'$ambig(abc, def)' 2>>~/EOE/ != 0 +buildfile:1:2: error: unmatched call to ambig(<untyped>, <untyped>) +/(( + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig +/)|( + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig +/)) +EOE + +: reverse +: +$* <'print $reverse([string] abc)' >'abc' + +: optional-absent +: +$* <'print $optional()' >'true' + +: optional-present +: +$* <'print $optional(abc)' >'false' + +: null-true +: +$* <'print $nullable([null])' >'true' + +: null-false +: +$* <'print $nullable(nonull)' >'false' + +: null-fail +: +$* <'$dummy1([string null])' 2>>EOE != 0 +error: invalid argument: null value +buildfile:1:2: info: while calling dummy1(string) +EOE + +: print-call-1-untyped +: +$* <'$bogus(abc)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to bogus(<untyped>) +EOE + +: print-call-1-typed +: +$* <'$bogus([uint64] 123)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to bogus(uint64) +EOE + +: print-call-2 +: +$* <'$bogus(abc, [uint64] 123)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to bogus(<untyped>, uint64) +EOE + +: print-fovl +: +$* <'$ambig([bool] true)' 2>>~/EOE/ != 0 +buildfile:1:2: error: ambiguous call to ambig(bool) +/(( + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig +/)|( + info: candidate: ambig(<untyped> [, string]), qualified name dummy.ambig + info: candidate: ambig(<untyped> [, uint64]), qualified name dummy.ambig +/)) +EOE + +: print-fovl-variadic +: +$* <'$variadic(abc)' 2>>EOE != 0 +buildfile:1:2: error: unmatched call to variadic(<untyped>) + info: candidate: variadic(bool [, ...]) +EOE + +: member-function +: +$* <'print $dummy.length([path] abc)' >'3' + +: data-member +: +$* <'print $dummy.type([name] cxx{foo})' >'cxx' diff --git a/build2/function+syntax.test.testscript b/build2/function+syntax.test.testscript new file mode 100644 index 0000000..bd86dd0 --- /dev/null +++ b/build2/function+syntax.test.testscript @@ -0,0 +1,29 @@ +# file : build2/function+syntax.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +$* <'$dump()' >:'' : none +$* <'$dump( )' >:'' : none-in-spaces +$* <'$dump("")' >'{}' : one-empty +$* <'$dump(a)' >'a' : one-single +$* <'$dump(a b c)' >'a b c' : one-list +$* <'$dump(d/t{x y z})' >'d/t{x} d/t{y} d/t{z}' : one-names + +$* <'print a$dummy1([string] b)c' >'abc' : concat +$* <'print $dummy2([uint64] 123, [uint64] 321)' >'444' : multi-arg + +: quoting +: Verify we can inhibit function call with quoting +: +$* <<EOI >>EOO +foo = FOO +bar = BAR + +print $foo"($bar)" +print "$foo"($bar) +print "$foo""($bar)" +EOI +FOOBAR +FOOBAR +FOOBAR +EOO diff --git a/build2/function.test.cxx b/build2/function.test.cxx new file mode 100644 index 0000000..a9ba7bb --- /dev/null +++ b/build2/function.test.cxx @@ -0,0 +1,131 @@ +// file : build2/function.test.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <iostream> + +#include <build2/types.hxx> +#include <build2/utility.hxx> + +#include <build2/parser.hxx> +#include <build2/context.hxx> +#include <build2/function.hxx> +#include <build2/variable.hxx> +#include <build2/diagnostics.hxx> + +using namespace std; + +namespace build2 +{ + static const optional<const value_type*> arg_bool[1] = + { + &value_traits<bool>::value_type + }; + + static dir_path + scoped (const scope*, dir_path d) + { + return d; + } + + static void + scoped_void (const scope*, dir_path) + { + } + + int + main (int, char* argv[]) + { + init (argv[0], 1); // Fake build system driver, default verbosity. + reset (strings ()); // No command line variables. + + function_family f ("dummy"); + + f["fail"] = []() {fail << "failed" << endf;}; + f["fail_arg"] = [](names a) {return convert<uint64_t> (move (a[0]));}; + + f["nullable"] = [](names* a) {return a == nullptr;}; + f["optional"] = [](optional<names> a) {return !a;}; + + f["dummy0"] = []() {return "abc";}; + f["dummy1"] = [](string s) {return s;}; + f["dummy2"] = [](uint64_t x, uint64_t y) {return x + y;}; + + f["ambig"] = [](names a, optional<string>) {return a;}; + f["ambig"] = [](names a, optional<uint64_t>) {return a;}; + + f["reverse"] = [](names a) {return a;}; + + f["scoped"] = [](const scope*, names a) {return a;}; + f["scoped_void"] = [](const scope*, names) {}; + f["scoped"] = &scoped; + f["scoped_void"] = &scoped_void; + + f[".qual"] = []() {return "abc";}; + + f[".length"] = &path::size; // Member function. + f[".type"] = &name::type; // Data member. + + f[".abs"] = [](dir_path d) {return d.absolute ();}; + + // Variadic function with first required argument of type bool. Returns + // number of arguments passed. + // + functions.insert ( + "variadic", + function_overload ( + nullptr, + 1, + function_overload::arg_variadic, + function_overload::types (arg_bool, 1), + [] (const scope*, vector_view<value> args, const function_overload&) + { + return value (static_cast<uint64_t> (args.size ())); + })); + + // Dump arguments. + // + functions.insert ( + "dump", + function_overload ( + nullptr, + 0, + function_overload::arg_variadic, + function_overload::types (), + [] (const scope*, vector_view<value> args, const function_overload&) + { + for (value& a: args) + { + if (a.null) + cout << "[null]"; + else if (!a.empty ()) + { + names storage; + cout << reverse (a, storage); + } + cout << endl; + } + return value (nullptr); + })); + + try + { + scope& s (*scope::global_); + + parser p; + p.parse_buildfile (cin, path ("buildfile"), s, s); + } + catch (const failed&) + { + return 1; + } + + return 0; + } +} + +int +main (int argc, char* argv[]) +{ + return build2::main (argc, argv); +} diff --git a/build2/lexer+buildspec.test.testscript b/build2/lexer+buildspec.test.testscript new file mode 100644 index 0000000..9083abe --- /dev/null +++ b/build2/lexer+buildspec.test.testscript @@ -0,0 +1,16 @@ +# file : build2/lexer+buildspec.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +test.arguments = buildspec + +: punctuation +: +$* <:'x,x(x)' >>EOO +'x' +, +'x' + ( +'x' +) +EOO diff --git a/build2/lexer+comment.test.testscript b/build2/lexer+comment.test.testscript new file mode 100644 index 0000000..4323c84 --- /dev/null +++ b/build2/lexer+comment.test.testscript @@ -0,0 +1,139 @@ +# file : build2/lexer+comment.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +: single-line +: +{ + : only + : + $* <<EOI >>:EOO + # comment + EOI + EOO + + : first + : + $* <<EOI >>EOO + # comment + foo + EOI + 'foo' + <newline> + EOO + + : last + : + $* <<EOI >>EOO + foo + # comment + EOI + 'foo' + <newline> + EOO + + : few + : + $* <<EOI >>EOO + foo + # comment + # comment + EOI + 'foo' + <newline> + EOO + + : cont + : + $* <<EOI >>EOO + foo + # comment\\ + bar + EOI + 'foo' + <newline> + 'bar' + <newline> + EOO + + : same + : + $* <<EOI >>EOO + foo # comment + bar # comment + EOI + 'foo' + <newline> + 'bar' + <newline> + EOO +} + +: multi-line +: +{ + : only + : + $* <<EOI >>:EOO + #\ + comment + comment + #\ + EOI + EOO + + : empty + : + $* <<EOI >>:EOO + #\ + #\ + EOI + EOO + + : start-same + : + $* <<EOI >>EOO + foo #\ + comment + comment + #\ + EOI + 'foo' + <newline> + EOO + + : end-same + : + $* <<EOI >>EOO + #\ + comment + comment + foo #\ + bar + EOI + 'bar' + <newline> + EOO + + : end-not + : + $* <<EOI >>EOO + #\ + comment + #\ not an end + foo #\ + bar + EOI + 'bar' + <newline> + EOO + + : unterm + : + $* <<EOI 2>>EOE != 0 + #\ + comment + EOI + stdin:3:1: error: unterminated multi-line comment + EOE +} diff --git a/build2/lexer+eval.test.testscript b/build2/lexer+eval.test.testscript new file mode 100644 index 0000000..eccd029 --- /dev/null +++ b/build2/lexer+eval.test.testscript @@ -0,0 +1,76 @@ +# file : build2/lexer+eval.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +test.arguments = eval + +: punctuation +: +$* <:'x:x{x}x[x]x$x?x,x(x)' >>EOO +'x' +: +'x' +{ +'x' +} +'x' +[ +'x' +] +'x' +$ +'x' +? +'x' +, +'x' +( +'x' +) +EOO + +: logical +: +$* <:'x|x||x&x&&x!x!!x)' >>EOO +'x|x' +|| +'x&x' +&& +'x' +! +'x' +! +! +'x' +) +EOO + +: comparison +: +$* <:'x=x==x!=x<x<=x>x>=)' >>EOO +'x=x' +== +'x' +!= +'x' +< +'x' +<= +'x' +> +'x' +>= +) +EOO + +: newline +: +$* <'x' >- 2>>EOE != 0 +stdin:1:2: error: newline in evaluation context +EOE + +: eof +: +$* <:'' 2>>EOE != 0 +stdin:1:1: error: unterminated evaluation context +EOE diff --git a/build2/lexer+quoting.test.testscript b/build2/lexer+quoting.test.testscript new file mode 100644 index 0000000..21b9046 --- /dev/null +++ b/build2/lexer+quoting.test.testscript @@ -0,0 +1,108 @@ +# file : build2/lexer+quoting.test.testscript +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +test.options += -q + +: unquoted +: +$* <'foo' >>EOO +'foo' +<newline> +EOO + +: comp +: +{ + : single + : + $* <":'foo':" >>EOO + : + 'foo' [S/C] + : + <newline> + EOO + + : double + : + $* <':"foo":' >>EOO + : + 'foo' [D/C] + : + <newline> + EOO + + : single-empty + : + $* <"''" >>EOO + '' [S/C] + <newline> + EOO + + : double-empty + : + $* <'""' >>EOO + '' [D/C] + <newline> + EOO +} + +: part +{ + : quoted + { + : start + : Token start already quoted + : + $* <'"$foo"' >>EOO + '' [D/P] + $ [D/C] + 'foo' [D/P] + <newline> + EOO + + : end + : Token end still quoted + : + $* <'"foo$"' >>EOO + 'foo' [D/P] + $ [D/C] + '' [D/P] + <newline> + EOO + } + + : unquoted + { + : start + : Token starts with unquoted character + : + $* <'f"oo"' >>EOO + 'foo' [D/P] + <newline> + EOO + + : end + : Token continous with unquoted character + : + $* <'"fo"o' >>EOO + 'foo' [D/P] + <newline> + EOO + + : escape + : Token continous with unquoted escaped character + : + $* <'"fo"\"' >>EOO + 'fo"' [D/P] + <newline> + EOO + } +} + +: mixed +: +$* <"\"fo\"'o'" >>EOO +'foo' [M/P] +<newline> +EOO diff --git a/build2/lexer.test.cxx b/build2/lexer.test.cxx new file mode 100644 index 0000000..8abd5f7 --- /dev/null +++ b/build2/lexer.test.cxx @@ -0,0 +1,98 @@ +// file : build2/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/token.hxx> +#include <build2/lexer.hxx> + +using namespace std; + +namespace build2 +{ + // Usage: argv[0] [-q] [<lexer-mode>] + // + int + main (int argc, char* argv[]) + { + bool quote (false); + lexer_mode m (lexer_mode::normal); + + for (int i (1); i != argc; ++i) + { + string a (argv[i]); + + if (a == "-q") + quote = true; + else + { + if (a == "normal") m = lexer_mode::normal; + else if (a == "variable") m = lexer_mode::variable; + else if (a == "value") m = lexer_mode::value; + else if (a == "attribute") m = lexer_mode::attribute; + else if (a == "eval") m = lexer_mode::eval; + else if (a == "buildspec") m = lexer_mode::buildspec; + else assert (false); + break; + } + } + + try + { + cin.exceptions (istream::failbit | istream::badbit); + + // Most alternative modes auto-expire so we need something underneath. + // + lexer l (cin, path ("stdin")); + + if (m != lexer_mode::normal) + 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 ()) + { + if (t.separated && t.type != token_type::newline) + cout << ' '; + + // Print each token on a separate line without quoting operators. + // + t.printer (cout, t, false); + + if (quote) + { + char q ('\0'); + switch (t.qtype) + { + case quote_type::single: q = 'S'; break; + case quote_type::double_: q = 'D'; break; + case quote_type::mixed: q = 'M'; break; + case quote_type::unquoted: break; + } + + if (q != '\0') + cout << " [" << q << (t.qcomp ? "/C" : "/P") << ']'; + } + + cout << endl; + } + } + catch (const failed&) + { + return 1; + } + + return 0; + } +} + +int +main (int argc, char* argv[]) +{ + return build2::main (argc, argv); +} diff --git a/build2/name.test.cxx b/build2/name.test.cxx new file mode 100644 index 0000000..0434aac --- /dev/null +++ b/build2/name.test.cxx @@ -0,0 +1,96 @@ +// file : build2/name.test.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <sstream> + +#include <cassert> +#include <iostream> + +#include <build2/types.hxx> // Includes name. +#include <build2/utility.hxx> + +#include <build2/diagnostics.hxx> + +using namespace std; + +namespace build2 +{ + int + main (int, char*[]) + { + using dir = dir_path; + + // Test string representation. + // + { + auto ts = [] (const name& n) {return to_string (n);}; + + assert (ts (name ()) == ""); + + assert (ts (name ("foo")) == "foo"); + + assert (ts (name (dir ("bar/"))) == "bar/"); + assert (ts (name (dir ("bar/baz/"))) == "bar/baz/"); + + assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}"); + assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}"); + + assert (ts (name (dir ("bar/"), "foo")) == "bar/foo"); + + assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}"); + assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}"); + } + + // Test stream representation. + // + { + auto ts = [] (const name& n, bool quote = true) + { + ostringstream os; + stream_verb (os, stream_verbosity (0, 1)); + to_stream (os, n, quote); + return os.str (); + }; + + assert (ts (name ()) == "''"); + assert (ts (name (), false) == "{}"); + + assert (ts (name ("foo")) == "foo"); + + assert (ts (name (dir ("bar/"))) == "bar/"); + assert (ts (name (dir ("bar/baz/"))) == "bar/baz/"); + + assert (ts (name (dir ("bar/"), "dir", "")) == "dir{bar/}"); + assert (ts (name (dir ("bar/baz/"), "dir", "")) == "bar/dir{baz/}"); + + assert (ts (name (dir ("bar/"), "foo")) == "bar/foo"); + + assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}"); + assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}"); + + // Quoting. + // + assert (ts (name (dir ("bar baz/"), "dir", "foo fox")) == "'bar baz/'dir{'foo fox'}"); + + // Relative logic. + // +#ifndef _WIN32 + dir rb ("/bar/"); + relative_base = &rb; + + assert (ts (name (dir ("/bar/"), "dir", "")) == "dir{./}"); + assert (ts (name (dir ("/bar/"), "", "foo")) == "foo"); + assert (ts (name (dir ("/bar/baz/"), "dir", "")) == "dir{baz/}"); +#endif + } + + return 0; + } +} + +int +main (int argc, char* argv[]) +{ + return build2::main (argc, argv); +} diff --git a/build2/scheduler.test.cxx b/build2/scheduler.test.cxx new file mode 100644 index 0000000..b088c1d --- /dev/null +++ b/build2/scheduler.test.cxx @@ -0,0 +1,187 @@ +// file : build2/scheduler.test.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <chrono> +#include <thread> + +#include <cassert> +#include <iostream> + +#include <build2/types.hxx> +#include <build2/utility.hxx> + +#include <build2/scheduler.hxx> + +using namespace std; + +namespace build2 +{ + // Usage argv[0] [-v <volume>] [-d <difficulty>] [-c <concurrency>] + // [-q <queue-depth>] + // + // -v task tree volume (affects both depth and width), for example 100 + // -d computational difficulty of each task, for example 10 + // -c max active threads, if unspecified or 0, then hardware concurrency + // -q task queue depth, if unspecified or 0, then appropriate default used + // + // Specifying any option also turns on the verbose mode. + // + // Notes on testing: + // + // 1. Ideally you would want to test things on an SMP machine. + // + // 2. When need to compare performance, disable turbo boost since its + // availability depends on CPU utilization/temperature: + // + // # echo '1' >/sys/devices/system/cpu/intel_pstate/no_turbo + // + // 3. Use turbostat(1) to see per-CPU details (utlization, frequency): + // + // $ sudo turbostat --interval 1 ./driver -d 8 -v 300 + // + static bool + prime (uint64_t); + + // Find # of primes in the [x, y) range. + // + static void + inner (uint64_t x, uint64_t y, uint64_t& r) + { + for (; x != y; ++x) + if (prime (x)) + r++; + }; + + int + main (int argc, char* argv[]) + { + bool verb (false); + + // Adjust assert() below if changing these defaults. + // + size_t volume (100); + uint32_t difficulty (10); + + size_t max_active (0); + size_t queue_depth (0); + + for (int i (1); i != argc; ++i) + { + string a (argv[i]); + + if (a == "-v") + volume = stoul (argv[++i]); + else if (a == "-d") + difficulty = stoul (argv[++i]); + else if (a == "-c") + max_active = stoul (argv[++i]); + else if (a == "-q") + queue_depth = stoul (argv[++i]); + else + assert (false); + + verb = true; + } + + if (max_active == 0) + max_active = scheduler::hardware_concurrency (); + + scheduler s (max_active, 1, 0, queue_depth); + + // Find # prime counts of primes in [i, d*i*i) ranges for i in (0, n]. + // + auto outer = [difficulty, &s] (size_t n, vector<uint64_t>& o, uint64_t& r) + { + scheduler::atomic_count task_count (0); + + for (size_t i (1); i <= n; ++i) + { + o[i - 1] = 0; + s.async (task_count, + inner, + i, + i * i * difficulty, + ref (o[i - 1])); + } + + s.wait (task_count); + assert (task_count == 0); + + for (uint64_t v: o) + r += prime (v) ? 1 : 0; + }; + + vector<uint64_t> r (volume, 0); + vector<vector<uint64_t>> o (volume, vector<uint64_t> ()); + + scheduler::atomic_count task_count (0); + + for (size_t i (0); i != volume; ++i) + { + o[i].resize (i); + s.async (task_count, + outer, + i, + ref (o[i]), + ref (r[i])); + } + + s.wait (task_count); + assert (task_count == 0); + + uint64_t n (0); + for (uint64_t v: r) + n += v; + + if (volume == 100 && difficulty == 10) + assert (n == 580); + + scheduler::stat st (s.shutdown ()); + + if (verb) + { + cerr << "result " << n << endl + << endl; + + cerr << "thread_max_active " << st.thread_max_active << endl + << "thread_max_total " << st.thread_max_total << endl + << "thread_helpers " << st.thread_helpers << endl + << "thread_max_waiting " << st.thread_max_waiting << endl + << endl + << "task_queue_depth " << st.task_queue_depth << endl + << "task_queue_full " << st.task_queue_full << endl + << endl + << "wait_queue_slots " << st.wait_queue_slots << endl + << "wait_queue_collisions " << st.wait_queue_collisions << endl; + } + + return 0; + } + + static bool + prime (uint64_t x) + { + if (x == 2 || x == 3) + return true; + + if (x < 2 || x % 2 == 0 || x % 3 == 0) + return false; + + // Test divisors starting from 5 and incrementing alternatively by 2/4. + // + for (uint64_t d (5), i (2); d * d <= x; d += i, i = 6 - i) + { + if (x % d == 0) + return false; + } + + return true; + } +} + +int +main (int argc, char* argv[]) +{ + return build2::main (argc, argv); +} 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}))); + } +} |