aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/test
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/test')
-rw-r--r--libbuild2/test/common.cxx6
-rw-r--r--libbuild2/test/init.cxx18
-rw-r--r--libbuild2/test/operation.cxx14
-rw-r--r--libbuild2/test/rule.cxx132
-rw-r--r--libbuild2/test/script/lexer.cxx8
-rw-r--r--libbuild2/test/script/lexer.hxx2
-rw-r--r--libbuild2/test/script/parser.cxx37
-rw-r--r--libbuild2/test/script/script.cxx2
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?
//