diff options
Diffstat (limited to 'libbuild2/test')
-rw-r--r-- | libbuild2/test/common.cxx | 6 | ||||
-rw-r--r-- | libbuild2/test/init.cxx | 18 | ||||
-rw-r--r-- | libbuild2/test/operation.cxx | 14 | ||||
-rw-r--r-- | libbuild2/test/rule.cxx | 132 | ||||
-rw-r--r-- | libbuild2/test/script/lexer.cxx | 8 | ||||
-rw-r--r-- | libbuild2/test/script/lexer.hxx | 2 | ||||
-rw-r--r-- | libbuild2/test/script/parser.cxx | 37 | ||||
-rw-r--r-- | libbuild2/test/script/script.cxx | 2 |
8 files changed, 150 insertions, 69 deletions
diff --git a/libbuild2/test/common.cxx b/libbuild2/test/common.cxx index 7fdb347..89f3dd6 100644 --- a/libbuild2/test/common.cxx +++ b/libbuild2/test/common.cxx @@ -150,8 +150,7 @@ namespace build2 t.name == n->value && // Name matches. tt.name == n->type && // Target type matches. d == n->dir && // Directory matches. - (search_existing (*n, *root_) == &t || - search_existing (*n, *root_, d) == &t); + search_existing (*n, *root_) == &t; if (r) break; @@ -198,8 +197,7 @@ namespace build2 t.name == n->value && tt.name == n->type && d == n->dir && - (search_existing (*n, *root_) == &t || - search_existing (*n, *root_, d) == &t); + search_existing (*n, *root_) == &t; if (!r) continue; // Not our target. diff --git a/libbuild2/test/init.cxx b/libbuild2/test/init.cxx index b7cf25f..32548f4 100644 --- a/libbuild2/test/init.cxx +++ b/libbuild2/test/init.cxx @@ -23,6 +23,8 @@ namespace build2 { namespace test { + static const file_rule file_rule_ (true /* check_type */); + void boot (scope& rs, const location&, module_boot_extra& extra) { @@ -300,18 +302,18 @@ namespace build2 { default_rule& dr (m); - // Note: register for mtime_target to take priority over the fallback - // rule below. - // - rs.insert_rule<target> (perform_test_id, "test", dr); - rs.insert_rule<mtime_target> (perform_test_id, "test", dr); - rs.insert_rule<alias> (perform_test_id, "test", dr); + rs.insert_rule<target> (perform_test_id, "test", dr); + rs.insert_rule<alias> (perform_test_id, "test", dr); // Register the fallback file rule for the update-for-test operation, // similar to update. // - rs.global_scope ().insert_rule<mtime_target> ( - perform_test_id, "test.file", file_rule::instance); + // Note: use target instead of anything more specific (such as + // mtime_target) in order not to take precedence over the "test" rule + // above. + // + rs.global_scope ().insert_rule<target> ( + perform_test_id, "test.file", file_rule_); } return true; diff --git a/libbuild2/test/operation.cxx b/libbuild2/test/operation.cxx index 841abb5..2535adb 100644 --- a/libbuild2/test/operation.cxx +++ b/libbuild2/test/operation.cxx @@ -17,14 +17,8 @@ namespace build2 namespace test { static operation_id - test_pre (context&, - const values& params, - meta_operation_id mo, - const location& l) + pre_test (context&, const values&, meta_operation_id mo, const location&) { - if (!params.empty ()) - fail (l) << "unexpected parameters for operation test"; - // Run update as a pre-operation, unless we are disfiguring. // return mo != disfigure_id ? update_id : 0; @@ -70,7 +64,9 @@ namespace build2 "has nothing to test", // We cannot "be tested". execution_mode::first, 1 /* concurrency */, - &test_pre, + &pre_test, + nullptr, + nullptr, nullptr, nullptr, &adhoc_apply @@ -90,6 +86,8 @@ namespace build2 op_update.concurrency, op_update.pre_operation, op_update.post_operation, + op_update.operation_pre, + op_update.operation_post, op_update.adhoc_match, op_update.adhoc_apply }; diff --git a/libbuild2/test/rule.cxx b/libbuild2/test/rule.cxx index 0ee7641..28eb35b 100644 --- a/libbuild2/test/rule.cxx +++ b/libbuild2/test/rule.cxx @@ -563,22 +563,22 @@ namespace build2 { scope_state& r (res.back ()); - if (!ctx.sched.async (ctx.count_busy (), - t[a].task_count, - [this] (const diag_frame* ds, - scope_state& r, - const target& t, - const testscript& ts, - const dir_path& wd) - { - diag_frame::stack_guard dsg (ds); - r = perform_script_impl (t, ts, wd, *this); - }, - diag_frame::stack (), - ref (r), - cref (t), - cref (ts), - cref (wd))) + if (!ctx.sched->async (ctx.count_busy (), + t[a].task_count, + [this] (const diag_frame* ds, + scope_state& r, + const target& t, + const testscript& ts, + const dir_path& wd) + { + diag_frame::stack_guard dsg (ds); + r = perform_script_impl (t, ts, wd, *this); + }, + diag_frame::stack (), + ref (r), + cref (t), + cref (ts), + cref (wd))) { // Executed synchronously. If failed and we were not asked to // keep going, bail out. @@ -668,6 +668,16 @@ namespace build2 // bool terminated = false; + // True if this process has been terminated but we failed to read out + // its stderr stream in the reasonable timeframe (2 seconds) after the + // termination. + // + // Note that this may happen if there is a still running child process + // of the terminated process which has inherited the parent's stderr + // file descriptor. + // + bool unread_stderr = false; + pipe_process* prev; // NULL for the left-most program. pipe_process* next; // Left-most program for the right-most program. @@ -780,10 +790,14 @@ namespace build2 // Read out all the pipeline's buffered strerr streams watching for // the deadline, if specified. If the deadline is reached, then - // terminate the whole pipeline, reset the deadline to nullopt, and - // continue reading. Note that the further reading will be performed - // without timeout. This, however, is fine since all the processes are - // terminated and we only need to read out the buffered data. + // terminate the whole pipeline, move the deadline by another 2 + // seconds, and continue reading. + // + // Note that we assume that this timeout increment is normally + // sufficient to read out the buffered data written by the already + // terminated processes. If, however, that's not the case (see + // pipe_process for the possible reasons), then we just set + // unread_stderr flag to true for such processes and bail out. // // Also note that this implementation is inspired by the // script::run_pipe::read_pipe() lambda. @@ -800,6 +814,7 @@ namespace build2 } optional<timestamp> dl (deadline); + bool terminated (false); for (size_t unread (fds.size ()); unread != 0;) { @@ -814,9 +829,38 @@ namespace build2 if (*dl <= now || ifdselect (fds, *dl - now) == 0) { - term_pipe (&pp); - dl = nullopt; - continue; + if (!terminated) + { + term_pipe (&pp); + terminated = true; + + dl = system_clock::now () + chrono::seconds (2); + continue; + } + else + { + for (fdselect_state& s: fds) + { + if (s.fd != nullfd) + { + pipe_process* p (static_cast<pipe_process*> (s.data)); + + p->unread_stderr = true; + + // Let's also close the stderr stream not to confuse + // diag_buffer::close() (see script::read() for + // details). + // + try + { + p->dbuf.is.close (); + } + catch (const io_error&) {} + } + } + + break; + } } } else @@ -869,9 +913,9 @@ namespace build2 // Iterate over the pipeline processes left to right, printing their // stderr if buffered and issuing the diagnostics if the exit code is - // not available (terminated abnormally or due to a deadline) or is - // non-zero. Afterwards, fail if any of the processes didn't terminate - // normally with zero code. + // not available (terminated abnormally or due to a deadline), is + // non-zero, or stderr was not fully read. Afterwards, fail if any of + // such a faulty processes were encountered. // // Note that we only issue diagnostics for the first failure. // @@ -920,18 +964,44 @@ namespace build2 { diag_record dr; - if (!pe || !pe->normal () || pe->code () != 0) + // Note that there can be a race, so that the process we have + // terminated due to reaching the deadline has in fact exited + // normally. Thus, the 'unread stderr' situation can also happen + // to a successfully terminated process. If that's the case, we + // report this problem as the main error and the secondary error + // otherwise. + // + if (!pe || + !pe->normal () || + pe->code () != 0 || + p->unread_stderr) { fail = true; dr << error << "test " << t << " failed" // Multi test: test 1. << error << "process " << p->args[0] << ' '; - if (pe) - dr << *pe; - else + if (!pe) + { dr << "terminated: execution timeout expired"; + if (p->unread_stderr) + dr << error << "stderr not closed after exit"; + } + else if (!pe->normal () || pe->code () != 0) + { + dr << *pe; + + if (p->unread_stderr) + dr << error << "stderr not closed after exit"; + } + else + { + assert (p->unread_stderr); + + dr << "stderr not closed after exit"; + } + if (verb == 1) { dr << info << "test command line: "; @@ -1091,7 +1161,7 @@ namespace build2 fail << "invalid test executable override: '" << *n << "'"; else { - // Must be a target name. + // Must be a target name. Could be from src (e.g., a script). // // @@ OUT: what if this is a @-qualified pair of names? // diff --git a/libbuild2/test/script/lexer.cxx b/libbuild2/test/script/lexer.cxx index b470d25..aec91fc 100644 --- a/libbuild2/test/script/lexer.cxx +++ b/libbuild2/test/script/lexer.cxx @@ -339,15 +339,17 @@ namespace build2 } token lexer:: - word (state st, bool sep) + word (const state& st, bool sep) { - lexer_mode m (st.mode); + lexer_mode m (st.mode); // Save. token r (base_lexer::word (st, sep)); if (m == lexer_mode::variable) { - if (r.value.size () == 1 && digit (r.value[0])) // $N + if (r.type == type::word && + r.value.size () == 1 && + digit (r.value[0])) // $N { xchar c (peek ()); diff --git a/libbuild2/test/script/lexer.hxx b/libbuild2/test/script/lexer.hxx index 993a9db..39b950a 100644 --- a/libbuild2/test/script/lexer.hxx +++ b/libbuild2/test/script/lexer.hxx @@ -77,7 +77,7 @@ namespace build2 next_description (); virtual token - word (state, bool) override; + word (const state&, bool) override; }; } } diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index 60656a1..337b162 100644 --- a/libbuild2/test/script/parser.cxx +++ b/libbuild2/test/script/parser.cxx @@ -1609,6 +1609,17 @@ namespace build2 { runner_->enter (*scope_, scope_->start_loc_); + // Set thread-specific current directory override. In particular, this + // makes sure functions like $path.complete() work correctly. + // + auto wdg = make_guard ( + [old = path_traits::thread_current_directory ()] () + { + path_traits::thread_current_directory (old); + }); + + path_traits::thread_current_directory (&scope_->work_dir.path->string ()); + // Note that we rely on "small function object" optimization for the // exec_*() lambdas. // @@ -1831,19 +1842,19 @@ namespace build2 // UBSan workaround. // const diag_frame* df (diag_frame::stack ()); - if (!ctx->sched.async (task_count, - [] (const diag_frame* ds, - scope& s, - script& scr, - runner& r) - { - diag_frame::stack_guard dsg (ds); - execute_impl (s, scr, r); - }, - df, - ref (*chain), - ref (*script_), - ref (*runner_))) + if (!ctx->sched->async (task_count, + [] (const diag_frame* ds, + scope& s, + script& scr, + runner& r) + { + diag_frame::stack_guard dsg (ds); + execute_impl (s, scr, r); + }, + df, + ref (*chain), + ref (*script_), + ref (*runner_))) { // Bail out if the scope has failed and we weren't instructed // to keep going. diff --git a/libbuild2/test/script/script.cxx b/libbuild2/test/script/script.cxx index 05dc7b0..f7827f6 100644 --- a/libbuild2/test/script/script.cxx +++ b/libbuild2/test/script/script.cxx @@ -268,7 +268,7 @@ namespace build2 v = path (n->dir); else { - // Must be a target name. + // Must be a target name. Could be from src (e.g., a script). // // @@ OUT: what if this is a @-qualified pair of names? // |