diff options
Diffstat (limited to 'libbuild2/test/init.cxx')
-rw-r--r-- | libbuild2/test/init.cxx | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/libbuild2/test/init.cxx b/libbuild2/test/init.cxx new file mode 100644 index 0000000..3d13acc --- /dev/null +++ b/libbuild2/test/init.cxx @@ -0,0 +1,231 @@ +// file : libbuild2/test/init.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/test/init.hxx> + +#include <libbuild2/scope.hxx> +#include <libbuild2/target.hxx> +#include <libbuild2/rule.hxx> +#include <libbuild2/diagnostics.hxx> + +#include <libbuild2/config/utility.hxx> + +#include <libbuild2/test/module.hxx> +#include <libbuild2/test/target.hxx> +#include <libbuild2/test/operation.hxx> + +#include <libbuild2/test/script/regex.hxx> // script::regex::init() + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace test + { + bool + boot (scope& rs, const location&, unique_ptr<module_base>& mod) + { + tracer trace ("test::boot"); + + l5 ([&]{trace << "for " << rs;}); + + // Register our operations. + // + rs.insert_operation (test_id, op_test); + rs.insert_operation (update_for_test_id, op_update_for_test); + + // Enter module variables. Do it during boot in case they get assigned + // in bootstrap.build. + // + auto& vp (var_pool.rw (rs)); + + common_data d { + + // Tests to execute. + // + // Specified as <target>@<path-id> pairs with both sides being + // optional. The variable is untyped (we want a list of name-pairs), + // overridable, and inheritable. The target is relative (in essence a + // prerequisite) which is resolved from the (root) scope where the + // config.test value is defined. + // + vp.insert ("config.test", true), + + // Test working directory before/after cleanup (see Testscript spec + // for semantics). + // + vp.insert<name_pair> ("config.test.output", true), + + // The test variable is a name which can be a path (with the + // true/false special values) or a target name. + // + // Note: none are overridable. + // + vp.insert<name> ("test", variable_visibility::target), + vp.insert<strings> ("test.options", variable_visibility::project), + vp.insert<strings> ("test.arguments", variable_visibility::project), + + // Prerequisite-specific. + // + // test.stdin and test.stdout can be used to mark a prerequisite as a + // file to redirect stdin from and to compare stdout to, respectively. + // test.roundtrip is a shortcut to mark a prerequisite as both stdin + // and stdout. + // + // Prerequisites marked with test.input are treated as additional test + // inputs: they are made sure to be up to date and their paths are + // passed as additional command line arguments (after test.options and + // test.arguments). Their primary use is to pass inputs that may have + // varying file names/paths, for example: + // + // exe{parent}: exe{child}: test.input = true + // + // Note that currently this mechanism is only available to simple + // tests though we could also support it for testscript (e.g., by + // appending the input paths to test.arguments or by passing them in a + // separate test.inputs variable). + // + vp.insert<bool> ("test.stdin", variable_visibility::prereq), + vp.insert<bool> ("test.stdout", variable_visibility::prereq), + vp.insert<bool> ("test.roundtrip", variable_visibility::prereq), + vp.insert<bool> ("test.input", variable_visibility::prereq), + + // Test target platform. + // + vp.insert<target_triplet> ("test.target", variable_visibility::project) + }; + + // These are only used in testscript. + // + vp.insert<strings> ("test.redirects", variable_visibility::project); + vp.insert<strings> ("test.cleanups", variable_visibility::project); + + // Unless already set, default test.target to build.host. Note that it + // can still be overriden by the user, e.g., in root.build. + // + { + value& v (rs.assign (d.test_target)); + + if (!v || v.empty ()) + v = cast<target_triplet> ((*global_scope)["build.host"]); + } + + mod.reset (new module (move (d))); + return false; + } + + bool + init (scope& rs, + scope&, + const location& l, + unique_ptr<module_base>& mod, + bool first, + bool, + const variable_map& config_hints) + { + tracer trace ("test::init"); + + if (!first) + { + warn (l) << "multiple test module initializations"; + return true; + } + + const dir_path& out_root (rs.out_path ()); + l5 ([&]{trace << "for " << out_root;}); + + assert (mod != nullptr); + module& m (static_cast<module&> (*mod)); + + // Configure. + // + assert (config_hints.empty ()); // We don't known any hints. + + // Adjust module priority so that the config.test.* values are saved at + // the end of config.build. + // + config::save_module (rs, "test", INT32_MAX); + + // config.test + // + if (lookup l = config::omitted (rs, m.config_test).first) + { + // Figure out which root scope it came from. + // + scope* s (&rs); + for (; + s != nullptr && !l.belongs (*s); + s = s->parent_scope ()->root_scope ()) + assert (s != nullptr); + + m.test_ = &cast<names> (l); + m.root_ = s; + } + + // config.test.output + // + if (lookup l = config::omitted (rs, m.config_test_output).first) + { + const name_pair& p (cast<name_pair> (l)); + + // If second half is empty, then first is the after value. + // + const name& a (p.second.empty () ? p.first : p.second); // after + const name& b (p.second.empty () ? p.second : p.first); // before + + // Parse and validate. + // + if (!b.simple ()) + fail << "invalid config.test.output before value '" << b << "'"; + + if (!a.simple ()) + fail << "invalid config.test.output after value '" << a << "'"; + + if (a.value == "clean") m.after = output_after::clean; + else if (a.value == "keep") m.after = output_after::keep; + else fail << "invalid config.test.output after value '" << a << "'"; + + if (b.value == "fail") m.before = output_before::fail; + else if (b.value == "warn") m.before = output_before::warn; + else if (b.value == "clean") m.before = output_before::clean; + else if (b.value == "") m.before = output_before::clean; + else fail << "invalid config.test.output before value '" << b << "'"; + } + + //@@ TODO: Need ability to specify extra diff options (e.g., + // --strip-trailing-cr, now hardcoded). + // + //@@ TODO: Pring report. + + // Register target types. + // + { + auto& t (rs.target_types); + + auto& tt (t.insert<testscript> ()); + t.insert_file ("testscript", tt); + } + + // Register our test running rule. + // + { + default_rule& dr (m); + + rs.rules.insert<target> (perform_test_id, "test", dr); + rs.rules.insert<alias> (perform_test_id, "test", dr); + } + + return true; + } + + module_functions + build2_test_load () + { + script::regex::init (); + + return module_functions {&boot, &init}; + } + } +} |