From adc8527ff2a32f71e7b93938aa22ec5fde38114a Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 23 Oct 2019 22:55:57 +0300 Subject: Add support for tests, examples, and benchmarks package manifest values --- bbot/worker/worker.cxx | 602 +++++++++++++++++++++++++++++++++---------- doc/manual.cli | 74 +++++- tests/integration/testscript | 15 +- 3 files changed, 544 insertions(+), 147 deletions(-) diff --git a/bbot/worker/worker.cxx b/bbot/worker/worker.cxx index d263a5c..d97dff0 100644 --- a/bbot/worker/worker.cxx +++ b/bbot/worker/worker.cxx @@ -118,7 +118,7 @@ run_cmd (tracer& t, process pr ( process_start_callback (cmdc, - fdnull (), // Never reads from stdout. + fdnull (), // Never reads from stdin. 2, // 1>&2 pipe, pe, @@ -195,9 +195,10 @@ run_cmd (tracer& t, } } -template +template static result_status -run_bpkg (tracer& t, +run_bpkg (const V& envvars, + tracer& t, string& log, const regexes& warn_detect, const char* verbosity, const string& cmd, A&&... a) @@ -205,14 +206,30 @@ run_bpkg (tracer& t, return run_cmd (t, log, warn_detect, "bpkg " + cmd, - "bpkg", verbosity, cmd, forward (a)...); + process_env ("bpkg", envvars), + verbosity, cmd, forward (a)...); +} + +template +static result_status +run_bpkg (tracer& t, + string& log, const regexes& warn_detect, + const char* verbosity, + const string& cmd, A&&... a) +{ + const char* const* envvars (nullptr); + + return run_bpkg (envvars, + t, + log, warn_detect, + verbosity, cmd, forward (a)...); } template static result_status -run_b (tracer& t, +run_b (const V& envvars, + tracer& t, string& log, const regexes& warn_detect, - const V& envvars, const char* verbosity, const strings& buildspecs, A&&... a) { @@ -234,9 +251,9 @@ run_b (tracer& t, template static result_status -run_b (tracer& t, +run_b (const V& envvars, + tracer& t, string& log, const regexes& warn_detect, - const V& envvars, const char* verbosity, const string& buildspec, A&&... a) { @@ -255,16 +272,77 @@ run_b (tracer& t, const string& buildspec, A&&... a) { const char* const* envvars (nullptr); - return run_b (t, + return run_b (envvars, + t, log, warn_detect, - envvars, - verbosity, - buildspec, forward (a)...); + verbosity, buildspec, forward (a)...); +} + +// Run a program and return its output as a string if it prints exactly one +// line and exits with zero code and fail otherwise. +// +template +static string +cmd_line (tracer& t, const string& name, const process_env& pe, A&&... a) +{ + try + { + fdpipe pipe (fdopen_pipe ()); // Text mode seems appropriate. + + process pr ( + process_start_callback (t, + fdnull () /* stdin */, + pipe, /* stdout */ + 2 /* stderr */, + pe, + forward (a)...)); + + pipe.out.close (); + + ifdstream is (move (pipe.in), fdstream_mode::skip); + + optional r; + if (is.peek () != ifdstream::traits_type::eof ()) + { + string s; + getline (is, s); + + if (!is.eof () && is.peek () == ifdstream::traits_type::eof ()) + { + r = move (s); + + if (verb >= 3) + t << *r; + } + } + + is.close (); + + if (pr.wait ()) + { + if (r) + return move (*r); + + fail << "invalid " << name << " output"; + } + + fail << name << ' ' << *pr.exit << endf; + } + catch (const process_error& e) + { + fail << "unable to execute " << name << ": " << e << endf; + } + catch (const io_error& e) + { + fail << "unable to read " << name << " stdout: " << e << endf; + } } static int bbot:: build (size_t argc, const char* argv[]) { + using namespace bpkg; + using string_parser::unquote; tracer trace ("build"); @@ -337,6 +415,7 @@ build (size_t argc, const char* argv[]) b_test_installed_create, b_test_installed_configure, b_test_installed_test, + bpkg_test_installed_create, bpkg_uninstall_uninstall }; @@ -351,6 +430,7 @@ build (size_t argc, const char* argv[]) "b.test-installed.create", "b.test-installed.configure", "b.test-installed.test", + "bpkg.test-installed.create", "bpkg.uninstall.uninstall"}; // Split the argument into prefix (empty if not present) and unquoted @@ -385,7 +465,8 @@ build (size_t argc, const char* argv[]) else { args.emplace ("bpkg.configure.create", a.second); - args.emplace ("b.test-installed.create", move (a.second)); + args.emplace ("b.test-installed.create", a.second); + args.emplace ("bpkg.test-installed.create", move (a.second)); } }; @@ -476,6 +557,26 @@ build (size_t argc, const char* argv[]) // dir_path install_root; + // bpkg-rep-fetch trust options. + // + cstrings trust_ops; + { + const char* t ("--trust-no"); + + for (const string& fp: tm.trust) + { + if (fp == "yes") + t = "--trust-yes"; + else + { + trust_ops.push_back ("--trust"); + trust_ops.push_back (fp.c_str ()); + } + } + + trust_ops.push_back (t); + } + // Configuration directory name. // dir_path build_dir ("build"); @@ -554,28 +655,13 @@ build (size_t argc, const char* argv[]) // // bpkg.configure.fetch // - string t ("--trust-no"); - - cstrings ts; - for (const string& fp: tm.trust) - { - if (fp == "yes") - t = "--trust-yes"; - else - { - ts.push_back ("--trust"); - ts.push_back (fp.c_str ()); - } - } - r.status |= run_bpkg ( trace, r.log, wre, "-v", "fetch", step_args (config_args, step_id::bpkg_configure_fetch), step_args (env_args, step_id::bpkg_configure_fetch), - ts, - t); + trust_ops); if (!r.status) break; @@ -588,11 +674,11 @@ build (size_t argc, const char* argv[]) // Specify the revision explicitly not to end up with a race condition // building the latest revision rather than the zero revision. // - bpkg::version v (tm.version.epoch, - tm.version.upstream, - tm.version.release, - tm.version.effective_revision (), - tm.version.iteration); + 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, @@ -657,39 +743,206 @@ build (size_t argc, const char* argv[]) fail << "unable to query project " << prj_dir << " info: " << e; } - // Test the package if the test operation is supported by the project. + // 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 + // configured. + // + path mf (prj_dir / "manifest"); + package_manifest pm (parse_manifest (mf, "package")); + + // Run the package external tests if any of the tests, examples, or + // benchmarks manifest values are specified. + // + // 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. + // + bool external_tests (!pm.tests.empty () || + !pm.examples.empty () || + !pm.benchmarks.empty ()); + + // Configure, update, and test packages in the bpkg configuration in the + // current working directory. Optionally set the environment variables for + // bpkg processes. Return true if all operations for all packages succeed. // - if (find (prj.operations.begin (), prj.operations.end (), "test") != - prj.operations.end ()) + auto test = [&trace, &wre, &step_args, &config_args, &env_args] + (const small_vector& pkgs, + operation_result& r, + const small_vector& envvars = {}) + { + for (const dependency& d: pkgs) + { + const string& pkg (d.name.string ()); + + // Configure. + // + // bpkg build --configure-only + // '[ ]' + // + // bpkg.configure.build + // + r.status |= run_bpkg ( + envvars, + trace, r.log, wre, + "-v", + "build", + "--configure-only", + "--yes", + step_args (config_args, step_id::bpkg_configure_build), + step_args (env_args, step_id::bpkg_configure_build), + "--", + d.string ()); + + if (!r.status) + return false; + + // Update. + // + // bpkg update + // + // bpkg.update.update + // + r.status |= run_bpkg ( + envvars, + trace, r.log, wre, + "-v", + "update", + step_args (config_args, step_id::bpkg_update_update), + step_args (env_args, step_id::bpkg_update_update), + pkg); + + if (!r.status) + return false; + + // Test. + // + // Note that we assume that the package supports the test operation + // since this is its main purpose. + // + // Lets change the working directory to the package directory to + // help ported to build2 third-party packages a bit. Note that this + // is not uncommon for them to expect that tests should run in the + // project root directory. + // + // To determine the package directory name we need to obtain the + // configured package version. Parsing the bpkg-pkg-status output + // seems to be the easiest way to accomplish this. + // + version ver; + { + string status (cmd_line (trace, + "bpkg-pkg-status", + "bpkg", "status", "--no-hold", pkg)); + + // Get the version offset in the status line. + // + string prefix (pkg + " configured "); + size_t p (prefix.size ()); + + // Make sure the status line prefix matches our expectations. + // + if (status.compare (0, p, prefix) != 0) + fail << "unexpected " << pkg << " status: " << status; + + // Extract the package version from the status line. + // + size_t n (status.find (' ', p)); + string v (status, p, n != string::npos ? n - p : n); + + try + { + ver = version (v); + } + catch (const invalid_argument& e) + { + fail << "invalid " << pkg << " version '" << v << "' in " + << "package status: " << e << + info << "status: " << status; + } + } + + // Finally, change the working directory to the package directory + // and run the tests. + // + dir_path prj_dir (pkg + '-' + ver.string ()); + dir_path owd (change_wd (trace, &r.log, prj_dir)); + + // bpkg test + // + // bpkg.test.test + // + r.status |= run_bpkg ( + envvars, + trace, r.log, wre, + "-v", + "test", + "-d", "..", + step_args (config_args, step_id::bpkg_test_test), + step_args (env_args, step_id::bpkg_test_test), + pkg); + + if (!r.status) + return false; + + change_wd (trace, &r.log, owd); + } + + return true; + }; + + if (internal_tests || external_tests) { operation_result& r (add_result ("test")); - // Lets change the working directory to the package directory to help - // ported to build2 third-party packages a bit. Note that this is not - // uncommon for them to expect that tests should run in the project root - // directory. + // Run internal tests. // - dir_path rwd (change_wd (trace, &r.log, prj_dir)); + if (internal_tests) + { + // Lets change the working directory to the package directory to help + // ported to build2 third-party packages a bit (see above for + // details). + // + dir_path owd (change_wd (trace, &r.log, prj_dir)); - // bpkg test - // - // bpkg.test.test - // - r.status |= run_bpkg ( - trace, r.log, wre, - "-v", - "test", - "-d", "..", - step_args (config_args, step_id::bpkg_test_test), - step_args (env_args, step_id::bpkg_test_test), - tm.name.string ()); + // bpkg test + // + // bpkg.test.test + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "test", + "-d", "..", + step_args (config_args, step_id::bpkg_test_test), + step_args (env_args, step_id::bpkg_test_test), + tm.name.string ()); - if (!r.status) - break; + if (!r.status) + break; - rm.status |= r.status; + change_wd (trace, &r.log, owd); + } - change_wd (trace, &r.log, rwd); + // Run external tests. + // + if (external_tests) + { + if (!test (pm.tests, r) || + !test (pm.examples, r) || + !test (pm.benchmarks, r)) + break; + } + + rm.status |= r.status; } // Install the package, optionally test the installation and uninstall @@ -710,7 +963,11 @@ build (size_t argc, const char* argv[]) // configure, build, and test them out of the source tree against the // installed package. // - // 3. Uninstall the package. + // 3. If any of the tests, examples, or benchmarks packages are specified, + // then configure, build, and test them in a separate bpkg + // configuration against the installed package. + // + // 4. Uninstall the package. // // Install. // @@ -737,6 +994,26 @@ build (size_t argc, const char* argv[]) // Test installed. // + // Make sure that the installed package executables are properly imported + // when configuring and running tests. + // + small_vector envvars; + { + // Note that we add the $config.install.root/bin directory at the + // beginning of the PATH environment variable value, so the installed + // executables are found first. + // + string paths ("PATH=" + (install_root / "bin").string ()); + + if (optional s = getenv ("PATH")) + { + paths += path::traits_type::path_separator; + paths += *s; + } + + envvars.push_back (move (paths)); + } + // Collect the "testable" subprojects. // dir_paths subprj_dirs; @@ -770,114 +1047,167 @@ build (size_t argc, const char* argv[]) // If there are any "testable" subprojects, then configure them // (sequentially) and test/build in parallel afterwards. // - if (!subprj_dirs.empty ()) + internal_tests = !subprj_dirs.empty (); + + if (internal_tests || external_tests) { operation_result& r (add_result ("test-installed")); change_wd (trace, &r.log, rwd); - string mods; // build2 create meta-operation parameters. - - for (const string& m: - step_args (modules, step_id::b_test_installed_create)) + // Run internal tests. + // + if (internal_tests) { - mods += mods.empty () ? ", " : " "; - mods += m; - } + string mods; // build2 create meta-operation parameters. - // b create(, ) - // - // b.test-installed.create - // - // Amalgamation directory that will contain configuration subdirectory - // for package tests out of source tree build. - // - dir_path out_dir ("build-installed"); + for (const string& m: + step_args (modules, step_id::b_test_installed_create)) + { + mods += mods.empty () ? ", " : " "; + mods += m; + } - r.status |= run_b ( - trace, r.log, wre, - "-V", - "create('" + out_dir.representation () + "'" + mods + ")", - step_args (config_args, step_id::b_test_installed_create), - step_args (env_args, step_id::b_test_installed_create)); + // b create(, ) + // + // b.test-installed.create + // + // Amalgamation directory that will contain configuration subdirectory + // for package tests out of source tree build. + // + dir_path out_dir ("build-installed"); - if (!r.status) - break; + r.status |= run_b ( + trace, r.log, wre, + "-V", + "create('" + out_dir.representation () + "'" + mods + ")", + step_args (config_args, step_id::b_test_installed_create), + step_args (env_args, step_id::b_test_installed_create)); - rm.status |= r.status; + if (!r.status) + break; - // Make sure that the installed package executables are properly imported - // when configuring and running tests. - // - // Note that we add the $config.install.root/bin directory at the - // beginning of the PATH environment variable value, so the installed - // executables are found first. - // - string paths ("PATH=" + (install_root / "bin").string ()); + // Configure subprojects and create buildspecs for their testing. + // + strings test_specs; + for (const dir_path& d: subprj_dirs) + { + // b configure(@) + // + // + // b.test-installed.configure + // + dir_path subprj_src_dir (build_dir / prj_dir / d); + dir_path subprj_out_dir (out_dir / d); + + r.status |= run_b ( + envvars, + trace, r.log, wre, + "-v", + "configure('" + + subprj_src_dir.representation () + "'@'" + + subprj_out_dir.representation () + "')", + step_args (config_args, step_id::b_test_installed_configure), + step_args (env_args, step_id::b_test_installed_configure)); + + if (!r.status) + break; - if (optional s = getenv ("PATH")) - { - paths += path::traits_type::path_separator; - paths += *s; - } + test_specs.push_back ( + "test('" + subprj_out_dir.representation () + "')"); + } - small_vector envvars {move (paths)}; + if (!r.status) + break; + + // Build/test subprojects. + // + // b test()... + // + // b.test-installed.test + // + r.status |= run_b ( + envvars, + trace, r.log, wre, + "-v", + test_specs, + step_args (config_args, step_id::b_test_installed_test), + step_args (env_args, step_id::b_test_installed_test)); + + if (!r.status) + break; + } - // Configure subprojects and create buildspecs for their testing. + // Run external tests. // - strings test_specs; - for (const dir_path& d: subprj_dirs) + if (external_tests) { - // b configure(@) - // + // Configure. // - // b.test-installed.configure + // bpkg create // - dir_path subprj_src_dir (build_dir / prj_dir / d); - dir_path subprj_out_dir (out_dir / d); + // bpkg.test-installed.create + // + dir_path config_dir ("build-installed-bpkg"); - r.status |= run_b ( + r.status |= run_bpkg ( trace, r.log, wre, - envvars, - "-v", - "configure('" + - subprj_src_dir.representation () + "'@'" + - subprj_out_dir.representation () + "')", - step_args (config_args, step_id::b_test_installed_configure), - step_args (env_args, step_id::b_test_installed_configure)); + "-V", + "create", + "-d", config_dir.string (), + "--wipe", + step_args (modules, step_id::bpkg_test_installed_create), + step_args (config_args, step_id::bpkg_test_installed_create), + step_args (env_args, step_id::bpkg_test_installed_create)); if (!r.status) break; - test_specs.push_back ( - "test('" + subprj_out_dir.representation () + "')"); - } + dir_path owd (change_wd (trace, &r.log, config_dir)); - if (!r.status) - break; + // bpkg add + // + // bpkg.configure.add + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "add", + step_args (config_args, step_id::bpkg_configure_add), + step_args (env_args, step_id::bpkg_configure_add), + tm.repository.string ()); - rm.status |= r.status; + if (!r.status) + break; - // Build/test subprojects. - // - // b test()... - // - // b.test-installed.test - // - r.status |= run_b ( - trace, r.log, wre, - envvars, - "-v", - test_specs, - step_args (config_args, step_id::b_test_installed_test), - step_args (env_args, step_id::b_test_installed_test)); + // bpkg fetch + // + // bpkg.configure.fetch + // + r.status |= run_bpkg ( + trace, r.log, wre, + "-v", + "fetch", + step_args (config_args, step_id::bpkg_configure_fetch), + step_args (env_args, step_id::bpkg_configure_fetch), + trust_ops); - if (!r.status) - break; + if (!r.status) + break; - rm.status |= r.status; + // Build/test. + // + if (!test (pm.tests, r, envvars) || + !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; } // Uninstall. diff --git a/doc/manual.cli b/doc/manual.cli index d2457da..41973f7 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -784,6 +784,24 @@ bpkg -v update # bpkg -v test +# for each package referred to by the tests, examples, or benchmarks +# package manifest values: +# +{ + # bpkg.configure.build + # + bpkg -v build --yes --configure-only \\ + ' []' + + # bpkg.update.update + # + bpkg -v update + + # bpkg.test.test + # + bpkg -v test +} + # if config.install.root is specified: # { @@ -807,11 +825,46 @@ bpkg -v test # b -v test } -} -# bpkg.uninstall.uninstall -# -bpkg -v uninstall + # if any of the tests, examples, or benchmarks package manifest + # values are specified: + # + { + # bpkg.test-installed.create + # + bpkg -V create + + # bpkg.configure.add + # + bpkg -v add + + # bpkg.configure.fetch + # + bpkg -v fetch --trust + + # for each package referred to by the tests, examples, or benchmarks + # package manifest values: + # + { + # bpkg.configure.build + # + bpkg -v build --yes --configure-only \\ + ' []' + + # bpkg.update.update + # + bpkg -v update + + # bpkg.test.test + # + bpkg -v test + } + } + + # bpkg.uninstall.uninstall + # + bpkg -v uninstall +} \ For details on configuring and testing installation refer to @@ -891,9 +944,10 @@ expressions are included into the build task manifest. Values in the \c{} list can be opionally prefixed with the \i{step id} or a leading portion thereof to restrict it to a specific step, operation, or tool in the \i{worked script} (see \l{#arch-worker Worker -Logic}). Unprefixed values only apply to the \c{bpkg.configure.create} and -\c{b.test-installed.create} steps. Note that options with values can only be -specified using the single argument notation. For example: +Logic}). Unprefixed values only apply to the \c{bpkg.configure.create}, +\c{b.test-installed.create}, and \c{bpkg.test-installed.create} steps. Note +that options with values can only be specified using the single argument +notation. For example: \ bpkg:--fetch-timeout=600 bpkg.configure.fetch:--fetch-timeout=60 b:-j1 @@ -962,8 +1016,10 @@ If the \c{} list contains the \c{config.install.root} variable that applies to the \c{bpkg.configure.create} step, then in addition to building and possibly running tests, the \c{bbot} worker will also test installing and uninstalling each package. Furthermore, if the package contains -subprojects that support the test operation, then the worker will additionally -build such subprojects against the installation and run their tests. +subprojects that support the test operation and/or refers to other packages +via the \c{tests}, \c{examples}, or c\{benchmarks} manifest values, then the +worker will additionally build such subprojects/packages against the +installation and run their tests. Two types of installations can be tested: \i{system} and \i{private}. A system installation uses a well-known location, such as \c{/usr} or \c{/usr/local}, diff --git a/tests/integration/testscript b/tests/integration/testscript index 94c57ac..7692436 100644 --- a/tests/integration/testscript +++ b/tests/integration/testscript @@ -64,6 +64,14 @@ rep_type = pkg rfp = yes #\ +#\ +pkg = hello +ver = 0.1.0-a.0.20191025174646.0150b7eb45af +rep_url = "https://github.com/karen-arutyunov/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. @@ -72,7 +80,9 @@ config = "\"config.install.root='$~/install'\" \ bpkg:--fetch-timeout=60 \ b.test-installed.configure:\"config.cc.loptions=-L'$~/install/lib'\" \ b.test-installed.configure:\"config.bin.rpath='$~/install/lib'\" \ -b.test-installed:--progress" +b.test-installed:--progress \ +bpkg.test-installed.create:\"config.cc.loptions=-L'$~/install/lib'\" \ +bpkg.test-installed.create:\"config.bin.rpath='$~/install/lib'\"" +cat <<"EOI" >=task : 1 @@ -120,5 +130,6 @@ a = $0 chmod ugo+x $target; sleep $wait; $w --verbose 3 --startup --tftp-host $tftp --environments $~ \ - &build/*** &?build-installed/*** &task.manifest 2>| + &build/*** &?build-installed/*** &?build-tests-installed/*** \ + &task.manifest 2>| } -- cgit v1.1