From dd591dae0f04035f4c4697cdd0d2c58461cddf06 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 4 Nov 2019 23:33:28 +0300 Subject: Add support for testing build system modules to worker --- bbot/worker/worker.cxx | 478 ++++++++++++++++++++++++++++++------------- doc/manual.cli | 33 +++ tests/integration/testscript | 12 +- 3 files changed, 383 insertions(+), 140 deletions(-) diff --git a/bbot/worker/worker.cxx b/bbot/worker/worker.cxx index 70dbac2..6872234 100644 --- a/bbot/worker/worker.cxx +++ b/bbot/worker/worker.cxx @@ -54,6 +54,17 @@ namespace bbot } static dir_path +current_directory () +try +{ + return dir_path::current_directory (); +} +catch (const system_error& e) +{ + fail << "unable to obtain current directory: " << e << endf; +} + +static dir_path change_wd (tracer& t, string* log, const dir_path& d, bool create = false) try { @@ -68,7 +79,7 @@ try try_mkdir_p (d); } - dir_path r (dir_path::current_directory ()); + dir_path r (current_directory ()); if (verb >= 3) t << "cd " << d; @@ -312,8 +323,18 @@ build (size_t argc, const char* argv[]) operation_results {} }; + // Reserve storage large enough to hold all the potential operation results + // without reallocations. Note that this is not an optimization but is + // required to make sure that element references are not invalidated when + // new results are added. + // + const size_t max_results (6); + rm.results.reserve (max_results); + auto add_result = [&rm] (string o) -> operation_result& { + assert (rm.results.size () < max_results); + rm.results.push_back ( operation_result {move (o), result_status::success, ""}); @@ -488,14 +509,35 @@ build (size_t argc, const char* argv[]) return r; }; - // Configure. - // // Search for config.install.root variable. If it is present and has a // non-empty value, then test the package installation and uninstall. Note // that passing [null] value would be meaningless, so we don't recognize it - // as a special one. + // as a special one. While at it, cache the bpkg.configure.create args for + // later use. // dir_path install_root; + strings cargs (step_args (config_args, step_id::bpkg_configure_create)); + { + size_t n (19); + auto space = [] (char c) {return c == ' ' || c == '\t';}; + + for (const string& s: reverse_iterate (cargs)) + { + if (s.compare (0, n, "config.install.root") == 0 && + (s[n] == '=' || space (s[n]))) + { + while (space (s[n])) ++n; // Skip spaces. + if (s[n] == '=') ++n; // Skip the equal sign. + while (space (s[n])) ++n; // Skip spaces. + + // Note that the config.install.root variable value may + // potentially be quoted. + // + install_root = dir_path (unquote (string (s, n, s.size () - n))); + break; + } + } + } // bpkg-rep-fetch trust options. // @@ -517,64 +559,248 @@ build (size_t argc, const char* argv[]) trust_ops.push_back (t); } - // Configuration directory name. + const string& pkg (tm.name.string ()); + const version& ver (tm.version); + const string repo (tm.repository.string ()); + const dir_path pkg_dir (pkg + '-' + ver.string ()); + + // Specify the revision explicitly for the bpkg-build command not to end + // up with a race condition building the latest revision rather than the + // zero revision. // - dir_path build_dir ("build"); + const string pkg_rev (pkg + + '/' + + version (ver.epoch, + ver.upstream, + ver.release, + ver.effective_revision (), + ver.iteration).string ()); + + // Query the project's build system information with `b info`. + // + auto prj_info = [&trace] (const dir_path& d, const char* what) { - strings cargs (step_args (config_args, step_id::bpkg_configure_create)); + // Note that the `b info` diagnostics won't be copied into any of the + // build logs. This is fine as this is likely to be an infrastructure + // problem, given that the project distribution has been successfully + // created. It's actually not quite clear which log this diagnostics + // could go into. + // + try { - size_t n (19); - auto space = [] (char c) {return c == ' ' || c == '\t';}; - - for (const string& s: reverse_iterate (cargs)) - { - if (s.compare (0, n, "config.install.root") == 0 && - (s[n] == '=' || space (s[n]))) - { - while (space (s[n])) ++n; // Skip spaces. - if (s[n] == '=') ++n; // Skip the equal sign. - while (space (s[n])) ++n; // Skip spaces. + return b_info (d, verb, trace); + } + catch (const b_error& e) + { + if (e.normal ()) + throw failed (); // Assume the build2 process issued diagnostics. - // Note that the config.install.root variable value may - // potentially be quoted. - // - install_root = dir_path (unquote (string (s, n, s.size () - n))); - break; - } - } + fail << "unable to query " << what << ' ' << d << " info: " << e + << endf; } + }; + + b_project_info prj; // Package project information. - operation_result& r (add_result ("configure")); + // If this is a build system module, perform a "pre-step" by building it + // in a separate configuration reproducing the one used to build build2 + // itself. Note that the configuration and the environment options and + // variables are not passed to commands that may affect this + // configuration. + // + bool module (pkg.compare (0, 10, "libbuild2-") == 0); + dir_path module_dir ("build-module"); + + // Note that we don't consider the module configuring and testing during + // this pre-step separate operations and share the operation logs with the + // "main" configure and test steps (see below). Thus, we save pointers to + // the added result objects for the subsequent use. + // + operation_result* configure_result (nullptr); + operation_result* test_result (nullptr); - // Noop, just for the log record. + rwd = current_directory (); + + if (module) + { + // Configure. // - try { - change_wd (trace, &r.log, dir_path::current_directory ()); + operation_result& r (add_result ("configure")); + configure_result = &r; + + // Noop, just for the log record. + // + change_wd (trace, &r.log, rwd); + + // b create() config.import=~host + // + r.status |= run_b ( + trace, r.log, wre, + "-V", + "create(" + module_dir.representation () + ",cc)", + "config.import=~host"); + + if (!r.status) + break; + + change_wd (trace, &r.log, module_dir); + + // bpkg create --existing + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "create", + "--existing"); + + if (!r.status) + break; + + // bpkg add + // + // bpkg.configure.add + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "add", + step_args (env_args, step_id::bpkg_configure_add), + step_args (config_args, step_id::bpkg_configure_add), + repo); + + if (!r.status) + break; + + // bpkg fetch + // + // bpkg.configure.fetch + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "fetch", + step_args (env_args, step_id::bpkg_configure_fetch), + step_args (config_args, step_id::bpkg_configure_fetch), + trust_ops); + + if (!r.status) + break; + + // bpkg build --configure-only / + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "build", + "--configure-only", + "--yes", + pkg_rev); + + if (!r.status) + break; + + rm.status |= r.status; } - catch (const system_error& e) + + // Update. + // { - fail << "unable to obtain current directory: " << e; + operation_result& r (add_result ("update")); + + // Noop, just for the log record to reduce the potential confusion for + // the combined log reader due to the configure operation log sharing + // (see above for details). + // + change_wd (trace, &r.log, current_directory ()); + + // bpkg update + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "update", + pkg); + + if (!r.status) + break; + + rm.status |= r.status; } + // Run the package internal tests if the test operation is supported by + // the project. + // + prj = prj_info (pkg_dir, "project"); + + if (find (prj.operations.begin (), prj.operations.end (), "test") != + prj.operations.end ()) + { + operation_result& r (add_result ("test")); + test_result = &r; + + // Use --package-cwd to help ported to build2 third-party packages a + // bit (see bpkg-pkg-test(1) for details). + // + // bpkg test + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "test", + "--package-cwd", + pkg); + + if (!r.status) + break; + + rm.status |= r.status; + } + } + + // The "main" step. + // + + // Configure. + // + dir_path build_dir ("build"); // Configuration directory name. + dir_path pkg_config (rwd / (module ? module_dir : build_dir)); + { + operation_result& r (configure_result != nullptr + ? *configure_result + : add_result ("configure")); + + change_wd (trace, &r.log, rwd); + // bpkg create // // bpkg.configure.create // - r.status |= run_bpkg ( - trace, r.log, wre, - "-V", - "create", - "-d", build_dir.string (), - "--wipe", - step_args (modules, step_id::bpkg_configure_create), - step_args (env_args, step_id::bpkg_configure_create), - cargs); + { + // If the package is a build system module, then make sure it is + // importable in this configuration. + // + string import (module + ? ("config.import." + tm.name.variable () + "=" + + module_dir.string ()) + : ""); - if (!r.status) - break; + r.status |= run_bpkg ( + trace, r.log, wre, + "-V", + "create", + "-d", build_dir.string (), + "--wipe", + step_args (modules, step_id::bpkg_configure_create), + step_args (env_args, step_id::bpkg_configure_create), + cargs, + import.empty () ? nullptr : import.c_str ()); + + if (!r.status) + break; + } - rwd = change_wd (trace, &r.log, build_dir); + change_wd (trace, &r.log, build_dir); // bpkg add // @@ -586,7 +812,7 @@ build (size_t argc, const char* argv[]) "add", step_args (env_args, step_id::bpkg_configure_add), step_args (config_args, step_id::bpkg_configure_add), - tm.repository.string ()); + repo); if (!r.status) break; @@ -611,34 +837,29 @@ build (size_t argc, const char* argv[]) // // bpkg.configure.build // - // Specify the revision explicitly not to end up with a race condition - // building the latest revision rather than the zero revision. - // - version v (tm.version.epoch, - tm.version.upstream, - tm.version.release, - tm.version.effective_revision (), - tm.version.iteration); - - r.status |= run_bpkg ( - trace, r.log, wre, - "-v", - "build", - "--configure-only", - "--yes", - step_args (env_args, step_id::bpkg_configure_build), - step_args (config_args, step_id::bpkg_configure_build), - "--", - tm.name.string () + '/' + v.string ()); + if (!module) // Note: the module is already built in the pre-step. + { + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "build", + "--configure-only", + "--yes", + step_args (env_args, step_id::bpkg_configure_build), + step_args (config_args, step_id::bpkg_configure_build), + "--", + pkg_rev); - if (!r.status) - break; + if (!r.status) + break; + } rm.status |= r.status; } // Update. // + if (!module) // Note: the module is already built in the pre-step. { operation_result& r (add_result ("update")); @@ -652,7 +873,7 @@ build (size_t argc, const char* argv[]) "update", step_args (env_args, step_id::bpkg_update_update), step_args (config_args, step_id::bpkg_update_update), - tm.name.string ()); + pkg); if (!r.status) break; @@ -660,43 +881,30 @@ build (size_t argc, const char* argv[]) rm.status |= r.status; } - // Query the package's build system information with `b info`. - // - dir_path prj_dir (tm.name.string () + '-' + tm.version.string ()); - b_project_info prj; - - // Note that `b info` diagnostics will not be copied into any of the build - // logs. This seems to be fine, as this is likely to be an infrastructural - // problem, given that the project distribution has been successfully - // created. It's actually not quite clear which log this diagnostics could - // go into. + // Run the package internal tests if the test operation is supported by + // the project, except for the build system module which is taken care of + // in the pre-step. // - try - { - prj = b_info (prj_dir, verb, trace); - } - catch (const b_error& e) + bool internal_tests; + + if (module) + internal_tests = false; + else { - if (e.normal ()) - throw failed (); // Assume the build2 process issued diagnostics. + prj = prj_info (pkg_dir, "project"); - fail << "unable to query project " << prj_dir << " info: " << e; + internal_tests = find (prj.operations.begin (), + prj.operations.end (), + "test") != prj.operations.end (); } - // Run the package internal tests if the test operation is supported by - // the project. - // - bool internal_tests (find (prj.operations.begin (), - prj.operations.end (), - "test") != prj.operations.end ()); - // Parse the package manifest. // // Note that being unable to parse the package manifest is likely to be an - // infrastructural problem, given that the package has been successfully + // infrastructure problem, given that the package has been successfully // configured. // - path mf (prj_dir / "manifest"); + path mf (pkg_config / pkg_dir / "manifest"); package_manifest pm (parse_manifest (mf, "package")); // Run the package external tests if any of the tests, examples, or @@ -704,7 +912,8 @@ build (size_t argc, const char* argv[]) // // Note that we assume that these packages belong to the dependent // package's repository or its complement repositories, recursively. Thus, - // we test them in the configuration used to build the dependent package. + // we test them in the configuration used to build the dependent package + // (except for the build system module). // bool external_tests (!pm.tests.empty () || !pm.examples.empty () || @@ -768,9 +977,6 @@ build (size_t argc, const char* argv[]) // Note that we assume that the package supports the test operation // since this is its main purpose. // - // Use --package-cwd to help ported to build2 third-party packages a - // bit (see bpkg-pkg-test(1) for details). - // // bpkg test // // bpkg.test.test @@ -780,7 +986,7 @@ build (size_t argc, const char* argv[]) trace, r.log, wre, "-v", "test", - "--package-cwd", + "--package-cwd", // See above for details. step_args (env_args, step_id::bpkg_test_test), step_args (config_args, step_id::bpkg_test_test), pkg); @@ -794,7 +1000,9 @@ build (size_t argc, const char* argv[]) if (internal_tests || external_tests) { - operation_result& r (add_result ("test")); + operation_result& r (test_result != nullptr + ? *test_result + : add_result ("test")); // Run internal tests. // @@ -811,7 +1019,7 @@ build (size_t argc, const char* argv[]) "--package-cwd", // See above for details. step_args (env_args, step_id::bpkg_test_test), step_args (config_args, step_id::bpkg_test_test), - tm.name.string ()); + pkg); if (!r.status) break; @@ -859,6 +1067,8 @@ build (size_t argc, const char* argv[]) { operation_result& r (add_result ("install")); + change_wd (trace, &r.log, pkg_config); + // bpkg install // // bpkg.install.install @@ -869,7 +1079,7 @@ build (size_t argc, const char* argv[]) "install", step_args (env_args, step_id::bpkg_install_install), step_args (config_args, step_id::bpkg_install_install), - tm.name.string ()); + pkg); if (!r.status) break; @@ -880,9 +1090,19 @@ build (size_t argc, const char* argv[]) // Test installed. // // Make sure that the installed package executables are properly imported - // when configuring and running tests. + // when configuring and running tests, unless we are testing the build + // system module (that supposedly doesn't install any executables). // small_vector envvars; + + dir_paths subprj_dirs; // "Testable" package subprojects. + + // We expect the build system modules to not have any testable subprojects + // but to have external tests package instead. + // + if (module) + internal_tests = false; + else { // Note that we add the $config.install.root/bin directory at the // beginning of the PATH environment variable value, so the installed @@ -897,43 +1117,27 @@ build (size_t argc, const char* argv[]) } envvars.push_back (move (paths)); - } - - // Collect the "testable" subprojects. - // - dir_paths subprj_dirs; - for (const b_project_info::subproject& sp: prj.subprojects) - { - // Retrieve the subproject information similar to how we've done it for - // the package. - // - dir_path sd (prj_dir / sp.path); - // Note that `b info` diagnostics will not be logged (see above for - // details). + // Collect the "testable" subprojects. // - try + for (const b_project_info::subproject& sp: prj.subprojects) { - b_project_info si (b_info (sd, verb, trace)); + // Retrieve the subproject information similar to how we've done it + // for the package. + // + b_project_info si (prj_info (pkg_dir / sp.path, "subproject")); const strings& ops (si.operations); if (find (ops.begin (), ops.end (), "test") != ops.end ()) subprj_dirs.push_back (sp.path); } - catch (const b_error& e) - { - if (e.normal ()) - throw failed (); // Assume the build2 process issued diagnostics. - fail << "unable to query subproject " << sd << " info: " << e; - } + // If there are any "testable" subprojects, then configure them + // (sequentially) and test/build in parallel afterwards. + // + internal_tests = !subprj_dirs.empty (); } - // If there are any "testable" subprojects, then configure them - // (sequentially) and test/build in parallel afterwards. - // - internal_tests = !subprj_dirs.empty (); - if (internal_tests || external_tests) { operation_result& r (add_result ("test-installed")); @@ -946,8 +1150,8 @@ build (size_t argc, const char* argv[]) { string mods; // build2 create meta-operation parameters. - for (const string& m: - step_args (modules, step_id::b_test_installed_create)) + for (const string& m: step_args (modules, + step_id::b_test_installed_create)) { mods += mods.empty () ? ", " : " "; mods += m; @@ -982,7 +1186,7 @@ build (size_t argc, const char* argv[]) // // b.test-installed.configure // - dir_path subprj_src_dir (build_dir / prj_dir / d); + dir_path subprj_src_dir (build_dir / pkg_dir / d); dir_path subprj_out_dir (out_dir / d); r.status |= run_b ( @@ -1048,7 +1252,7 @@ build (size_t argc, const char* argv[]) if (!r.status) break; - dir_path owd (change_wd (trace, &r.log, config_dir)); + change_wd (trace, &r.log, config_dir); // bpkg add // @@ -1060,7 +1264,7 @@ build (size_t argc, const char* argv[]) "add", step_args (env_args, step_id::bpkg_configure_add), step_args (config_args, step_id::bpkg_configure_add), - tm.repository.string ()); + repo); if (!r.status) break; @@ -1086,12 +1290,8 @@ build (size_t argc, const char* argv[]) !test (pm.examples, r, envvars) || !test (pm.benchmarks, r, envvars)) break; - - change_wd (trace, &r.log, owd); } - change_wd (trace, &r.log, build_dir); - rm.status |= r.status; } @@ -1100,6 +1300,8 @@ build (size_t argc, const char* argv[]) { operation_result& r (add_result ("uninstall")); + change_wd (trace, &r.log, pkg_config); + // bpkg uninstall // // bpkg.uninstall.uninstall @@ -1110,7 +1312,7 @@ build (size_t argc, const char* argv[]) "uninstall", step_args (env_args, step_id::bpkg_uninstall_uninstall), step_args (config_args, step_id::bpkg_uninstall_uninstall), - tm.name.string ()); + pkg); if (!r.status) break; diff --git a/doc/manual.cli b/doc/manual.cli index 66c2b02..43a2709 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -881,6 +881,39 @@ bpkg -v test For details on configuring and testing installation refer to \l{#arch-controller Controller Logic}. +If the package is a build system module, then it is built and tested (using +the bundled tests) in a separate configuration that mimics the one used to +build \c{build2} itself. Note that the configuration and environment options +and variables are not passed to commands that may affect this configuration. +Such commands, therefore, have no associated \i{step id}: + +\ +# +# +b -V create config.import=~host +bpkg -v create --existing + +# bpkg.configure.add +# +bpkg -v add + +# bpkg.configure.fetch +# +bpkg -v fetch --trust + +# +# +bpkg -v build --yes --configure-only / + +# +# +bpkg -v update + +# if the test operation is supported by the package: +# +bpkg -v test +\ + As an example, the following POSIX shell script can be used to setup the environment for building C and C++ packages with GCC 9 on most Linux distributions. diff --git a/tests/integration/testscript b/tests/integration/testscript index d06b931..5475b12 100644 --- a/tests/integration/testscript +++ b/tests/integration/testscript @@ -73,6 +73,14 @@ rep_type = git rfp = yes #\ +#\ +pkg = libbuild2-hello +ver = 0.1.0-a.0.20191106105744.1ad196b43c42 +rep_url = "https://github.com/build2/libbuild2-hello.git#master" +rep_type = git +rfp = yes +#\ + # Note that we also need to make sure that the installed package libraries are # properly imported when configuring and running tests, and that the installed # executables are runnable. @@ -137,6 +145,6 @@ a = $0 chmod ugo+x $env; sleep $wait; $w --verbose 3 --startup --tftp-host $tftp --environments $~ \ - &build/*** &?build-installed/*** &?build-installed-bpkg/*** \ - &task.manifest 2>| + &?build-module/*** &build/*** &?build-installed/*** \ + &?build-installed-bpkg/*** &task.manifest 2>| } -- cgit v1.1