From d9b26553b67e87dd45b652dd91eaac782fdf91f9 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 21 Oct 2016 17:07:18 +0200 Subject: Add support for testscript scope id, working directory --- build2/test/script/lexer.cxx | 4 +- build2/test/script/parser.cxx | 14 ++++-- build2/test/script/runner | 4 +- build2/test/script/runner.cxx | 2 +- build2/test/script/script | 50 +++++++++++++--------- build2/test/script/script.cxx | 73 +++++++++++++++++++++++++++----- doc/testscript.cli | 2 + unit-tests/test/script/lexer/driver.cxx | 4 +- unit-tests/test/script/parser/buildfile | 2 +- unit-tests/test/script/parser/driver.cxx | 14 +++--- unit-tests/test/script/parser/scope.test | 15 +++++++ 11 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 unit-tests/test/script/parser/scope.test diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx index 01c93a4..5061a1d 100644 --- a/build2/test/script/lexer.cxx +++ b/build2/test/script/lexer.cxx @@ -284,14 +284,14 @@ namespace build2 lexer_mode m (st.mode); // Customized implementation that handles special variable names ($*, - // $~, $NNN). + // $NN, $~, $@). // if (m != lexer_mode::variable) return base_lexer::word (st, sep); xchar c (peek ()); - if (c != '*' && c != '~' && !digit (c)) + if (c != '*' && c != '~' && c != '@' && !digit (c)) return base_lexer::word (st, sep); uint64_t ln (c.line), cn (c.column); diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 756761f..a3860c4 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -79,6 +79,7 @@ namespace build2 if (tt == type::eos) break; + const location ll (get_location (t)); line_type lt (pre_parse_script_line (t, tt)); assert (tt == type::newline); @@ -98,11 +99,11 @@ namespace build2 } case line_type::test: { - // Create implicit test scope. + // Create implicit test scope. Use line number as the scope id. // group_->scopes.push_back ( unique_ptr ( - (test_ = new test (*group_)))); + (test_ = new test (to_string (ll.line), *group_)))); ls = &test_->tests; @@ -740,7 +741,7 @@ namespace build2 // Now that we have all the pieces, run the command. // if (!pre_parse_) - runner_->run (c, li, ll); + runner_->run (*scope_, c, li, ll); } command_exit parser:: @@ -847,7 +848,8 @@ namespace build2 if (!qual.empty ()) fail (loc) << "qualified variable name"; - // @@ MT: will need RW mutex on var_pool. + // @@ MT: will need RW mutex on var_pool. Or maybe if it's not there + // then it can't possibly be found? Still will be setting variables. // if (name != "*" && !digits (name)) return scope_->find (script_->var_pool.insert (move (name))); @@ -861,6 +863,10 @@ namespace build2 // we don't know which $NN vars will be looked up from inside. // Could we collect all the variable names during the pre-parse // stage? They could be computed. + // + // Or we could set all the non-NULL $NN (i.e., based on the number + // of elements in $*). + // // In both cases first thing we do is lookup $*. It should always be // defined since we set it on the script's root scope. diff --git a/build2/test/script/runner b/build2/test/script/runner index 57e506f..e2cffcf 100644 --- a/build2/test/script/runner +++ b/build2/test/script/runner @@ -31,14 +31,14 @@ namespace build2 // It can be used in diagnostics. // virtual void - run (const command&, size_t index, const location&) = 0; + run (const scope&, const command&, size_t index, const location&) = 0; }; class concurrent_runner: public runner { public: virtual void - run (const command&, size_t, const location&) override; + run (const scope&, const command&, size_t, const location&) override; }; } } diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index abd1ef3..7844119 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -250,7 +250,7 @@ namespace build2 } void concurrent_runner:: - run (const command& c, size_t ci, const location& cl) + run (const scope&, const command& c, size_t ci, const location& cl) { if (verb >= 3) text << c; diff --git a/build2/test/script/script b/build2/test/script/script index 860e5d4..7fbe949 100644 --- a/build2/test/script/script +++ b/build2/test/script/script @@ -103,10 +103,21 @@ namespace build2 ostream& operator<< (ostream&, const command&); + class script; + class scope { public: - scope* parent; // NULL for the root (script) scope. + scope* const parent; // NULL for the root (script) scope. + script* const root; // Self for the root (script) scope. + + // Note that if we pass the variable name as a string, then it will + // be looked up in the wrong pool. + // + variable_map vars; + + const path& id_path; // Id path ($@, relative in POSIX form). + const dir_path& wd_path; // Working dir ($~, absolute and normalized). lines setup; lines tdown; @@ -114,11 +125,6 @@ namespace build2 // Variables. // public: - // Note that if we pass the variable name as a string, then it will - // be looked up in the wrong pool. - // - variable_map vars; - // Lookup the variable starting from this scope, continuing with outer // scopes, then the target being tested, then the testscript target, // and then outer buildfile scopes (including testscript-type/pattern @@ -148,8 +154,7 @@ namespace build2 ~scope () = default; protected: - scope (scope* p): parent (p) {} - scope (): parent (nullptr) {} // For the root (script) scope. + scope (const string& id, scope* parent); }; class group: public scope @@ -158,10 +163,10 @@ namespace build2 vector> scopes; public: - group (group& p): scope (&p) {} + group (const string& id, group& p): scope (id, &p) {} protected: - group (): scope (nullptr) {} // For the root (script) scope. + group (const string& id): scope (id, nullptr) {} // For root. }; class test: public scope @@ -170,17 +175,13 @@ namespace build2 lines tests; public: - test (group& p): scope (&p) {} + test (const string& id, group& p): scope (id, &p) {} }; - class script: public group + class script_base // Make sure certain things are initialized early. { - public: - script (target& test_target, testscript& script_target); - - public: - target& test_target; // Target we are testing. - testscript& script_target; // Target of the testscript file. + protected: + script_base (); public: variable_pool var_pool; @@ -190,7 +191,18 @@ namespace build2 const variable& args_var; // test.arguments const variable& cmd_var; // $* - const variable& cwd_var; // $~ + const variable& wd_var; // $~ + const variable& id_var; // $@ + }; + + class script: public script_base, public group + { + public: + script (target& test_target, testscript& script_target); + + public: + target& test_target; // Target we are testing. + testscript& script_target; // Target of the testscript file. }; } } diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index 5931d3f..6602518 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -102,11 +102,38 @@ namespace build2 } } - script:: - script (target& tt, testscript& st) - : test_target (tt), script_target (st), + scope:: + scope (const string& id, scope* p) + : parent (p), + root (p != nullptr ? p->root : static_cast (this)), + id_path (cast (assign (root->id_var) = path ())), + wd_path (cast (assign (root->wd_var) = dir_path ())) + + { + // Construct the id_path as a string to ensure POSIX form. In fact, + // the only reason we keep it as a path is to be able to easily get id + // by calling leaf(). + // + { + string s (p != nullptr ? p->id_path.string () : string ()); + + if (!s.empty () && !id.empty ()) + s += '/'; - // Enter the test* variables with the same variable types as in + s += id; + const_cast (id_path) = path (move (s)); + } + + // Calculate the working directory path unless this is the root scope + // (handled in an ad hoc way). + // + if (p != nullptr) + const_cast (wd_path) = dir_path (p->wd_path) /= id; + } + + script_base:: + script_base () + : // Enter the test* variables with the same variable types as in // buildfiles. // test_var (var_pool.insert ("test")), @@ -114,8 +141,38 @@ namespace build2 args_var (var_pool.insert ("test.arguments")), cmd_var (var_pool.insert ("*")), - cwd_var (var_pool.insert ("~")) + wd_var (var_pool.insert ("~")), + id_var (var_pool.insert ("@")) {} + + static inline string + script_id (const path& p) + { + string r (p.leaf ().string ()); + + if (r == "testscript") + return string (); + + size_t n (path::traits::find_extension (r)); + assert (n != string::npos); + r.resize (n); + return r; + } + + script:: + script (target& tt, testscript& st) + : group (script_id (st.path ())), + test_target (tt), script_target (st) { + // Set the script working dir ($~) to $out_base/test/ (id_path + // for root is just the id). + // + { + auto& wd (const_cast (wd_path)); + wd = tt.out_dir (); + wd /= "test"; + wd /= id_path.string (); + } + // Unless we have the test variable set on the test or script target, // set it at the script level to the test target's path. // @@ -138,12 +195,6 @@ namespace build2 // on first access. // assign (cmd_var) = nullptr; - - // Set the script CWD ($~) which is the $out_base/. - // - // @@ This will conflict for 'testscript'! - // - assign (cwd_var) = dir_path (tt.out_dir ()) /= st.name; } lookup scope:: diff --git a/doc/testscript.cli b/doc/testscript.cli index 48f9e1e..027fb64 100644 --- a/doc/testscript.cli +++ b/doc/testscript.cli @@ -399,6 +399,8 @@ EOO @@ how to preserve test output +@@ term: 'test working directory' + \h1#integration|Build System Integration| The \c{build2} \c{test} module provides the ability to run an executable diff --git a/unit-tests/test/script/lexer/driver.cxx b/unit-tests/test/script/lexer/driver.cxx index 6d9bfb7..cd7110f 100644 --- a/unit-tests/test/script/lexer/driver.cxx +++ b/unit-tests/test/script/lexer/driver.cxx @@ -19,11 +19,11 @@ namespace build2 { namespace script { + // Usage: argv[0] + // int main (int argc, char* argv[]) { - // Usage: argv[0] - // lexer_mode m; { assert (argc == 2); diff --git a/unit-tests/test/script/parser/buildfile b/unit-tests/test/script/parser/buildfile index af602b1..a6be1f6 100644 --- a/unit-tests/test/script/parser/buildfile +++ b/unit-tests/test/script/parser/buildfile @@ -11,6 +11,6 @@ filesystem config/{utility init operation} dump types-parsers \ test/{target script/{token lexer parser script}} exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs \ -test{pre-parse expansion here-document command-re-parse} +test{pre-parse expansion here-document command-re-parse scope} include ../../../../build2/ diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx index 148a081..db253eb 100644 --- a/unit-tests/test/script/parser/driver.cxx +++ b/unit-tests/test/script/parser/driver.cxx @@ -29,7 +29,7 @@ namespace build2 { public: virtual void - run (const command& t, size_t, const location&) override + run (const scope&, const command& t, size_t, const location&) override { // Here we assume we are running serially. // @@ -37,8 +37,10 @@ namespace build2 } }; + // Usage: argv[0] [] + // int - main () + main (int argc, char* argv[]) { tracer trace ("main"); @@ -47,7 +49,7 @@ namespace build2 try { - path name ("testscript"); + path name (argc > 1 ? argv[1] : "testscript"); cin.exceptions (istream::failbit | istream::badbit); // Enter mock targets. Use fixed names and paths so that we can use @@ -70,7 +72,7 @@ namespace build2 trace)); tt.path (path ("driver")); - st.path (path ("testscript")); + st.path (name); // Parse and run. // @@ -93,7 +95,7 @@ namespace build2 } int -main () +main (int argc, char* argv[]) { - return build2::test::script::main (); + return build2::test::script::main (argc, argv); } diff --git a/unit-tests/test/script/parser/scope.test b/unit-tests/test/script/parser/scope.test new file mode 100644 index 0000000..a2c6d9f --- /dev/null +++ b/unit-tests/test/script/parser/scope.test @@ -0,0 +1,15 @@ +$* testscript <'cmd $@' >"cmd 1" # id-testscript +$* foo.test <'cmd $@' >"cmd foo/1" # id + +wd = [dir_path] $build.work +wd += test +wd += 1 +$* testscript <'cmd $~' >"cmd $wd" # wd-testscript + +# @@ TMP wd1 +# +wd1 = [dir_path] $build.work +wd1 += test +wd1 += foo +wd1 += 1 +$* foo.test <'cmd $~' >"cmd $wd1" # wd -- cgit v1.1