aboutsummaryrefslogtreecommitdiff
path: root/build/test/rule.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build/test/rule.cxx')
-rw-r--r--build/test/rule.cxx439
1 files changed, 0 insertions, 439 deletions
diff --git a/build/test/rule.cxx b/build/test/rule.cxx
deleted file mode 100644
index 3a4c91d..0000000
--- a/build/test/rule.cxx
+++ /dev/null
@@ -1,439 +0,0 @@
-// file : build/test/rule.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <build/test/rule>
-
-#include <butl/process>
-
-#include <build/scope>
-#include <build/target>
-#include <build/algorithm>
-#include <build/diagnostics>
-
-#include <build/config/utility> // add_options()
-
-using namespace std;
-using namespace butl;
-
-namespace build
-{
- namespace test
- {
- match_result rule::
- match (action a, target& t, const std::string&) const
- {
- // First determine if this is a test. This is controlled by
- // the test target variable and text.<tt> scope variables.
- // Also, it feels redundant to specify, say, "test = true"
- // and "test.output = test.out" -- the latter already says
- // this is a test. So take care of that as well.
- //
- bool r (false);
- lookup<const value> l;
-
- // @@ This logic doesn't take into account target type/pattern-
- // specific variables.
- //
- // @@ Perhaps a find_any(<list-of-vars>)?
- //
- for (auto p (t.vars.find_namespace ("test"));
- p.first != p.second;
- ++p.first)
- {
- const variable& var (p.first->first);
- const value& val (p.first->second);
-
- // If we have test, then always use that.
- //
- if (var.name == "test")
- {
- l = lookup<const value> (val, t);
- break;
- }
-
- // Otherwise check for variables that would indicate this
- // is a test.
- //
- if (var.name == "test.input" ||
- var.name == "test.output" ||
- var.name == "test.roundtrip" ||
- var.name == "test.options" ||
- var.name == "test.arguments")
- {
- r = true;
- break;
- }
- }
-
- if (!r)
- {
- // See if there is a scope variable.
- //
- if (!l.defined ())
- l = t.base_scope ()[
- var_pool.find (string("test.") + t.type ().name, bool_type)];
-
- r = l && as<bool> (*l);
- }
-
- // If this is the update pre-operation, then all we really need to
- // do is say we are not a match and the standard matching machinery
- // will (hopefully) find the rule to update this target.
- //
- // There is one thing that compilates this simple approach: test
- // input/output. While normally they will be existing (in src_base)
- // files, they could also be auto-generated. In fact, they could
- // only be needed for testing, which means the normall update won't
- // even know about them (nor clean, for that matter; this is why we
- // need cleantest).
- //
- // To make generated input/output work we will have to cause their
- // update ourselves. I other words, we may have to do some actual
- // work for (update, test), and not simply "guide" (update, 0) as
- // to which targets need updating. For how exactly we are going to
- // do it, see apply() below.
- //
- match_result mr (t, r);
-
- // If this is the update pre-operation, change the recipe action
- // to (update, 0) (i.e., "unconditional update").
- //
- if (r && a.operation () == update_id)
- mr.recipe_action = action (a.meta_operation (), update_id);
-
- return mr;
- }
-
- recipe rule::
- apply (action a, target& t, const match_result& mr) const
- {
- tracer trace ("test::rule::apply");
-
- if (!mr.bvalue) // Not a test.
- return noop_recipe;
-
- // Ok, if we are here, then this means:
- //
- // 1. This target is a test.
- // 2. The action is either
- // a. (perform, test, 0) or
- // b. (*, update, install)
- //
- // In both cases, the next step is to see if we have test.{input,
- // output,roundtrip}.
- //
-
- // First check the target-specific vars since they override any
- // scope ones.
- //
- auto il (t.vars["test.input"]);
- auto ol (t.vars["test.output"]);
- auto rl (t.vars["test.roundtrip"]);
- auto al (t.vars["test.arguments"]); // Should be input or arguments.
-
- if (al)
- {
- if (il)
- fail << "both test.input and test.arguments specified for "
- << "target " << t;
-
- if (rl)
- fail << "both test.roundtrip and test.arguments specified for "
- << "target " << t;
- }
-
- scope& bs (t.base_scope ());
-
- if (!il && !ol && !rl)
- {
- string n ("test.");
- n += t.type ().name;
-
- const variable& in (var_pool.find (n + ".input", name_type));
- const variable& on (var_pool.find (n + ".output", name_type));
- const variable& rn (var_pool.find (n + ".roundtrip", name_type));
-
- // We should only keep value(s) that were specified together
- // in the innermost scope.
- //
- // @@ Shouldn't we stop at project root?
- //
- for (scope* s (&bs); s != nullptr; s = s->parent_scope ())
- {
- ol = s->vars[on];
-
- if (!al) // Not overriden at target level by test.arguments?
- {
- il = s->vars[in];
- rl = s->vars[rn];
- }
-
- if (il || ol || rl)
- break;
- }
- }
-
- const name* in;
- const name* on;
-
- // Reduce the roundtrip case to input/output.
- //
- if (rl)
- {
- if (il || ol)
- fail << "both test.roundtrip and test.input/output specified "
- << "for target " << t;
-
- in = on = &as<name> (*rl);
- }
- else
- {
- in = il ? &as<name> (*il) : nullptr;
- on = ol ? &as<name> (*ol) : nullptr;
- }
-
- // Resolve them to targets, which normally would be existing files
- // but could also be targets that need updating.
- //
- target* it (in != nullptr ? &search (*in, bs) : nullptr);
- target* ot (on != nullptr ? in == on ? it : &search (*on, bs) : nullptr);
-
- if (a.operation () == update_id)
- {
- // First see if input/output are existing, up-to-date files. This
- // is a common case optimization.
- //
- if (it != nullptr)
- {
- build::match (a, *it);
-
- if (it->state () == target_state::unchanged)
- {
- unmatch (a, *it);
- it = nullptr;
- }
- }
-
- if (ot != nullptr)
- {
- if (in != on)
- {
- build::match (a, *ot);
-
- if (ot->state () == target_state::unchanged)
- {
- unmatch (a, *ot);
- ot = nullptr;
- }
- }
- else
- ot = it;
- }
-
-
- // Find the "real" update rule, that is, the rule that would
- // have been found if we signalled that we do not match from
- // match() above.
- //
- recipe d (match_delegate (a, t).first);
-
- // If we have no input/output that needs updating, then simply
- // redirect to it.
- //
- if (it == nullptr && ot == nullptr)
- return d;
-
- // Ok, time to handle the worst case scenario: we need to
- // cause update of input/output targets and also delegate
- // to the real update.
- //
- return [it, ot, dr = move (d)] (action a, target& t) -> target_state
- {
- // Do the general update first.
- //
- target_state r (execute_delegate (dr, a, t));
-
- if (it != nullptr)
- r |= execute (a, *it);
-
- if (ot != nullptr)
- r |= execute (a, *ot);
-
- return r;
- };
- }
- else
- {
- // Cache the targets in our prerequsite targets lists where they
- // can be found by perform_test(). If we have either or both,
- // then the first entry is input and the second -- output (either
- // can be NULL).
- //
- if (it != nullptr || ot != nullptr)
- {
- auto& pts (t.prerequisite_targets);
- pts.resize (2, nullptr);
- pts[0] = it;
- pts[1] = ot;
- }
-
- return &perform_test;
- }
- }
-
- static void
- add_arguments (cstrings& args, const target& t, const char* n)
- {
- string var ("test.");
- var += n;
-
- auto l (t.vars[var]);
-
- if (!l)
- {
- var.resize (5);
- var += t.type ().name;
- var += '.';
- var += n;
- l = t.base_scope ()[var_pool.find (var, strings_type)];
- }
-
- if (l)
- config::append_options (args, as<strings> (*l));
- }
-
- // The format of args shall be:
- //
- // name1 arg arg ... nullptr
- // name2 arg arg ... nullptr
- // ...
- // nameN arg arg ... nullptr nullptr
- //
- static bool
- run_test (target& t,
- diag_record& dr,
- char const** args,
- process* prev = nullptr)
- {
- // Find the next process, if any.
- //
- char const** next (args);
- for (next++; *next != nullptr; next++) ;
- next++;
-
- // Redirect stdout to a pipe unless we are last, in which
- // case redirect it to stderr.
- //
- int out (*next == nullptr ? 2 : -1);
- bool pr, wr;
-
- try
- {
- if (prev == nullptr)
- {
- // First process.
- //
- process p (args, 0, out);
- pr = *next == nullptr || run_test (t, dr, next, &p);
- wr = p.wait ();
- }
- else
- {
- // Next process.
- //
- process p (args, *prev, out);
- pr = *next == nullptr || run_test (t, dr, next, &p);
- wr = p.wait ();
- }
- }
- catch (const process_error& e)
- {
- error << "unable to execute " << args[0] << ": " << e.what ();
-
- if (e.child ())
- exit (1);
-
- throw failed ();
- }
-
- if (!wr)
- {
- if (pr) // First failure?
- dr << fail << "test " << t << " failed"; // Multi test: test 1.
-
- dr << error << "non-zero exit status: ";
- print_process (dr, args);
- }
-
- return pr && wr;
- }
-
- target_state rule::
- perform_test (action, target& t)
- {
- // @@ Would be nice to print what signal/core was dumped.
- //
- // @@ Doesn't have to be a file target if we have test.cmd.
- //
-
- file& ft (static_cast<file&> (t));
- assert (!ft.path ().empty ()); // Should have been assigned by update.
-
- cstrings args {ft.path ().string ().c_str ()};
-
- // Do we have options?
- //
- add_arguments (args, t, "options");
-
- // Do we have input?
- //
- auto& pts (t.prerequisite_targets);
- if (pts.size () != 0 && pts[0] != nullptr)
- {
- file& it (static_cast<file&> (*pts[0]));
- assert (!it.path ().empty ()); // Should have been assigned by update.
- args.push_back (it.path ().string ().c_str ());
- }
- // Maybe arguments then?
- //
- else
- add_arguments (args, t, "arguments");
-
- args.push_back (nullptr);
-
- // Do we have output?
- //
- if (pts.size () != 0 && pts[1] != nullptr)
- {
- file& ot (static_cast<file&> (*pts[1]));
- assert (!ot.path ().empty ()); // Should have been assigned by update.
-
- args.push_back ("diff");
- args.push_back ("-u");
- args.push_back (ot.path ().string ().c_str ());
- args.push_back ("-");
- args.push_back (nullptr);
- }
-
- args.push_back (nullptr); // Second.
-
- if (verb >= 2)
- print_process (args);
- else if (verb)
- text << "test " << t;
-
- {
- diag_record dr;
-
- if (!run_test (t, dr, args.data ()))
- {
- dr << info << "test command line: ";
- print_process (dr, args);
- }
- }
-
- return target_state::changed;
- }
- }
-}