diff --git a/contrib/kyua/doc/kyuafile.5.in b/contrib/kyua/doc/kyuafile.5.in index ae1e4fe40e32..43f00816d407 100644 --- a/contrib/kyua/doc/kyuafile.5.in +++ b/contrib/kyua/doc/kyuafile.5.in @@ -1,550 +1,560 @@ .\" Copyright 2012-2024 The Kyua Authors. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions are .\" met: .\" .\" * Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" * Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" * Neither the name of Google Inc. nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT .\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .Dd March 23, 2024 .Dt KYUAFILE 5 .Os .Sh NAME .Nm Kyuafile .Nd Test suite description files .Sh SYNOPSIS .Fn atf_test_program "string name" "[string metadata]" .Fn current_kyuafile .Fn fs.basename "string path" .Fn fs.dirname "string path" .Fn fs.exists "string path" .Fn fs.files "string path" .Fn fs.is_absolute "string path" .Fn fs.join "string path" "string path" .Fn include "string path" .Fn plain_test_program "string name" "[string metadata]" .Fn syntax "int version" .Fn tap_test_program "string name" "[string metadata]" .Fn test_suite "string name" .Sh DESCRIPTION A test suite is a collection of test programs and is represented by a hierarchical layout of test binaries on the file system. Any subtree of the file system can represent a test suite, provided that it includes one or more .Nm Ns s , which are the test suite definition files. .Pp A .Nm is a Lua script whose purpose is to describe the structure of the test suite it belongs to. To do so, the script has access to a collection of special functions provided by .Xr kyua 1 as described in .Sx Helper functions . .Ss File versioning Every .Nm file starts with a call to .Fn syntax "int version" . This call determines the specific schema used by the file so that future backwards-incompatible modifications to the file can be introduced. .Pp Any new .Nm file should set .Fa version to .Sq 2 . .Ss Test suite definition If the .Nm registers any test programs, the .Nm must define the name of the test suite the test programs belong to by using the .Fn test_suite function at the very beginning of the file. .Pp The test suite name provided in the .Fn test_suite call tells .Xr kyua 1 which set of configuration variables from .Xr kyua.conf 5 to pass to the test programs at run time. .Ss Test program registration A .Nm can register test programs by means of a variety of .Fn *_test_program functions, all of which take the name of a test program and a set of optional metadata properties that describe such test program. .Pp The test programs to be registered must live in the current directory; in other words, the various .Fn *_test_program calls cannot reference test programs in other directories. The rationale for this is to force all .Nm files to be self-contained, and to simplify their internal representation. .Pp .Em ATF test programs are those that use the .Xr atf 7 libraries. They can be registered with the .Fn atf_test_program table constructor. This function takes the .Fa name of the test program and a collection of optional metadata settings for all the test cases in the test program. Any metadata properties defined by the test cases themselves override the metadata values defined here. The mapping to ATF metadata naming for each property is provided below. .Pp .Em Plain test programs are those that return 0 on success and non-0 on failure; in general, most test programs (even those that use fancy unit-testing libraries) behave this way and thus also qualify as plain test programs. They can be registered with the .Fn plain_test_program table constructor. This function takes the .Fa name of the test program, an optional .Fa test_suite name that overrides the global test suite name, and a collection of optional metadata settings for the test program. .Pp .Em TAP test programs are those that implement the Test Anything Protocol. They can be registered with the .Fn tap_test_program table constructor. This function takes the .Fa name of the test program and a collection of optional metadata settings for the test program. .Pp The following metadata properties can be passed to any test program definition: .Bl -tag -width XX -offset indent .It Va allowed_architectures Whitespace-separated list of machine architecture names allowed by the test. If empty or not defined, the test is allowed to run on any machine architecture. .Pp ATF: .Va require.arch .It Va allowed_platforms Whitespace-separated list of machine platform names allowed by the test. If empty or not defined, the test is allowed to run on any machine platform. .Pp ATF: .Va require.machine .It Va custom.NAME Custom variable defined by the test where .Sq NAME denotes the name of the variable. These variables are useful to tag your tests with information specific to your project. The values of such variables are propagated all the way from the tests to the results files and later to any generated reports. .Pp Note that if the name happens to have dashes or any other special characters in it, you will have to use a special Lua syntax to define the property. Refer to the .Sx EXAMPLES section below for clarification. .Pp ATF: .Va X-NAME .It Va description Textual description of the test. .Pp ATF: .Va descr .It Va execenv The name of the execution environment to be used for running the test. If empty or not defined, the .Sq host execution environment is meant. The possible values are: .Bl -tag -width xUnnnnnnn .It host The default environment which runs the test as a usual child process. .It jail The .Fx .Xr jail 8 environment. It creates a temporary jail to run the test and its optional cleanup logic within. .Pp This feature requires .Xr kyua 1 to be running with superuser privileges. .Pp The difference between .Va security.jail.children.max and .Va security.jail.children.cur sysctl of the jail .Xr kyua 1 is running within must have a value high enough for the jail based tests planned to be run. For instance, the value 1 should be enough for a sequential run of simple tests. Otherwise, such aspects as parallel test execution and sub-jails spawned by specific test cases should be considered. .Pp The formula of a temporary jail name is .Sq kyua + .Va test program path + .Sq _ + .Va test case name . All non-alphanumeric characters are replaced with .Sq _ . .Sq kyua_usr_tests_sys_netpfil_pf_pass_block_v4 is an example for /usr/tests/sys/netpfil/pf/pass_block:v4 test case. .El .Pp ATF: .Va execenv .It Va execenv_jail_params Additional test-specific whitespace-separated parameters of .Fx .Xr jail 8 to create a temporary jail within which the test is run. It makes sense only if execenv is set to .Sq jail . .sp .Xr kyua 1 implicitly passes .Sq children.max parameter to .Xr jail 8 for a temporary jail with the maximum possible value according to the jail .Xr kyua 1 itself is running within. It allows tests to easily spawn their own sub-jails without additional configuration. It can be overridden via .Va execenv_jail_params if needed. .Pp ATF: .Va execenv.jail.params .It Va is_exclusive If true, indicates that this test program cannot be executed along any other programs at the same time. Test programs that affect global system state, such as those that modify the value of a .Xr sysctl 8 setting, must set themselves as exclusive to prevent failures due to race conditions. Defaults to false. .Pp ATF: .Va is.exclusive .It Va required_configs Whitespace-separated list of configuration variables that the test requires to be defined before it can run. .Pp ATF: .Va require.config .It Va required_disk_space Amount of available disk space that the test needs to run successfully. .Pp ATF: .Va require.diskspace .It Va required_files Whitespace-separated list of paths that the test requires to exist before it can run. .Pp ATF: .Va require.files +.It Va required_kmods +Whitespace-separated list of kernel module names that the test requires to +be loaded before it can run. +This requirement checking is platform-dependent. +It is ignored for a non-supported platform. +Supported platforms: +.Fx . +.Pp +ATF: +.Va require.kmods .It Va required_memory Amount of physical memory that the test needs to run successfully. .Pp ATF: .Va require.memory .It Va required_programs Whitespace-separated list of basenames or absolute paths pointing to executable binaries that the test requires to exist before it can run. .Pp ATF: .Va require.progs .It Va required_user If empty, the test has no restrictions on the calling user for it to run. If set to .Sq unprivileged , the test needs to not run as root. If set to .Sq root , the test must run as root. .Pp ATF: .Va require.user .It Va timeout Amount of seconds that the test is allowed to execute before being killed. .Pp ATF: .Va timeout .El .Ss Recursion To reference test programs in another subdirectory, a different .Nm must be created in that directory and it must be included into the original .Nm by means of the .Fn include function. .Pp .Fn include may only be called with a relative path and with at most one directory component. This is by design: Kyua uses the file system structure as the layout of the test suite definition. Therefore, each subdirectory in a test suite must include its own .Nm and each .Nm can only descend into the .Nm Ns s of immediate subdirectories. .Pp If you need to source a .Nm located in disjoint parts of your file system namespace, you will have to create a .Sq shadow tree using symbolic links and possibly helper .Nm Ns s to plug the various subdirectories together. See the .Sx EXAMPLES section below for details. .Pp Note that each file is processed in its own Lua environment: there is no mechanism to pass state from one file to the other. The reason for this is that there is no such thing as a .Dq top-level .Nm in a test suite: the user has to be able to run the test suite from any directory in a given hierarchy, and this execution must not depend on files that live in parent directories. .Ss Top-level Kyuafile Every system has a top directory into which test suites get installed. The default is .Pa __TESTSDIR__ . Within this directory live test suites, each of which is in an independent subdirectory. Each subdirectory can be provided separately by independent third-party packages. .Pp Kyua allows running all the installed test suites at once in order to provide comprehensive cross-component reports. In order to do this, there is a special file in the top directory that knows how to inspect the subdirectories in search for other Kyuafiles and include them. .Pp The .Sx FILES section includes more details on where this file lives. .Ss Helper functions The .Sq base , .Sq string , and .Sq table Lua modules are fully available in the context of a .Nm . .Pp The following extra functions are provided by Kyua: .Bl -tag -width XX -offset indent .It Ft string Fn current_kyuafile Returns the absolute path to the current .Nm . .It Ft string Fn fs.basename "string path" Returns the last component of the given path. .It Ft string Fn fs.dirname "string path" Returns the given path without its last component or a dot if the path has a single component. .It Ft bool Fn fs.exists "string path" Checks if the given path exists. If the path is not absolute, it is relative to the directory containing the .Nm in which the call to this function occurs. .It Ft iterator Fn fs.files "string path" Opens a directory for scanning of its entries. The returned iterator yields an entry on each call, and the entry is simply the filename. If the path is not absolute, it is relative to the directory containing the .Nm in which the call to this function occurs. .It Ft is_absolute Fn fs.is_absolute "string path" Returns true if the given path is absolute; false otherwise. .It Ft join Fn fs.join "string path" "string path" Concatenates the two paths. The second path cannot be absolute. .El .Sh FILES .Bl -tag -width XX .It Pa __TESTSDIR__/Kyuafile . Top-level .Nm for the current system. .It Pa __EGDIR__/Kyuafile.top . Sample file to serve as a top-level .Nm . .El .Sh EXAMPLES The following .Nm is the simplest you can define. It provides a test suite definition and registers a couple of different test programs using different interfaces: .Bd -literal -offset indent syntax(2) test_suite('first') atf_test_program{name='integration_test'} plain_test_program{name='legacy_test'} .Ed .Pp The following example is a bit more elaborate. It introduces some metadata properties to the test program definitions and recurses into a couple of subdirectories: .Bd -literal -offset indent syntax(2) test_suite('second') plain_test_program{name='legacy_test', allowed_architectures='amd64 i386', required_files='/bin/ls', timeout=30} tap_test_program{name='privileged_test', required_user='root'} include('module-1/Kyuafile') include('module-2/Kyuafile') .Ed .Pp The syntax to define custom properties may be not obvious if their names have any characters that make the property name not be a valid Lua identifier. Dashes are just one example. To set such properties, do something like this: .Bd -literal -offset indent syntax(2) test_suite('FreeBSD') plain_test_program{name='the_test', ['custom.FreeBSD-Bug-Id']='category/12345'} .Ed .Ss FreeBSD jail execution environment The following example configures the test to be run within a temporary jail with .Xr vnet 9 support and the permission to create raw sockets: .Bd -literal -offset indent syntax(2) test_suite('FreeBSD') atf_test_program{name='network_test', execenv='jail', execenv_jail_params='vnet allow.raw_sockets', required_user='root'} .Ed .Pp A test case itself may have no requirements in superuser privileges, but required_user='root' metadata property reminds that the jail execution environment requires .Xr kyua 1 being running with root privileges, and the test is skipped otherwise with the respective message. The combination of .Va execenv set to .Sq jail and .Va required_user set to .Sq unprivileged does not work respectively. .Ss Connecting disjoint test suites Now suppose you had various test suites on your file system and you would like to connect them together so that they could be executed and treated as a single unit. The test suites we would like to connect live under .Pa /usr/tests , .Pa /usr/local/tests and .Pa ~/local/tests . .Pp We cannot create a .Nm that references these because the .Fn include directive does not support absolute paths. Instead, what we can do is create a shadow tree using symbolic links: .Bd -literal -offset indent $ mkdir ~/everything $ ln -s /usr/tests ~/everything/system-tests $ ln -s /usr/local/tests ~/everything/local-tests $ ln -s ~/local/tests ~/everything/home-tests .Ed .Pp And then we create an .Pa ~/everything/Kyuafile file to drive the execution of the integrated test suite: .Bd -literal -offset indent syntax(2) test_suite('test-all-the-things') include('system-tests/Kyuafile') include('local-tests/Kyuafile') include('home-tests/Kyuafile') .Ed .Pp Or, simply, you could reuse the sample top-level .Nm to avoid having to manually craft the list of directories into which to recurse: .Bd -literal -offset indent $ cp __EGDIR__/Kyuafile.top ~/everything/Kyuafile .Ed .Sh SEE ALSO .Xr kyua 1 diff --git a/contrib/kyua/drivers/report_junit_test.cpp b/contrib/kyua/drivers/report_junit_test.cpp index 0f009c6befd3..1c0929c0fef2 100644 --- a/contrib/kyua/drivers/report_junit_test.cpp +++ b/contrib/kyua/drivers/report_junit_test.cpp @@ -1,423 +1,426 @@ // Copyright 2014 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "drivers/report_junit.hpp" #include #include #include #include "drivers/scan_results.hpp" #include "engine/filters.hpp" #include "model/context.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" #include "store/write_backend.hpp" #include "store/write_transaction.hpp" #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/units.hpp" namespace datetime = utils::datetime; namespace fs = utils::fs; namespace units = utils::units; using utils::none; namespace { /// Formatted metadata for a test case with defaults. static const char* const default_metadata = "allowed_architectures is empty\n" "allowed_platforms is empty\n" "description is empty\n" "execenv is empty\n" "execenv_jail_params is empty\n" "has_cleanup = false\n" "is_exclusive = false\n" "required_configs is empty\n" "required_disk_space = 0\n" "required_files is empty\n" + "required_kmods is empty\n" "required_memory = 0\n" "required_programs is empty\n" "required_user is empty\n" "timeout = 300\n"; /// Formatted metadata for a test case constructed with the "with_metadata" flag /// set to true in add_tests. static const char* const overriden_metadata = "allowed_architectures is empty\n" "allowed_platforms is empty\n" "description = Textual description\n" "execenv is empty\n" "execenv_jail_params is empty\n" "has_cleanup = false\n" "is_exclusive = false\n" "required_configs is empty\n" "required_disk_space = 0\n" "required_files is empty\n" + "required_kmods is empty\n" "required_memory = 0\n" "required_programs is empty\n" "required_user is empty\n" "timeout = 5678\n"; /// Populates the context of the given database. /// /// \param tx Transaction to use for the writes to the database. /// \param env_vars Number of environment variables to add to the context. static void add_context(store::write_transaction& tx, const std::size_t env_vars) { std::map< std::string, std::string > env; for (std::size_t i = 0; i < env_vars; i++) env[F("VAR%s") % i] = F("Value %s") % i; const model::context context(fs::path("/root"), env); (void)tx.put_context(context); } /// Adds a new test program with various test cases to the given database. /// /// \param tx Transaction to use for the writes to the database. /// \param prog Test program name. /// \param results Collection of results for the added test cases. The size of /// this vector indicates the number of tests in the test program. /// \param with_metadata Whether to add metadata overrides to the test cases. /// \param with_output Whether to add stdout/stderr messages to the test cases. static void add_tests(store::write_transaction& tx, const char* prog, const std::vector< model::test_result >& results, const bool with_metadata, const bool with_output) { model::test_program_builder test_program_builder( "plain", fs::path(prog), fs::path("/root"), "suite"); for (std::size_t j = 0; j < results.size(); j++) { model::metadata_builder builder; if (with_metadata) { builder.set_description("Textual description"); builder.set_timeout(datetime::delta(5678, 0)); } test_program_builder.add_test_case(F("t%s") % j, builder.build()); } const model::test_program test_program = test_program_builder.build(); const int64_t tp_id = tx.put_test_program(test_program); for (std::size_t j = 0; j < results.size(); j++) { const int64_t tc_id = tx.put_test_case(test_program, F("t%s") % j, tp_id); const datetime::timestamp start = datetime::timestamp::from_microseconds(0); const datetime::timestamp end = datetime::timestamp::from_microseconds(j * 1000000 + 500000); tx.put_result(results[j], tc_id, start, end); if (with_output) { atf::utils::create_file("fake-out", F("stdout file %s") % j); tx.put_test_case_file("__STDOUT__", fs::path("fake-out"), tc_id); atf::utils::create_file("fake-err", F("stderr file %s") % j); tx.put_test_case_file("__STDERR__", fs::path("fake-err"), tc_id); } } } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(junit_classname); ATF_TEST_CASE_BODY(junit_classname) { const model::test_program test_program = model::test_program_builder( "plain", fs::path("dir1/dir2/program"), fs::path("/root"), "suite") .build(); ATF_REQUIRE_EQ("dir1.dir2.program", drivers::junit_classname(test_program)); } ATF_TEST_CASE_WITHOUT_HEAD(junit_duration); ATF_TEST_CASE_BODY(junit_duration) { ATF_REQUIRE_EQ("0.457", drivers::junit_duration(datetime::delta(0, 456700))); ATF_REQUIRE_EQ("3.120", drivers::junit_duration(datetime::delta(3, 120000))); ATF_REQUIRE_EQ("5.000", drivers::junit_duration(datetime::delta(5, 0))); } ATF_TEST_CASE_WITHOUT_HEAD(junit_metadata__defaults); ATF_TEST_CASE_BODY(junit_metadata__defaults) { const model::metadata metadata = model::metadata_builder().build(); const std::string expected = std::string() + drivers::junit_metadata_header + default_metadata; ATF_REQUIRE_EQ(expected, drivers::junit_metadata(metadata)); } ATF_TEST_CASE_WITHOUT_HEAD(junit_metadata__overrides); ATF_TEST_CASE_BODY(junit_metadata__overrides) { const model::metadata metadata = model::metadata_builder() .add_allowed_architecture("arch1") .add_allowed_platform("platform1") .set_description("This is a test") .set_execenv("jail") .set_execenv_jail_params("vnet") .set_has_cleanup(true) .set_is_exclusive(true) .add_required_config("config1") .set_required_disk_space(units::bytes(456)) .add_required_file(fs::path("file1")) .set_required_memory(units::bytes(123)) .add_required_program(fs::path("prog1")) .set_required_user("root") .set_timeout(datetime::delta(10, 0)) .build(); const std::string expected = std::string() + drivers::junit_metadata_header + "allowed_architectures = arch1\n" + "allowed_platforms = platform1\n" + "description = This is a test\n" + "execenv = jail\n" + "execenv_jail_params = vnet\n" + "has_cleanup = true\n" + "is_exclusive = true\n" + "required_configs = config1\n" + "required_disk_space = 456\n" + "required_files = file1\n" + + "required_kmods is empty\n" + "required_memory = 123\n" + "required_programs = prog1\n" + "required_user = root\n" + "timeout = 10\n"; ATF_REQUIRE_EQ(expected, drivers::junit_metadata(metadata)); } ATF_TEST_CASE_WITHOUT_HEAD(junit_timing); ATF_TEST_CASE_BODY(junit_timing) { const std::string expected = std::string() + drivers::junit_timing_header + "Start time: 2015-06-12T01:02:35.123456Z\n" "End time: 2016-07-13T18:47:10.000001Z\n" "Duration: 34364674.877s\n"; const datetime::timestamp start_time = datetime::timestamp::from_values(2015, 6, 12, 1, 2, 35, 123456); const datetime::timestamp end_time = datetime::timestamp::from_values(2016, 7, 13, 18, 47, 10, 1); ATF_REQUIRE_EQ(expected, drivers::junit_timing(start_time, end_time)); } ATF_TEST_CASE_WITHOUT_HEAD(report_junit_hooks__minimal); ATF_TEST_CASE_BODY(report_junit_hooks__minimal) { store::write_backend backend = store::write_backend::open_rw( fs::path("test.db")); store::write_transaction tx = backend.start_write(); add_context(tx, 0); tx.commit(); backend.close(); std::ostringstream output; drivers::report_junit_hooks hooks(output); drivers::scan_results::drive(fs::path("test.db"), std::set< engine::test_filter >(), hooks); const char* expected = "\n" "\n" "\n" "\n" "\n" "\n"; ATF_REQUIRE_EQ(expected, output.str()); } ATF_TEST_CASE_WITHOUT_HEAD(report_junit_hooks__some_tests); ATF_TEST_CASE_BODY(report_junit_hooks__some_tests) { std::vector< model::test_result > results1; results1.push_back(model::test_result( model::test_result_broken, "Broken")); results1.push_back(model::test_result( model::test_result_expected_failure, "XFail")); results1.push_back(model::test_result( model::test_result_failed, "Failed")); std::vector< model::test_result > results2; results2.push_back(model::test_result( model::test_result_passed)); results2.push_back(model::test_result( model::test_result_skipped, "Skipped")); store::write_backend backend = store::write_backend::open_rw( fs::path("test.db")); store::write_transaction tx = backend.start_write(); add_context(tx, 2); add_tests(tx, "dir/prog-1", results1, false, false); add_tests(tx, "dir/sub/prog-2", results2, true, true); tx.commit(); backend.close(); std::ostringstream output; drivers::report_junit_hooks hooks(output); drivers::scan_results::drive(fs::path("test.db"), std::set< engine::test_filter >(), hooks); const std::string expected = std::string() + "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "" + drivers::junit_metadata_header + default_metadata + drivers::junit_timing_header + "Start time: 1970-01-01T00:00:00.000000Z\n" "End time: 1970-01-01T00:00:00.500000Z\n" "Duration: 0.500s\n" + drivers::junit_stderr_header + "<EMPTY>\n" "\n" "\n" "\n" "" "Expected failure result details\n" "-------------------------------\n" "\n" "XFail\n" "\n" + drivers::junit_metadata_header + default_metadata + drivers::junit_timing_header + "Start time: 1970-01-01T00:00:00.000000Z\n" "End time: 1970-01-01T00:00:01.500000Z\n" "Duration: 1.500s\n" + drivers::junit_stderr_header + "<EMPTY>\n" "\n" "\n" "\n" "\n" "" + drivers::junit_metadata_header + default_metadata + drivers::junit_timing_header + "Start time: 1970-01-01T00:00:00.000000Z\n" "End time: 1970-01-01T00:00:02.500000Z\n" "Duration: 2.500s\n" + drivers::junit_stderr_header + "<EMPTY>\n" "\n" "\n" "\n" "stdout file 0\n" "" + drivers::junit_metadata_header + overriden_metadata + drivers::junit_timing_header + "Start time: 1970-01-01T00:00:00.000000Z\n" "End time: 1970-01-01T00:00:00.500000Z\n" "Duration: 0.500s\n" + drivers::junit_stderr_header + "stderr file 0\n" "\n" "\n" "\n" "stdout file 1\n" "" "Skipped result details\n" "----------------------\n" "\n" "Skipped\n" "\n" + drivers::junit_metadata_header + overriden_metadata + drivers::junit_timing_header + "Start time: 1970-01-01T00:00:00.000000Z\n" "End time: 1970-01-01T00:00:01.500000Z\n" "Duration: 1.500s\n" + drivers::junit_stderr_header + "stderr file 1\n" "\n" "\n"; ATF_REQUIRE_EQ(expected, output.str()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, junit_classname); ATF_ADD_TEST_CASE(tcs, junit_duration); ATF_ADD_TEST_CASE(tcs, junit_metadata__defaults); ATF_ADD_TEST_CASE(tcs, junit_metadata__overrides); ATF_ADD_TEST_CASE(tcs, junit_timing); ATF_ADD_TEST_CASE(tcs, report_junit_hooks__minimal); ATF_ADD_TEST_CASE(tcs, report_junit_hooks__some_tests); } diff --git a/contrib/kyua/engine/atf_list.cpp b/contrib/kyua/engine/atf_list.cpp index e0c4170605d1..5c74a80be913 100644 --- a/contrib/kyua/engine/atf_list.cpp +++ b/contrib/kyua/engine/atf_list.cpp @@ -1,208 +1,206 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "engine/atf_list.hpp" #include #include #include #include "engine/exceptions.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "utils/config/exceptions.hpp" #include "utils/format/macros.hpp" namespace config = utils::config; namespace fs = utils::fs; namespace { /// Splits a property line of the form "name: word1 [... wordN]". /// /// \param line The line to parse. /// /// \return A (property_name, property_value) pair. /// /// \throw format_error If the value of line is invalid. static std::pair< std::string, std::string > split_prop_line(const std::string& line) { const std::string::size_type pos = line.find(": "); if (pos == std::string::npos) throw engine::format_error("Invalid property line; expecting line of " "the form 'name: value'"); return std::make_pair(line.substr(0, pos), line.substr(pos + 2)); } /// Parses a set of consecutive property lines. /// /// Processing stops when an empty line or the end of file is reached. None of /// these conditions indicate errors. /// /// \param input The stream to read the lines from. /// /// \return The parsed property lines. /// /// throw format_error If the input stream has an invalid format. static model::properties_map parse_properties(std::istream& input) { model::properties_map properties; std::string line; while (std::getline(input, line).good() && !line.empty()) { const std::pair< std::string, std::string > property = split_prop_line( line); if (properties.find(property.first) != properties.end()) throw engine::format_error("Duplicate value for property " + property.first); properties.insert(property); } return properties; } } // anonymous namespace /// Parses the metadata of an ATF test case. /// /// \param props The properties (name/value string pairs) as provided by the /// ATF test program. /// /// \return A parsed metadata object. /// /// \throw engine::format_error If the syntax of any of the properties is /// invalid. model::metadata engine::parse_atf_metadata(const model::properties_map& props) { model::metadata_builder mdbuilder; try { for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); iter++) { const std::string& name = (*iter).first; const std::string& value = (*iter).second; if (name == "descr") { mdbuilder.set_string("description", value); } else if (name == "has.cleanup") { mdbuilder.set_string("has_cleanup", value); } else if (name == "require.arch") { mdbuilder.set_string("allowed_architectures", value); } else if (name == "execenv") { mdbuilder.set_string("execenv", value); } else if (name == "execenv.jail.params") { mdbuilder.set_string("execenv_jail_params", value); } else if (name == "is.exclusive") { mdbuilder.set_string("is_exclusive", value); } else if (name == "require.config") { mdbuilder.set_string("required_configs", value); } else if (name == "require.diskspace") { mdbuilder.set_string("required_disk_space", value); } else if (name == "require.files") { mdbuilder.set_string("required_files", value); -#ifdef __FreeBSD__ } else if (name == "require.kmods") { mdbuilder.set_string("required_kmods", value); -#endif } else if (name == "require.machine") { mdbuilder.set_string("allowed_platforms", value); } else if (name == "require.memory") { mdbuilder.set_string("required_memory", value); } else if (name == "require.progs") { mdbuilder.set_string("required_programs", value); } else if (name == "require.user") { mdbuilder.set_string("required_user", value); } else if (name == "timeout") { mdbuilder.set_string("timeout", value); } else if (name.length() > 2 && name.substr(0, 2) == "X-") { mdbuilder.add_custom(name.substr(2), value); } else { throw engine::format_error(F("Unknown test case metadata " "property '%s'") % name); } } } catch (const config::error& e) { throw engine::format_error(e.what()); } return mdbuilder.build(); } /// Parses the ATF list of test cases from an open stream. /// /// \param input The stream to read from. /// /// \return The collection of parsed test cases. /// /// \throw format_error If there is any problem in the input data. model::test_cases_map engine::parse_atf_list(std::istream& input) { std::string line; std::getline(input, line); if (line != "Content-Type: application/X-atf-tp; version=\"1\"" || !input.good()) throw format_error(F("Invalid header for test case list; expecting " "Content-Type for application/X-atf-tp version 1, " "got '%s'") % line); std::getline(input, line); if (!line.empty() || !input.good()) throw format_error(F("Invalid header for test case list; expecting " "a blank line, got '%s'") % line); model::test_cases_map_builder test_cases_builder; while (std::getline(input, line).good()) { const std::pair< std::string, std::string > ident = split_prop_line( line); if (ident.first != "ident" or ident.second.empty()) throw format_error("Invalid test case definition; must be " "preceeded by the identifier"); const model::properties_map props = parse_properties(input); test_cases_builder.add(ident.second, parse_atf_metadata(props)); } const model::test_cases_map test_cases = test_cases_builder.build(); if (test_cases.empty()) { // The scheduler interface also checks for the presence of at least one // test case. However, because the atf format itself requires one test // case to be always present, we check for this condition here as well. throw format_error("No test cases"); } return test_cases; } diff --git a/contrib/kyua/engine/requirements.cpp b/contrib/kyua/engine/requirements.cpp index dff43e531a57..d5838b83f33a 100644 --- a/contrib/kyua/engine/requirements.cpp +++ b/contrib/kyua/engine/requirements.cpp @@ -1,356 +1,353 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "engine/requirements.hpp" #include "engine/execenv/execenv.hpp" #include "model/metadata.hpp" #include "model/types.hpp" #include "utils/config/nodes.ipp" #include "utils/config/tree.ipp" #include "utils/format/macros.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/memory.hpp" #include "utils/passwd.hpp" #include "utils/sanity.hpp" #include "utils/units.hpp" -#ifdef __FreeBSD__ -#include -#endif - namespace config = utils::config; namespace fs = utils::fs; namespace passwd = utils::passwd; namespace units = utils::units; namespace { /// Checks if all required configuration variables are present. /// /// \param required_configs Set of required variable names. /// \param user_config Runtime user configuration. /// \param test_suite_name Name of the test suite the test belongs to. /// /// \return Empty if all variables are present or an error message otherwise. static std::string check_required_configs(const model::strings_set& required_configs, const config::tree& user_config, const std::string& test_suite_name) { for (model::strings_set::const_iterator iter = required_configs.begin(); iter != required_configs.end(); iter++) { std::string property; // TODO(jmmv): All this rewrite logic belongs in the ATF interface. if ((*iter) == "unprivileged-user" || (*iter) == "unprivileged_user") property = "unprivileged_user"; else property = F("test_suites.%s.%s") % test_suite_name % (*iter); if (!user_config.is_set(property)) return F("Required configuration property '%s' not defined") % (*iter); } return ""; } /// Checks if the allowed architectures match the current architecture. /// /// \param allowed_architectures Set of allowed architectures. /// \param user_config Runtime user configuration. /// /// \return Empty if the current architecture is in the list or an error /// message otherwise. static std::string check_allowed_architectures(const model::strings_set& allowed_architectures, const config::tree& user_config) { if (!allowed_architectures.empty()) { const std::string architecture = user_config.lookup< config::string_node >("architecture"); if (allowed_architectures.find(architecture) == allowed_architectures.end()) return F("Current architecture '%s' not supported") % architecture; } return ""; } /// Checks if test's execenv matches the user configuration. /// /// \param execenv Execution environment name a test is designed for. /// \param user_config Runtime user configuration. /// /// \return Empty if the execenv is in the list or an error message otherwise. static std::string check_execenv(const std::string& execenv, const config::tree& user_config) { std::string name = execenv; if (name.empty()) name = engine::execenv::default_execenv_name; // if test claims nothing std::set< std::string > execenvs; try { execenvs = user_config.lookup< config::strings_set_node >("execenvs"); } catch (const config::unknown_key_error&) { // okay, user config does not define it, empty set then } if (execenvs.find(name) == execenvs.end()) return F("'%s' execenv is not supported or not allowed by " "the runtime user configuration") % name; return ""; } /// Checks if the allowed platforms match the current architecture. /// /// \param allowed_platforms Set of allowed platforms. /// \param user_config Runtime user configuration. /// /// \return Empty if the current platform is in the list or an error message /// otherwise. static std::string check_allowed_platforms(const model::strings_set& allowed_platforms, const config::tree& user_config) { if (!allowed_platforms.empty()) { const std::string platform = user_config.lookup< config::string_node >("platform"); if (allowed_platforms.find(platform) == allowed_platforms.end()) return F("Current platform '%s' not supported") % platform; } return ""; } /// Checks if the current user matches the required user. /// /// \param required_user Name of the required user category. /// \param user_config Runtime user configuration. /// /// \return Empty if the current user fits the required user characteristics or /// an error message otherwise. static std::string check_required_user(const std::string& required_user, const config::tree& user_config) { if (!required_user.empty()) { const passwd::user user = passwd::current_user(); if (required_user == "root") { if (!user.is_root()) return "Requires root privileges"; } else if (required_user == "unprivileged") { if (user.is_root()) if (!user_config.is_set("unprivileged_user")) return "Requires an unprivileged user but the " "unprivileged-user configuration variable is not " "defined"; } else UNREACHABLE_MSG("Value of require.user not properly validated"); } return ""; } /// Checks if all required files exist. /// /// \param required_files Set of paths. /// /// \return Empty if the required files all exist or an error message otherwise. static std::string check_required_files(const model::paths_set& required_files) { for (model::paths_set::const_iterator iter = required_files.begin(); iter != required_files.end(); iter++) { INV((*iter).is_absolute()); if (!fs::exists(*iter)) return F("Required file '%s' not found") % *iter; } return ""; } /// Checks if all required programs exist. /// /// \param required_programs Set of paths. /// /// \return Empty if the required programs all exist or an error message /// otherwise. static std::string check_required_programs(const model::paths_set& required_programs) { for (model::paths_set::const_iterator iter = required_programs.begin(); iter != required_programs.end(); iter++) { if ((*iter).is_absolute()) { if (!fs::exists(*iter)) return F("Required program '%s' not found") % *iter; } else { if (!fs::find_in_path((*iter).c_str())) return F("Required program '%s' not found in PATH") % *iter; } } return ""; } -#ifdef __FreeBSD__ -/// Checks if all required kmods are loaded. -/// -/// \param required_programs Set of kmods. -/// -/// \return Empty if the required kmods are all loaded or an error -/// message otherwise. -static std::string -check_required_kmods(const model::strings_set& required_kmods) -{ - for (model::strings_set::const_iterator iter = required_kmods.begin(); - iter != required_kmods.end(); iter++) { - if (!kld_isloaded((*iter).c_str())) - return F("Required kmod '%s' not loaded") % *iter; - } - return ""; -} -#endif - - /// Checks if the current system has the specified amount of memory. /// /// \param required_memory Amount of required physical memory, or zero if not /// applicable. /// /// \return Empty if the current system has the required amount of memory or an /// error message otherwise. static std::string check_required_memory(const units::bytes& required_memory) { if (required_memory > 0) { const units::bytes physical_memory = utils::physical_memory(); if (physical_memory > 0 && physical_memory < required_memory) return F("Requires %s bytes of physical memory but only %s " "available") % required_memory.format() % physical_memory.format(); } return ""; } /// Checks if the work directory's file system has enough free disk space. /// /// \param required_disk_space Amount of required free disk space, or zero if /// not applicable. /// \param work_directory Path to where the test case will be run. /// /// \return Empty if the file system where the work directory is hosted has /// enough free disk space or an error message otherwise. static std::string check_required_disk_space(const units::bytes& required_disk_space, const fs::path& work_directory) { if (required_disk_space > 0) { const units::bytes free_disk_space = fs::free_disk_space( work_directory); if (free_disk_space < required_disk_space) return F("Requires %s bytes of free disk space but only %s " "available") % required_disk_space.format() % free_disk_space.format(); } return ""; } +/// List of registered extra requirement checkers. +/// +/// Use register_reqs_checker() to add an entry to this global list. +static std::vector< std::shared_ptr< engine::reqs_checker > > _reqs_checkers; + + } // anonymous namespace +const std::vector< std::shared_ptr< engine::reqs_checker > > +engine::reqs_checkers() +{ + return _reqs_checkers; +} + +void +engine::register_reqs_checker( + const std::shared_ptr< engine::reqs_checker > checker) +{ + _reqs_checkers.push_back(checker); +} + + /// Checks if all the requirements specified by the test case are met. /// /// \param md The test metadata. /// \param cfg The engine configuration. /// \param test_suite Name of the test suite the test belongs to. /// \param work_directory Path to where the test case will be run. /// /// \return A string describing the reason for skipping the test, or empty if /// the test should be executed. std::string engine::check_reqs(const model::metadata& md, const config::tree& cfg, const std::string& test_suite, const fs::path& work_directory) { std::string reason; reason = check_required_configs(md.required_configs(), cfg, test_suite); if (!reason.empty()) return reason; reason = check_allowed_architectures(md.allowed_architectures(), cfg); if (!reason.empty()) return reason; reason = check_execenv(md.execenv(), cfg); if (!reason.empty()) return reason; reason = check_allowed_platforms(md.allowed_platforms(), cfg); if (!reason.empty()) return reason; reason = check_required_user(md.required_user(), cfg); if (!reason.empty()) return reason; reason = check_required_files(md.required_files()); if (!reason.empty()) return reason; reason = check_required_programs(md.required_programs()); if (!reason.empty()) return reason; -#ifdef __FreeBSD__ - reason = check_required_kmods(md.required_kmods()); - if (!reason.empty()) - return reason; -#endif - reason = check_required_memory(md.required_memory()); if (!reason.empty()) return reason; reason = check_required_disk_space(md.required_disk_space(), work_directory); if (!reason.empty()) return reason; + // Iterate over extra checkers registered. + for (auto& checker : engine::reqs_checkers()) { + reason = checker->exec(md, cfg, test_suite, work_directory); + if (!reason.empty()) + return reason; + } + INV(reason.empty()); return reason; } diff --git a/contrib/kyua/engine/requirements.hpp b/contrib/kyua/engine/requirements.hpp index a36a938b3034..92e80c5122aa 100644 --- a/contrib/kyua/engine/requirements.hpp +++ b/contrib/kyua/engine/requirements.hpp @@ -1,51 +1,77 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file engine/requirements.hpp /// Handling of test case requirements. #if !defined(ENGINE_REQUIREMENTS_HPP) #define ENGINE_REQUIREMENTS_HPP #include #include "model/metadata_fwd.hpp" #include "utils/config/tree_fwd.hpp" #include "utils/fs/path_fwd.hpp" namespace engine { std::string check_reqs(const model::metadata&, const utils::config::tree&, const std::string&, const utils::fs::path&); +/// Abstract interface of a requirement checker. +class reqs_checker { +public: + /// Constructor. + reqs_checker() {} + + /// Destructor. + virtual ~reqs_checker() {} + + /// Run the checker. + virtual std::string exec(const model::metadata&, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const = 0; +}; + +/// Register an extra requirement checker. +/// +/// \param checker A requirement checker. +void register_reqs_checker(const std::shared_ptr< reqs_checker > checker); + +/// Returns the list of registered extra requirement checkers. +/// +/// \return A vector of pointers to extra requirement checkers. +const std::vector< std::shared_ptr< reqs_checker > > reqs_checkers(); + } // namespace engine #endif // !defined(ENGINE_REQUIREMENTS_HPP) diff --git a/contrib/kyua/integration/cmd_report_junit_test.sh b/contrib/kyua/integration/cmd_report_junit_test.sh index d86228acf7e5..49b8c5790167 100755 --- a/contrib/kyua/integration/cmd_report_junit_test.sh +++ b/contrib/kyua/integration/cmd_report_junit_test.sh @@ -1,308 +1,312 @@ # Copyright 2014 The Kyua Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Google Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Executes a mock test suite to generate data in the database. # # \param mock_env The value to store in a MOCK variable in the environment. # Use this to be able to differentiate executions by inspecting the # context of the output. # \param dbfile_name File to which to write the path to the generated database # file. run_tests() { local mock_env="${1}"; shift local dbfile_name="${1}"; shift cat >Kyuafile <"${dbfile_name}" rm stdout # Ensure the results of 'report-junit' come from the database. rm Kyuafile simple_all_pass } # Removes the contents of a properties tag from stdout. strip_properties='awk " BEGIN { skip = 0; } /<\/properties>/ { print \"\"; skip = 0; next; } // { print \"\"; print \"CONTENTS STRIPPED BY TEST\"; skip = 1; next; } { if (!skip) print; }"' utils_test_case default_behavior__ok default_behavior__ok_body() { utils_install_times_wrapper run_tests "mock1 this should not be seen mock1 new line" unused_dbfile_name cat >expout < CONTENTS STRIPPED BY TEST This is the stdout of pass Test case metadata ------------------ allowed_architectures is empty allowed_platforms is empty description is empty execenv is empty execenv_jail_params is empty has_cleanup = false is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty timeout = 300 Timing information ------------------ Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Duration: S.UUUs Original stderr --------------- This is the stderr of pass This is the stdout of skip Skipped result details ---------------------- The reason for skipping is this Test case metadata ------------------ allowed_architectures is empty allowed_platforms is empty description is empty execenv is empty execenv_jail_params is empty has_cleanup = false is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty timeout = 300 Timing information ------------------ Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Duration: S.UUUs Original stderr --------------- This is the stderr of skip EOF atf_check -s exit:0 -o file:expout -e empty -x "kyua report-junit" \ "| ${strip_properties}" } utils_test_case default_behavior__no_store default_behavior__no_store_body() { echo 'kyua: E: No previous results file found for test suite' \ "$(utils_test_suite_id)." >experr atf_check -s exit:2 -o empty -e file:experr kyua report-junit } utils_test_case results_file__explicit results_file__explicit_body() { run_tests "mock1" dbfile_name1 run_tests "mock2" dbfile_name2 atf_check -s exit:0 -o match:"MOCK.*mock1" -o not-match:"MOCK.*mock2" \ -e empty kyua report-junit --results-file="$(cat dbfile_name1)" atf_check -s exit:0 -o not-match:"MOCK.*mock1" -o match:"MOCK.*mock2" \ -e empty kyua report-junit --results-file="$(cat dbfile_name2)" } utils_test_case results_file__not_found results_file__not_found_body() { atf_check -s exit:2 -o empty -e match:"kyua: E: No previous results.*foo" \ kyua report-junit --results-file=foo } utils_test_case output__explicit output__explicit_body() { run_tests unused_mock unused_dbfile_name cat >report < CONTENTS STRIPPED BY TEST This is the stdout of pass Test case metadata ------------------ allowed_architectures is empty allowed_platforms is empty description is empty execenv is empty execenv_jail_params is empty has_cleanup = false is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty timeout = 300 Timing information ------------------ Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Duration: S.UUUs Original stderr --------------- This is the stderr of pass This is the stdout of skip Skipped result details ---------------------- The reason for skipping is this Test case metadata ------------------ allowed_architectures is empty allowed_platforms is empty description is empty execenv is empty execenv_jail_params is empty has_cleanup = false is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty timeout = 300 Timing information ------------------ Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Duration: S.UUUs Original stderr --------------- This is the stderr of skip EOF atf_check -s exit:0 -o file:report -e empty -x kyua report-junit \ --output=/dev/stdout "| ${strip_properties} | ${utils_strip_times}" atf_check -s exit:0 -o empty -e save:stderr kyua report-junit \ --output=/dev/stderr atf_check -s exit:0 -o file:report -x cat stderr \ "| ${strip_properties} | ${utils_strip_times}" atf_check -s exit:0 -o empty -e empty kyua report-junit \ --output=my-file atf_check -s exit:0 -o file:report -x cat my-file \ "| ${strip_properties} | ${utils_strip_times}" } atf_init_test_cases() { atf_add_test_case default_behavior__ok atf_add_test_case default_behavior__no_store atf_add_test_case results_file__explicit atf_add_test_case results_file__not_found atf_add_test_case output__explicit } diff --git a/contrib/kyua/integration/cmd_report_test.sh b/contrib/kyua/integration/cmd_report_test.sh index 8b2b97f9cb4a..1fc1932d3c47 100755 --- a/contrib/kyua/integration/cmd_report_test.sh +++ b/contrib/kyua/integration/cmd_report_test.sh @@ -1,383 +1,384 @@ # Copyright 2011 The Kyua Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of Google Inc. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Executes a mock test suite to generate data in the database. # # \param mock_env The value to store in a MOCK variable in the environment. # Use this to be able to differentiate executions by inspecting the # context of the output. # \param dbfile_name File to which to write the path to the generated database # file. run_tests() { local mock_env="${1}"; shift local dbfile_name="${1}"; shift cat >Kyuafile <"${dbfile_name}" rm stdout # Ensure the results of 'report' come from the database. rm Kyuafile simple_all_pass } utils_test_case default_behavior__ok default_behavior__ok_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report run_tests "mock2" dbfile_name2 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name2) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report } utils_test_case default_behavior__no_store default_behavior__no_store_body() { echo 'kyua: E: No previous results file found for test suite' \ "$(utils_test_suite_id)." >experr atf_check -s exit:2 -o empty -e file:experr kyua report } utils_test_case results_file__explicit results_file__explicit_body() { run_tests "mock1" dbfile_name1 run_tests "mock2" dbfile_name2 atf_check -s exit:0 -o match:"MOCK=mock1" -o not-match:"MOCK=mock2" \ -e empty kyua report --results-file="$(cat dbfile_name1)" \ --verbose atf_check -s exit:0 -o not-match:"MOCK=mock1" -o match:"MOCK=mock2" \ -e empty kyua report --results-file="$(cat dbfile_name2)" \ --verbose } utils_test_case results_file__not_found results_file__not_found_body() { atf_check -s exit:2 -o empty -e match:"kyua: E: No previous results.*foo" \ kyua report --results-file=foo } utils_test_case output__explicit output__explicit_body() { run_tests unused_mock dbfile_name cat >report < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:report -e empty -x kyua report \ --output=/dev/stdout "| ${utils_strip_times_but_not_ids}" atf_check -s exit:0 -o empty -e save:stderr kyua report \ --output=/dev/stderr atf_check -s exit:0 -o file:report -x cat stderr \ "| ${utils_strip_times_but_not_ids}" atf_check -s exit:0 -o empty -e empty kyua report \ --output=my-file atf_check -s exit:0 -o file:report -x cat my-file \ "| ${utils_strip_times_but_not_ids}" } utils_test_case filter__ok filter__ok_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 1 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ simple_all_pass:skip } utils_test_case filter__ok_passed_excluded_by_default filter__ok_passed_excluded_by_default_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 # Passed results are excluded by default so they are not displayed even if # requested with a test case filter. This might be somewhat confusing... cat >expout < Summary Results read from $(cat dbfile_name1) Test cases: 1 total, 0 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ simple_all_pass:pass cat >expout < Passed tests simple_all_pass:pass -> passed [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 1 total, 0 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ --results-filter= simple_all_pass:pass } utils_test_case filter__no_match filter__no_match_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 1 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF cat >experr <expout < Execution context Current directory: ${real_cwd} Environment variables: EOF # $_ is a bash variable. To keep our tests stable, we override its value # below to match the hardcoded value in run_tests. env \ HOME="${real_cwd}" \ MOCK="mock1 has multiple lines and terminates here" \ _='fake-value' \ "$(atf_get_srcdir)/helpers/dump_env" ' ' ' ' >>expout cat >>expout < simple_all_pass:skip Result: skipped: The reason for skipping is this Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Duration: S.UUUs Metadata: allowed_architectures is empty allowed_platforms is empty description is empty execenv is empty execenv_jail_params is empty has_cleanup = false is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty + required_kmods is empty required_memory = 0 required_programs is empty required_user is empty timeout = 300 Standard output: This is the stdout of skip Standard error: This is the stderr of skip ===> Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Start time: YYYY-MM-DDTHH:MM:SS.ssssssZ End time: YYYY-MM-DDTHH:MM:SS.ssssssZ Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty -x kyua report --verbose \ "| ${utils_strip_times_but_not_ids}" } utils_test_case results_filter__empty results_filter__empty_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Passed tests simple_all_pass:pass -> passed [S.UUUs] ===> Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report --results-filter= } utils_test_case results_filter__one results_filter__one_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Passed tests simple_all_pass:pass -> passed [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ --results-filter=passed } utils_test_case results_filter__multiple_all_match results_filter__multiple_all_match_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Passed tests simple_all_pass:pass -> passed [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ --results-filter=skipped,passed } utils_test_case results_filter__multiple_some_match results_filter__multiple_some_match_body() { utils_install_times_wrapper run_tests "mock1" dbfile_name1 cat >expout < Skipped tests simple_all_pass:skip -> skipped: The reason for skipping is this [S.UUUs] ===> Summary Results read from $(cat dbfile_name1) Test cases: 2 total, 1 skipped, 0 expected failures, 0 broken, 0 failed Total time: S.UUUs EOF atf_check -s exit:0 -o file:expout -e empty kyua report \ --results-filter=skipped,xfail,broken,failed } atf_init_test_cases() { atf_add_test_case default_behavior__ok atf_add_test_case default_behavior__no_store atf_add_test_case results_file__explicit atf_add_test_case results_file__not_found atf_add_test_case filter__ok atf_add_test_case filter__ok_passed_excluded_by_default atf_add_test_case filter__no_match atf_add_test_case verbose atf_add_test_case output__explicit atf_add_test_case results_filter__empty atf_add_test_case results_filter__one atf_add_test_case results_filter__multiple_all_match atf_add_test_case results_filter__multiple_some_match } diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp index 1f17b9ce4624..09a521a76cec 100644 --- a/contrib/kyua/model/metadata.cpp +++ b/contrib/kyua/model/metadata.cpp @@ -1,1166 +1,1162 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/metadata.hpp" #include #include "engine/execenv/execenv.hpp" #include "model/exceptions.hpp" #include "model/types.hpp" #include "utils/config/exceptions.hpp" #include "utils/config/nodes.ipp" #include "utils/config/tree.ipp" #include "utils/datetime.hpp" #include "utils/defs.hpp" #include "utils/format/macros.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/path.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/text/exceptions.hpp" #include "utils/text/operations.hpp" #include "utils/units.hpp" namespace config = utils::config; namespace datetime = utils::datetime; namespace fs = utils::fs; namespace text = utils::text; namespace units = utils::units; using utils::optional; namespace { /// Global instance of defaults. /// /// This exists so that the getters in metadata can return references instead /// of object copies. Use get_defaults() to query. static optional< config::tree > defaults; /// A leaf node that holds a bytes quantity. class bytes_node : public config::native_leaf_node< units::bytes > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { std::auto_ptr< bytes_node > new_node(new bytes_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. void push_lua(lutok::state& /* state */) const { UNREACHABLE; } /// Sets the value of the node from an entry in the Lua stack. void set_lua(lutok::state& /* state */, const int /* index */) { UNREACHABLE; } }; /// A leaf node that holds a time delta. class delta_node : public config::typed_leaf_node< datetime::delta > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { std::auto_ptr< delta_node > new_node(new delta_node()); new_node->_value = _value; return new_node.release(); } /// Sets the value of the node from a raw string representation. /// /// \param raw_value The value to set the node to. /// /// \throw value_error If the value is invalid. void set_string(const std::string& raw_value) { unsigned int seconds; try { seconds = text::to_type< unsigned int >(raw_value); } catch (const text::error& e) { throw config::value_error(F("Invalid time delta %s") % raw_value); } set(datetime::delta(seconds, 0)); } /// Converts the contents of the node to a string. /// /// \pre The node must have a value. /// /// \return A string representation of the value held by the node. std::string to_string(void) const { return F("%s") % value().seconds; } /// Pushes the node's value onto the Lua stack. void push_lua(lutok::state& /* state */) const { UNREACHABLE; } /// Sets the value of the node from an entry in the Lua stack. void set_lua(lutok::state& /* state */, const int /* index */) { UNREACHABLE; } }; /// A leaf node that holds a "required user" property. /// /// This node is just a string, but it provides validation of the only allowed /// values. class user_node : public config::string_node { /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { std::auto_ptr< user_node > new_node(new user_node()); new_node->_value = _value; return new_node.release(); } /// Checks a given user textual representation for validity. /// /// \param user The value to validate. /// /// \throw config::value_error If the value is not valid. void validate(const value_type& user) const { if (!user.empty() && user != "root" && user != "unprivileged") throw config::value_error("Invalid required user value"); } }; /// A leaf node that holds a set of paths. /// /// This node type is used to represent the value of the required files and /// required programs, for example, and these do not allow relative paths. We /// check this here. class paths_set_node : public config::base_set_node< fs::path > { /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { std::auto_ptr< paths_set_node > new_node(new paths_set_node()); new_node->_value = _value; return new_node.release(); } /// Converts a single path to the native type. /// /// \param raw_value The value to parse. /// /// \return The parsed value. /// /// \throw config::value_error If the value is invalid. fs::path parse_one(const std::string& raw_value) const { try { return fs::path(raw_value); } catch (const fs::error& e) { throw config::value_error(e.what()); } } /// Checks a collection of paths for validity. /// /// \param paths The value to validate. /// /// \throw config::value_error If the value is not valid. void validate(const value_type& paths) const { for (value_type::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) { const fs::path& path = *iter; if (!path.is_absolute() && path.ncomponents() > 1) throw config::value_error(F("Relative path '%s' not allowed") % *iter); } } }; /// Initializes a tree to hold test case requirements. /// /// \param [in,out] tree The tree to initialize. static void init_tree(config::tree& tree) { tree.define< config::strings_set_node >("allowed_architectures"); tree.define< config::strings_set_node >("allowed_platforms"); tree.define_dynamic("custom"); tree.define< config::string_node >("description"); tree.define< config::string_node >("execenv"); tree.define< config::string_node >("execenv_jail_params"); tree.define< config::bool_node >("has_cleanup"); tree.define< config::bool_node >("is_exclusive"); tree.define< config::strings_set_node >("required_configs"); tree.define< bytes_node >("required_disk_space"); tree.define< paths_set_node >("required_files"); tree.define< bytes_node >("required_memory"); -#ifdef __FreeBSD__ tree.define< config::strings_set_node >("required_kmods"); -#endif tree.define< paths_set_node >("required_programs"); tree.define< user_node >("required_user"); tree.define< delta_node >("timeout"); } /// Sets default values on a tree object. /// /// \param [in,out] tree The tree to configure. static void set_defaults(config::tree& tree) { tree.set< config::strings_set_node >("allowed_architectures", model::strings_set()); tree.set< config::strings_set_node >("allowed_platforms", model::strings_set()); tree.set< config::string_node >("description", ""); tree.set< config::string_node >("execenv", ""); tree.set< config::string_node >("execenv_jail_params", ""); tree.set< config::bool_node >("has_cleanup", false); tree.set< config::bool_node >("is_exclusive", false); tree.set< config::strings_set_node >("required_configs", model::strings_set()); tree.set< bytes_node >("required_disk_space", units::bytes(0)); tree.set< paths_set_node >("required_files", model::paths_set()); tree.set< bytes_node >("required_memory", units::bytes(0)); -#ifdef __FreeBSD__ tree.set< config::strings_set_node >("required_kmods", model::strings_set()); -#endif tree.set< paths_set_node >("required_programs", model::paths_set()); tree.set< user_node >("required_user", ""); // TODO(jmmv): We shouldn't be setting a default timeout like this. See // Issue 5 for details. tree.set< delta_node >("timeout", datetime::delta(300, 0)); } /// Queries the global defaults tree object with lazy initialization. /// /// \return A metadata tree. This object is statically allocated so it is /// acceptable to obtain references to it and its members. const config::tree& get_defaults(void) { if (!defaults) { config::tree props; init_tree(props); set_defaults(props); defaults = props; } return defaults.get(); } /// Looks up a value in a tree with error rewriting. /// /// \tparam NodeType The type of the node. /// \param tree The tree in which to insert the value. /// \param key The key to set. /// /// \return A read-write reference to the value in the node. /// /// \throw model::error If the key is not known or if the value is not valid. template< class NodeType > typename NodeType::value_type& lookup_rw(config::tree& tree, const std::string& key) { try { return tree.lookup_rw< NodeType >(key); } catch (const config::unknown_key_error& e) { throw model::error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::error(F("Invalid value for metadata property %s: %s") % key % e.what()); } } /// Sets a value in a tree with error rewriting. /// /// \tparam NodeType The type of the node. /// \param tree The tree in which to insert the value. /// \param key The key to set. /// \param value The value to set the node to. /// /// \throw model::error If the key is not known or if the value is not valid. template< class NodeType > void set(config::tree& tree, const std::string& key, const typename NodeType::value_type& value) { try { tree.set< NodeType >(key, value); } catch (const config::unknown_key_error& e) { throw model::error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::error(F("Invalid value for metadata property %s: %s") % key % e.what()); } } } // anonymous namespace /// Internal implementation of the metadata class. struct model::metadata::impl : utils::noncopyable { /// Metadata properties. config::tree props; /// Constructor. /// /// \param props_ Metadata properties of the test. impl(const utils::config::tree& props_) : props(props_) { } /// Equality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are equal; false otherwise. bool operator==(const impl& other) const { return (get_defaults().combine(props) == get_defaults().combine(other.props)); } }; /// Constructor. /// /// \param props Metadata properties of the test. model::metadata::metadata(const utils::config::tree& props) : _pimpl(new impl(props)) { } /// Destructor. model::metadata::~metadata(void) { } /// Applies a set of overrides to this metadata object. /// /// \param overrides The overrides to apply. Any values explicitly set in this /// other object will override any possible values set in this object. /// /// \return A new metadata object with the combination. model::metadata model::metadata::apply_overrides(const metadata& overrides) const { return metadata(_pimpl->props.combine(overrides._pimpl->props)); } /// Returns the architectures allowed by the test. /// /// \return Set of architectures, or empty if this does not apply. const model::strings_set& model::metadata::allowed_architectures(void) const { if (_pimpl->props.is_set("allowed_architectures")) { return _pimpl->props.lookup< config::strings_set_node >( "allowed_architectures"); } else { return get_defaults().lookup< config::strings_set_node >( "allowed_architectures"); } } /// Returns the platforms allowed by the test. /// /// \return Set of platforms, or empty if this does not apply. const model::strings_set& model::metadata::allowed_platforms(void) const { if (_pimpl->props.is_set("allowed_platforms")) { return _pimpl->props.lookup< config::strings_set_node >( "allowed_platforms"); } else { return get_defaults().lookup< config::strings_set_node >( "allowed_platforms"); } } /// Returns all the user-defined metadata properties. /// /// \return A key/value map of properties. model::properties_map model::metadata::custom(void) const { return _pimpl->props.all_properties("custom", true); } /// Returns the description of the test. /// /// \return Textual description; may be empty. const std::string& model::metadata::description(void) const { if (_pimpl->props.is_set("description")) { return _pimpl->props.lookup< config::string_node >("description"); } else { return get_defaults().lookup< config::string_node >("description"); } } /// Returns execution environment name. /// /// \return Name of configured execution environment. const std::string& model::metadata::execenv(void) const { if (_pimpl->props.is_set("execenv")) { return _pimpl->props.lookup< config::string_node >("execenv"); } else { return get_defaults().lookup< config::string_node >("execenv"); } } /// Returns execenv jail(8) parameters string to run a test with. /// /// \return String of jail parameters. const std::string& model::metadata::execenv_jail_params(void) const { if (_pimpl->props.is_set("execenv_jail_params")) { return _pimpl->props.lookup< config::string_node >( "execenv_jail_params"); } else { return get_defaults().lookup< config::string_node >( "execenv_jail_params"); } } /// Returns whether the test has a cleanup part or not. /// /// \return True if there is a cleanup part; false otherwise. bool model::metadata::has_cleanup(void) const { if (_pimpl->props.is_set("has_cleanup")) { return _pimpl->props.lookup< config::bool_node >("has_cleanup"); } else { return get_defaults().lookup< config::bool_node >("has_cleanup"); } } /// Returns whether the test has a specific execenv apart from default one. /// /// \return True if there is a non-host execenv configured; false otherwise. bool model::metadata::has_execenv(void) const { const std::string& name = execenv(); return !name.empty() && name != engine::execenv::default_execenv_name; } /// Returns whether the test is exclusive or not. /// /// \return True if the test has to be run on its own, not concurrently with any /// other tests; false otherwise. bool model::metadata::is_exclusive(void) const { if (_pimpl->props.is_set("is_exclusive")) { return _pimpl->props.lookup< config::bool_node >("is_exclusive"); } else { return get_defaults().lookup< config::bool_node >("is_exclusive"); } } /// Returns the list of configuration variables needed by the test. /// /// \return Set of configuration variables. const model::strings_set& model::metadata::required_configs(void) const { if (_pimpl->props.is_set("required_configs")) { return _pimpl->props.lookup< config::strings_set_node >( "required_configs"); } else { return get_defaults().lookup< config::strings_set_node >( "required_configs"); } } /// Returns the amount of free disk space required by the test. /// /// \return Number of bytes, or 0 if this does not apply. const units::bytes& model::metadata::required_disk_space(void) const { if (_pimpl->props.is_set("required_disk_space")) { return _pimpl->props.lookup< bytes_node >("required_disk_space"); } else { return get_defaults().lookup< bytes_node >("required_disk_space"); } } /// Returns the list of files needed by the test. /// /// \return Set of paths. const model::paths_set& model::metadata::required_files(void) const { if (_pimpl->props.is_set("required_files")) { return _pimpl->props.lookup< paths_set_node >("required_files"); } else { return get_defaults().lookup< paths_set_node >("required_files"); } } /// Returns the amount of memory required by the test. /// /// \return Number of bytes, or 0 if this does not apply. const units::bytes& model::metadata::required_memory(void) const { if (_pimpl->props.is_set("required_memory")) { return _pimpl->props.lookup< bytes_node >("required_memory"); } else { return get_defaults().lookup< bytes_node >("required_memory"); } } -#ifdef __FreeBSD__ -/// Returns the list of kmods needed by the test. +/// Returns the list of kernel modules needed by the test. /// -/// \return Set of strings. +/// \return Set of kernel module names. const model::strings_set& model::metadata::required_kmods(void) const { if (_pimpl->props.is_set("required_kmods")) { - return _pimpl->props.lookup< config::strings_set_node >("required_kmods"); + return _pimpl->props.lookup< config::strings_set_node >( + "required_kmods"); } else { - return get_defaults().lookup< config::strings_set_node >("required_kmods"); + return get_defaults().lookup< config::strings_set_node >( + "required_kmods"); } } -#endif /// Returns the list of programs needed by the test. /// /// \return Set of paths. const model::paths_set& model::metadata::required_programs(void) const { if (_pimpl->props.is_set("required_programs")) { return _pimpl->props.lookup< paths_set_node >("required_programs"); } else { return get_defaults().lookup< paths_set_node >("required_programs"); } } /// Returns the user required by the test. /// /// \return One of unprivileged, root or empty. const std::string& model::metadata::required_user(void) const { if (_pimpl->props.is_set("required_user")) { return _pimpl->props.lookup< user_node >("required_user"); } else { return get_defaults().lookup< user_node >("required_user"); } } /// Returns the timeout of the test. /// /// \return A time delta; should be compared to default_timeout to see if it has /// been overriden. const datetime::delta& model::metadata::timeout(void) const { if (_pimpl->props.is_set("timeout")) { return _pimpl->props.lookup< delta_node >("timeout"); } else { return get_defaults().lookup< delta_node >("timeout"); } } /// Externalizes the metadata to a set of key/value textual pairs. /// /// \return A key/value representation of the metadata. model::properties_map model::metadata::to_properties(void) const { const config::tree fully_specified = get_defaults().combine(_pimpl->props); return fully_specified.all_properties(); } /// Equality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are equal; false otherwise. bool model::metadata::operator==(const metadata& other) const { return _pimpl == other._pimpl || *_pimpl == *other._pimpl; } /// Inequality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are different; false otherwise. bool model::metadata::operator!=(const metadata& other) const { return !(*this == other); } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& model::operator<<(std::ostream& output, const metadata& object) { output << "metadata{"; bool first = true; const model::properties_map props = object.to_properties(); for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) { if (!first) output << ", "; output << F("%s=%s") % (*iter).first % text::quote((*iter).second, '\''); first = false; } output << "}"; return output; } /// Internal implementation of the metadata_builder class. struct model::metadata_builder::impl : utils::noncopyable { /// Collection of requirements. config::tree props; /// Whether we have created a metadata object or not. bool built; /// Constructor. impl(void) : built(false) { init_tree(props); } /// Constructor. /// /// \param base The base model to construct a copy from. impl(const model::metadata& base) : props(base._pimpl->props.deep_copy()), built(false) { } }; /// Constructor. model::metadata_builder::metadata_builder(void) : _pimpl(new impl()) { } /// Constructor. model::metadata_builder::metadata_builder(const model::metadata& base) : _pimpl(new impl(base)) { } /// Destructor. model::metadata_builder::~metadata_builder(void) { } /// Accumulates an additional allowed architecture. /// /// \param arch The architecture. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_allowed_architecture(const std::string& arch) { if (!_pimpl->props.is_set("allowed_architectures")) { _pimpl->props.set< config::strings_set_node >( "allowed_architectures", get_defaults().lookup< config::strings_set_node >( "allowed_architectures")); } lookup_rw< config::strings_set_node >( _pimpl->props, "allowed_architectures").insert(arch); return *this; } /// Accumulates an additional allowed platform. /// /// \param platform The platform. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_allowed_platform(const std::string& platform) { if (!_pimpl->props.is_set("allowed_platforms")) { _pimpl->props.set< config::strings_set_node >( "allowed_platforms", get_defaults().lookup< config::strings_set_node >( "allowed_platforms")); } lookup_rw< config::strings_set_node >( _pimpl->props, "allowed_platforms").insert(platform); return *this; } /// Accumulates a single user-defined property. /// /// \param key Name of the property to define. /// \param value Value of the property. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_custom(const std::string& key, const std::string& value) { _pimpl->props.set_string(F("custom.%s") % key, value); return *this; } /// Accumulates an additional required configuration variable. /// /// \param var The name of the configuration variable. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_config(const std::string& var) { if (!_pimpl->props.is_set("required_configs")) { _pimpl->props.set< config::strings_set_node >( "required_configs", get_defaults().lookup< config::strings_set_node >( "required_configs")); } lookup_rw< config::strings_set_node >( _pimpl->props, "required_configs").insert(var); return *this; } /// Accumulates an additional required file. /// /// \param path The path to the file. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_file(const fs::path& path) { if (!_pimpl->props.is_set("required_files")) { _pimpl->props.set< paths_set_node >( "required_files", get_defaults().lookup< paths_set_node >("required_files")); } lookup_rw< paths_set_node >(_pimpl->props, "required_files").insert(path); return *this; } /// Accumulates an additional required program. /// /// \param path The path to the program. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_program(const fs::path& path) { if (!_pimpl->props.is_set("required_programs")) { _pimpl->props.set< paths_set_node >( "required_programs", get_defaults().lookup< paths_set_node >("required_programs")); } lookup_rw< paths_set_node >(_pimpl->props, "required_programs").insert(path); return *this; } /// Sets the architectures allowed by the test. /// /// \param as Set of architectures. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_allowed_architectures( const model::strings_set& as) { set< config::strings_set_node >(_pimpl->props, "allowed_architectures", as); return *this; } /// Sets the platforms allowed by the test. /// /// \return ps Set of platforms. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_allowed_platforms(const model::strings_set& ps) { set< config::strings_set_node >(_pimpl->props, "allowed_platforms", ps); return *this; } /// Sets the user-defined properties. /// /// \param props The custom properties to set. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_custom(const model::properties_map& props) { for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) _pimpl->props.set_string(F("custom.%s") % (*iter).first, (*iter).second); return *this; } /// Sets the description of the test. /// /// \param description Textual description of the test. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_description(const std::string& description) { set< config::string_node >(_pimpl->props, "description", description); return *this; } /// Sets execution environment name. /// /// \param name Execution environment name. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_execenv(const std::string& name) { set< config::string_node >(_pimpl->props, "execenv", name); return *this; } /// Sets execenv jail(8) parameters string to run the test with. /// /// \param params String of jail parameters. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_execenv_jail_params(const std::string& params) { set< config::string_node >(_pimpl->props, "execenv_jail_params", params); return *this; } /// Sets whether the test has a cleanup part or not. /// /// \param cleanup True if the test has a cleanup part; false otherwise. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_has_cleanup(const bool cleanup) { set< config::bool_node >(_pimpl->props, "has_cleanup", cleanup); return *this; } /// Sets whether the test is exclusive or not. /// /// \param exclusive True if the test is exclusive; false otherwise. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_is_exclusive(const bool exclusive) { set< config::bool_node >(_pimpl->props, "is_exclusive", exclusive); return *this; } /// Sets the list of configuration variables needed by the test. /// /// \param vars Set of configuration variables. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_configs(const model::strings_set& vars) { set< config::strings_set_node >(_pimpl->props, "required_configs", vars); return *this; } /// Sets the amount of free disk space required by the test. /// /// \param bytes Number of bytes. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_disk_space(const units::bytes& bytes) { set< bytes_node >(_pimpl->props, "required_disk_space", bytes); return *this; } /// Sets the list of files needed by the test. /// /// \param files Set of paths. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_files(const model::paths_set& files) { set< paths_set_node >(_pimpl->props, "required_files", files); return *this; } /// Sets the amount of memory required by the test. /// /// \param bytes Number of bytes. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_memory(const units::bytes& bytes) { set< bytes_node >(_pimpl->props, "required_memory", bytes); return *this; } /// Sets the list of programs needed by the test. /// /// \param progs Set of paths. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_programs(const model::paths_set& progs) { set< paths_set_node >(_pimpl->props, "required_programs", progs); return *this; } /// Sets the user required by the test. /// /// \param user One of unprivileged, root or empty. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_user(const std::string& user) { set< user_node >(_pimpl->props, "required_user", user); return *this; } /// Sets a metadata property by name from its textual representation. /// /// \param key The property to set. /// \param value The value to set the property to. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid or the key does not exist. model::metadata_builder& model::metadata_builder::set_string(const std::string& key, const std::string& value) { try { _pimpl->props.set_string(key, value); } catch (const config::unknown_key_error& e) { throw model::format_error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::format_error( F("Invalid value for metadata property %s: %s") % key % e.what()); } return *this; } /// Sets the timeout of the test. /// /// \param timeout The timeout to set. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_timeout(const datetime::delta& timeout) { set< delta_node >(_pimpl->props, "timeout", timeout); return *this; } /// Creates a new metadata object. /// /// \pre This has not yet been called. We only support calling this function /// once due to the way the internal tree works: we pass around references, not /// deep copies, so if we allowed a second build, we'd encourage reusing the /// same builder to construct different metadata objects, and this could have /// unintended consequences. /// /// \return The constructed metadata object. model::metadata model::metadata_builder::build(void) const { PRE(!_pimpl->built); _pimpl->built = true; return metadata(_pimpl->props); } diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp index 698613eba869..681f4a79f76c 100644 --- a/contrib/kyua/model/metadata.hpp +++ b/contrib/kyua/model/metadata.hpp @@ -1,141 +1,137 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file model/metadata.hpp /// Definition of the "test metadata" concept. #if !defined(MODEL_METADATA_HPP) #define MODEL_METADATA_HPP #include "model/metadata_fwd.hpp" #include #include #include #include "model/types.hpp" #include "utils/config/tree_fwd.hpp" #include "utils/datetime_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/noncopyable.hpp" #include "utils/units_fwd.hpp" namespace model { /// Collection of metadata properties of a test. class metadata { struct impl; /// Pointer to the shared internal implementation. std::shared_ptr< impl > _pimpl; friend class metadata_builder; public: metadata(const utils::config::tree&); ~metadata(void); metadata apply_overrides(const metadata&) const; const strings_set& allowed_architectures(void) const; const strings_set& allowed_platforms(void) const; model::properties_map custom(void) const; const std::string& description(void) const; const std::string& execenv(void) const; const std::string& execenv_jail_params(void) const; bool has_cleanup(void) const; bool has_execenv(void) const; bool is_exclusive(void) const; const strings_set& required_configs(void) const; const utils::units::bytes& required_disk_space(void) const; const paths_set& required_files(void) const; const utils::units::bytes& required_memory(void) const; -#ifdef __FreeBSD__ const strings_set& required_kmods(void) const; -#endif const paths_set& required_programs(void) const; const std::string& required_user(void) const; const utils::datetime::delta& timeout(void) const; model::properties_map to_properties(void) const; bool operator==(const metadata&) const; bool operator!=(const metadata&) const; }; std::ostream& operator<<(std::ostream&, const metadata&); /// Builder for a metadata object. class metadata_builder : utils::noncopyable { struct impl; /// Pointer to the shared internal implementation. std::auto_ptr< impl > _pimpl; public: metadata_builder(void); explicit metadata_builder(const metadata&); ~metadata_builder(void); metadata_builder& add_allowed_architecture(const std::string&); metadata_builder& add_allowed_platform(const std::string&); metadata_builder& add_custom(const std::string&, const std::string&); metadata_builder& add_required_config(const std::string&); metadata_builder& add_required_file(const utils::fs::path&); metadata_builder& add_required_program(const utils::fs::path&); metadata_builder& set_allowed_architectures(const strings_set&); metadata_builder& set_allowed_platforms(const strings_set&); metadata_builder& set_custom(const model::properties_map&); metadata_builder& set_description(const std::string&); metadata_builder& set_execenv(const std::string&); metadata_builder& set_execenv_jail_params(const std::string&); metadata_builder& set_has_cleanup(const bool); metadata_builder& set_is_exclusive(const bool); metadata_builder& set_required_configs(const strings_set&); metadata_builder& set_required_disk_space(const utils::units::bytes&); metadata_builder& set_required_files(const paths_set&); metadata_builder& set_required_memory(const utils::units::bytes&); -#ifdef __FreeBSD__ metadata_builder& set_required_kmods(const strings_set&); -#endif metadata_builder& set_required_programs(const paths_set&); metadata_builder& set_required_user(const std::string&); metadata_builder& set_string(const std::string&, const std::string&); metadata_builder& set_timeout(const utils::datetime::delta&); metadata build(void) const; }; } // namespace model #endif // !defined(MODEL_METADATA_HPP) diff --git a/contrib/kyua/model/metadata_test.cpp b/contrib/kyua/model/metadata_test.cpp index b4c3dff5b029..bdb1d3655c33 100644 --- a/contrib/kyua/model/metadata_test.cpp +++ b/contrib/kyua/model/metadata_test.cpp @@ -1,465 +1,467 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/metadata.hpp" #include #include #include "model/types.hpp" #include "utils/datetime.hpp" #include "utils/format/containers.ipp" #include "utils/fs/path.hpp" #include "utils/units.hpp" namespace datetime = utils::datetime; namespace fs = utils::fs; namespace units = utils::units; ATF_TEST_CASE_WITHOUT_HEAD(defaults); ATF_TEST_CASE_BODY(defaults) { const model::metadata md = model::metadata_builder().build(); ATF_REQUIRE(md.allowed_architectures().empty()); ATF_REQUIRE(md.allowed_platforms().empty()); ATF_REQUIRE(md.allowed_platforms().empty()); ATF_REQUIRE(md.custom().empty()); ATF_REQUIRE(md.description().empty()); ATF_REQUIRE(!md.has_cleanup()); ATF_REQUIRE(!md.is_exclusive()); ATF_REQUIRE(md.required_configs().empty()); ATF_REQUIRE_EQ(units::bytes(0), md.required_disk_space()); ATF_REQUIRE(md.required_files().empty()); + ATF_REQUIRE(md.required_kmods().empty()); ATF_REQUIRE_EQ(units::bytes(0), md.required_memory()); ATF_REQUIRE(md.required_programs().empty()); ATF_REQUIRE(md.required_user().empty()); ATF_REQUIRE(datetime::delta(300, 0) == md.timeout()); } ATF_TEST_CASE_WITHOUT_HEAD(add); ATF_TEST_CASE_BODY(add) { model::strings_set architectures; architectures.insert("1-architecture"); architectures.insert("2-architecture"); model::strings_set platforms; platforms.insert("1-platform"); platforms.insert("2-platform"); model::properties_map custom; custom["1-custom"] = "first"; custom["2-custom"] = "second"; model::strings_set configs; configs.insert("1-config"); configs.insert("2-config"); model::paths_set files; files.insert(fs::path("1-file")); files.insert(fs::path("2-file")); model::paths_set programs; programs.insert(fs::path("1-program")); programs.insert(fs::path("2-program")); const model::metadata md = model::metadata_builder() .add_allowed_architecture("1-architecture") .add_allowed_platform("1-platform") .add_custom("1-custom", "first") .add_custom("2-custom", "second") .add_required_config("1-config") .add_required_file(fs::path("1-file")) .add_required_program(fs::path("1-program")) .add_allowed_architecture("2-architecture") .add_allowed_platform("2-platform") .add_required_config("2-config") .add_required_file(fs::path("2-file")) .add_required_program(fs::path("2-program")) .build(); ATF_REQUIRE(architectures == md.allowed_architectures()); ATF_REQUIRE(platforms == md.allowed_platforms()); ATF_REQUIRE(custom == md.custom()); ATF_REQUIRE(configs == md.required_configs()); ATF_REQUIRE(files == md.required_files()); ATF_REQUIRE(programs == md.required_programs()); } ATF_TEST_CASE_WITHOUT_HEAD(copy); ATF_TEST_CASE_BODY(copy) { const model::metadata md1 = model::metadata_builder() .add_allowed_architecture("1-architecture") .add_allowed_platform("1-platform") .build(); const model::metadata md2 = model::metadata_builder(md1) .add_allowed_architecture("2-architecture") .build(); ATF_REQUIRE_EQ(1, md1.allowed_architectures().size()); ATF_REQUIRE_EQ(2, md2.allowed_architectures().size()); ATF_REQUIRE_EQ(1, md1.allowed_platforms().size()); ATF_REQUIRE_EQ(1, md2.allowed_platforms().size()); } ATF_TEST_CASE_WITHOUT_HEAD(apply_overrides); ATF_TEST_CASE_BODY(apply_overrides) { const model::metadata md1 = model::metadata_builder() .add_allowed_architecture("1-architecture") .add_allowed_platform("1-platform") .set_description("Explicit description") .build(); const model::metadata md2 = model::metadata_builder() .add_allowed_architecture("2-architecture") .set_description("") .set_timeout(datetime::delta(500, 0)) .build(); const model::metadata merge_1_2 = model::metadata_builder() .add_allowed_architecture("2-architecture") .add_allowed_platform("1-platform") .set_description("") .set_timeout(datetime::delta(500, 0)) .build(); ATF_REQUIRE_EQ(merge_1_2, md1.apply_overrides(md2)); const model::metadata merge_2_1 = model::metadata_builder() .add_allowed_architecture("1-architecture") .add_allowed_platform("1-platform") .set_description("Explicit description") .set_timeout(datetime::delta(500, 0)) .build(); ATF_REQUIRE_EQ(merge_2_1, md2.apply_overrides(md1)); } ATF_TEST_CASE_WITHOUT_HEAD(override_all_with_setters); ATF_TEST_CASE_BODY(override_all_with_setters) { model::strings_set architectures; architectures.insert("the-architecture"); model::strings_set platforms; platforms.insert("the-platforms"); model::properties_map custom; custom["first"] = "hello"; custom["second"] = "bye"; const std::string description = "Some long text"; model::strings_set configs; configs.insert("the-configs"); model::paths_set files; files.insert(fs::path("the-files")); const units::bytes disk_space(6789); const units::bytes memory(12345); model::paths_set programs; programs.insert(fs::path("the-programs")); const std::string user = "root"; const datetime::delta timeout(123, 0); const model::metadata md = model::metadata_builder() .set_allowed_architectures(architectures) .set_allowed_platforms(platforms) .set_custom(custom) .set_description(description) .set_has_cleanup(true) .set_is_exclusive(true) .set_required_configs(configs) .set_required_disk_space(disk_space) .set_required_files(files) .set_required_memory(memory) .set_required_programs(programs) .set_required_user(user) .set_timeout(timeout) .build(); ATF_REQUIRE(architectures == md.allowed_architectures()); ATF_REQUIRE(platforms == md.allowed_platforms()); ATF_REQUIRE(custom == md.custom()); ATF_REQUIRE_EQ(description, md.description()); ATF_REQUIRE(md.has_cleanup()); ATF_REQUIRE(md.is_exclusive()); ATF_REQUIRE(configs == md.required_configs()); ATF_REQUIRE_EQ(disk_space, md.required_disk_space()); ATF_REQUIRE(files == md.required_files()); ATF_REQUIRE_EQ(memory, md.required_memory()); ATF_REQUIRE(programs == md.required_programs()); ATF_REQUIRE_EQ(user, md.required_user()); ATF_REQUIRE(timeout == md.timeout()); } ATF_TEST_CASE_WITHOUT_HEAD(override_all_with_set_string); ATF_TEST_CASE_BODY(override_all_with_set_string) { model::strings_set architectures; architectures.insert("a1"); architectures.insert("a2"); model::strings_set platforms; platforms.insert("p1"); platforms.insert("p2"); model::properties_map custom; custom["user-defined"] = "the-value"; const std::string description = "Another long text"; model::strings_set configs; configs.insert("config-var"); model::paths_set files; files.insert(fs::path("plain")); files.insert(fs::path("/absolute/path")); const units::bytes disk_space( static_cast< uint64_t >(16) * 1024 * 1024 * 1024); const units::bytes memory(1024 * 1024); model::paths_set programs; programs.insert(fs::path("program")); programs.insert(fs::path("/absolute/prog")); const std::string user = "unprivileged"; const datetime::delta timeout(45, 0); const model::metadata md = model::metadata_builder() .set_string("allowed_architectures", "a1 a2") .set_string("allowed_platforms", "p1 p2") .set_string("custom.user-defined", "the-value") .set_string("description", "Another long text") .set_string("has_cleanup", "true") .set_string("is_exclusive", "true") .set_string("required_configs", "config-var") .set_string("required_disk_space", "16G") .set_string("required_files", "plain /absolute/path") .set_string("required_memory", "1M") .set_string("required_programs", "program /absolute/prog") .set_string("required_user", "unprivileged") .set_string("timeout", "45") .build(); ATF_REQUIRE(architectures == md.allowed_architectures()); ATF_REQUIRE(platforms == md.allowed_platforms()); ATF_REQUIRE(custom == md.custom()); ATF_REQUIRE_EQ(description, md.description()); ATF_REQUIRE(md.has_cleanup()); ATF_REQUIRE(md.is_exclusive()); ATF_REQUIRE(configs == md.required_configs()); ATF_REQUIRE_EQ(disk_space, md.required_disk_space()); ATF_REQUIRE(files == md.required_files()); ATF_REQUIRE_EQ(memory, md.required_memory()); ATF_REQUIRE(programs == md.required_programs()); ATF_REQUIRE_EQ(user, md.required_user()); ATF_REQUIRE(timeout == md.timeout()); } ATF_TEST_CASE_WITHOUT_HEAD(to_properties); ATF_TEST_CASE_BODY(to_properties) { const model::metadata md = model::metadata_builder() .add_allowed_architecture("abc") .add_required_file(fs::path("foo")) .add_required_file(fs::path("bar")) .set_required_memory(units::bytes(1024)) .add_custom("foo", "bar") .build(); model::properties_map props; props["allowed_architectures"] = "abc"; props["allowed_platforms"] = ""; props["custom.foo"] = "bar"; props["description"] = ""; props["execenv"] = ""; props["execenv_jail_params"] = ""; props["has_cleanup"] = "false"; props["is_exclusive"] = "false"; props["required_configs"] = ""; props["required_disk_space"] = "0"; props["required_files"] = "bar foo"; + props["required_kmods"] = ""; props["required_memory"] = "1.00K"; props["required_programs"] = ""; props["required_user"] = ""; props["timeout"] = "300"; ATF_REQUIRE_EQ(props, md.to_properties()); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty); ATF_TEST_CASE_BODY(operators_eq_and_ne__empty) { const model::metadata md1 = model::metadata_builder().build(); const model::metadata md2 = model::metadata_builder().build(); ATF_REQUIRE( md1 == md2); ATF_REQUIRE(!(md1 != md2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__copy); ATF_TEST_CASE_BODY(operators_eq_and_ne__copy) { const model::metadata md1 = model::metadata_builder() .add_custom("foo", "bar") .build(); const model::metadata md2 = md1; ATF_REQUIRE( md1 == md2); ATF_REQUIRE(!(md1 != md2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__equal); ATF_TEST_CASE_BODY(operators_eq_and_ne__equal) { const model::metadata md1 = model::metadata_builder() .add_allowed_architecture("a") .add_allowed_architecture("b") .add_custom("foo", "bar") .build(); const model::metadata md2 = model::metadata_builder() .add_allowed_architecture("b") .add_allowed_architecture("a") .add_custom("foo", "bar") .build(); ATF_REQUIRE( md1 == md2); ATF_REQUIRE(!(md1 != md2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__equal_overriden_defaults); ATF_TEST_CASE_BODY(operators_eq_and_ne__equal_overriden_defaults) { const model::metadata defaults = model::metadata_builder().build(); const model::metadata md1 = model::metadata_builder() .add_allowed_architecture("a") .build(); const model::metadata md2 = model::metadata_builder() .add_allowed_architecture("a") .set_timeout(defaults.timeout()) .build(); ATF_REQUIRE( md1 == md2); ATF_REQUIRE(!(md1 != md2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__different); ATF_TEST_CASE_BODY(operators_eq_and_ne__different) { const model::metadata md1 = model::metadata_builder() .add_custom("foo", "bar") .build(); const model::metadata md2 = model::metadata_builder() .add_custom("foo", "bar") .add_custom("baz", "foo bar") .build(); ATF_REQUIRE(!(md1 == md2)); ATF_REQUIRE( md1 != md2); } ATF_TEST_CASE_WITHOUT_HEAD(output__defaults); ATF_TEST_CASE_BODY(output__defaults) { std::ostringstream str; str << model::metadata_builder().build(); ATF_REQUIRE_EQ("metadata{allowed_architectures='', allowed_platforms='', " "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', " "required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}", str.str()); } ATF_TEST_CASE_WITHOUT_HEAD(output__some_values); ATF_TEST_CASE_BODY(output__some_values) { std::ostringstream str; str << model::metadata_builder() .add_allowed_architecture("abc") .add_required_file(fs::path("foo")) .add_required_file(fs::path("bar")) .set_is_exclusive(true) .set_required_memory(units::bytes(1024)) .build(); ATF_REQUIRE_EQ( "metadata{allowed_architectures='abc', allowed_platforms='', " "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='true', " "required_configs='', " "required_disk_space='0', required_files='bar foo', " - "required_memory='1.00K', " + "required_kmods='', required_memory='1.00K', " "required_programs='', required_user='', timeout='300'}", str.str()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, defaults); ATF_ADD_TEST_CASE(tcs, add); ATF_ADD_TEST_CASE(tcs, copy); ATF_ADD_TEST_CASE(tcs, apply_overrides); ATF_ADD_TEST_CASE(tcs, override_all_with_setters); ATF_ADD_TEST_CASE(tcs, override_all_with_set_string); ATF_ADD_TEST_CASE(tcs, to_properties); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__copy); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__equal); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__equal_overriden_defaults); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__different); ATF_ADD_TEST_CASE(tcs, output__defaults); ATF_ADD_TEST_CASE(tcs, output__some_values); // TODO(jmmv): Add tests for error conditions (invalid keys and invalid // values). } diff --git a/contrib/kyua/model/test_case_test.cpp b/contrib/kyua/model/test_case_test.cpp index 1e2597d1501e..29df7ee35863 100644 --- a/contrib/kyua/model/test_case_test.cpp +++ b/contrib/kyua/model/test_case_test.cpp @@ -1,264 +1,264 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/test_case.hpp" #include #include #include "model/metadata.hpp" #include "model/test_result.hpp" #include "utils/datetime.hpp" #include "utils/format/containers.ipp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" namespace datetime = utils::datetime; namespace fs = utils::fs; ATF_TEST_CASE_WITHOUT_HEAD(test_case__ctor_and_getters) ATF_TEST_CASE_BODY(test_case__ctor_and_getters) { const model::metadata md = model::metadata_builder() .add_custom("first", "value") .build(); const model::test_case test_case("foo", md); ATF_REQUIRE_EQ("foo", test_case.name()); ATF_REQUIRE_EQ(md, test_case.get_metadata()); ATF_REQUIRE_EQ(md, test_case.get_raw_metadata()); } ATF_TEST_CASE_WITHOUT_HEAD(test_case__fake_result) ATF_TEST_CASE_BODY(test_case__fake_result) { const model::test_result result(model::test_result_skipped, "Some reason"); const model::test_case test_case("__foo__", "Some description", result); ATF_REQUIRE_EQ("__foo__", test_case.name()); ATF_REQUIRE_EQ(result, test_case.fake_result().get()); const model::metadata exp_metadata = model::metadata_builder() .set_description("Some description") .build(); ATF_REQUIRE_EQ(exp_metadata, test_case.get_metadata()); ATF_REQUIRE_EQ(exp_metadata, test_case.get_raw_metadata()); } ATF_TEST_CASE_WITHOUT_HEAD(test_case__apply_metadata_overrides__real_test_case) ATF_TEST_CASE_BODY(test_case__apply_metadata_overrides__real_test_case) { const model::metadata overrides = model::metadata_builder() .add_required_config("the-variable") .set_description("The test case") .build(); const model::test_case base_test_case("foo", overrides); const model::metadata defaults = model::metadata_builder() .set_description("Default description") .set_timeout(datetime::delta(10, 0)) .build(); const model::test_case test_case = base_test_case.apply_metadata_defaults( &defaults); const model::metadata expected = model::metadata_builder() .add_required_config("the-variable") .set_description("The test case") .set_timeout(datetime::delta(10, 0)) .build(); ATF_REQUIRE_EQ(expected, test_case.get_metadata()); ATF_REQUIRE_EQ(overrides, test_case.get_raw_metadata()); // Ensure the original (although immutable) test case was not touched. ATF_REQUIRE_EQ(overrides, base_test_case.get_metadata()); } ATF_TEST_CASE_WITHOUT_HEAD(test_case__apply_metadata_overrides__fake_test_case) ATF_TEST_CASE_BODY(test_case__apply_metadata_overrides__fake_test_case) { const model::test_result result(model::test_result_skipped, "Irrelevant"); const model::test_case base_test_case("__foo__", "Fake test", result); const model::metadata overrides = model::metadata_builder() .set_description("Fake test") .build(); const model::metadata defaults = model::metadata_builder() .add_allowed_platform("some-value") .set_description("Default description") .build(); const model::test_case test_case = base_test_case.apply_metadata_defaults( &defaults); const model::metadata expected = model::metadata_builder() .add_allowed_platform("some-value") .set_description("Fake test") .build(); ATF_REQUIRE_EQ(expected, test_case.get_metadata()); ATF_REQUIRE_EQ(overrides, test_case.get_raw_metadata()); } ATF_TEST_CASE_WITHOUT_HEAD(test_case__operators_eq_and_ne__copy); ATF_TEST_CASE_BODY(test_case__operators_eq_and_ne__copy) { const model::test_case tc1("name", model::metadata_builder().build()); const model::test_case tc2 = tc1; ATF_REQUIRE( tc1 == tc2); ATF_REQUIRE(!(tc1 != tc2)); } ATF_TEST_CASE_WITHOUT_HEAD(test_case__operators_eq_and_ne__not_copy); ATF_TEST_CASE_BODY(test_case__operators_eq_and_ne__not_copy) { const std::string base_name("name"); const model::metadata base_metadata = model::metadata_builder() .add_custom("foo", "bar") .build(); const model::test_case base_tc(base_name, base_metadata); // Construct with all same values. { const model::test_case other_tc(base_name, base_metadata); ATF_REQUIRE( base_tc == other_tc); ATF_REQUIRE(!(base_tc != other_tc)); } // Construct with all same values but different metadata objects. { const model::metadata other_metadata = model::metadata_builder() .add_custom("foo", "bar") .set_timeout(base_metadata.timeout()) .build(); const model::test_case other_tc(base_name, other_metadata); ATF_REQUIRE( base_tc == other_tc); ATF_REQUIRE(!(base_tc != other_tc)); } // Different name. { const model::test_case other_tc("other", base_metadata); ATF_REQUIRE(!(base_tc == other_tc)); ATF_REQUIRE( base_tc != other_tc); } // Different metadata. { const model::test_case other_tc(base_name, model::metadata_builder().build()); ATF_REQUIRE(!(base_tc == other_tc)); ATF_REQUIRE( base_tc != other_tc); } } ATF_TEST_CASE_WITHOUT_HEAD(test_case__output); ATF_TEST_CASE_BODY(test_case__output) { const model::test_case tc1( "the-name", model::metadata_builder() .add_allowed_platform("foo").add_custom("bar", "baz").build()); std::ostringstream str; str << tc1; ATF_REQUIRE_EQ( "test_case{name='the-name', " "metadata=metadata{allowed_architectures='', allowed_platforms='foo', " "custom.bar='baz', description='', execenv='', execenv_jail_params='', " "has_cleanup='false', " "is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}}", str.str()); } ATF_TEST_CASE_WITHOUT_HEAD(test_cases_map__builder); ATF_TEST_CASE_BODY(test_cases_map__builder) { model::test_cases_map_builder builder; model::test_cases_map exp_test_cases; ATF_REQUIRE_EQ(exp_test_cases, builder.build()); builder.add("default-metadata"); { const model::test_case tc1("default-metadata", model::metadata_builder().build()); exp_test_cases.insert( model::test_cases_map::value_type(tc1.name(), tc1)); } ATF_REQUIRE_EQ(exp_test_cases, builder.build()); builder.add("with-metadata", model::metadata_builder().set_description("text").build()); { const model::test_case tc1("with-metadata", model::metadata_builder() .set_description("text").build()); exp_test_cases.insert( model::test_cases_map::value_type(tc1.name(), tc1)); } ATF_REQUIRE_EQ(exp_test_cases, builder.build()); const model::test_case tc1("fully_built", model::metadata_builder() .set_description("something else").build()); builder.add(tc1); exp_test_cases.insert(model::test_cases_map::value_type(tc1.name(), tc1)); ATF_REQUIRE_EQ(exp_test_cases, builder.build()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, test_case__ctor_and_getters); ATF_ADD_TEST_CASE(tcs, test_case__fake_result); ATF_ADD_TEST_CASE(tcs, test_case__apply_metadata_overrides__real_test_case); ATF_ADD_TEST_CASE(tcs, test_case__apply_metadata_overrides__fake_test_case); ATF_ADD_TEST_CASE(tcs, test_case__operators_eq_and_ne__copy); ATF_ADD_TEST_CASE(tcs, test_case__operators_eq_and_ne__not_copy); ATF_ADD_TEST_CASE(tcs, test_case__output); ATF_ADD_TEST_CASE(tcs, test_cases_map__builder); } diff --git a/contrib/kyua/model/test_program_test.cpp b/contrib/kyua/model/test_program_test.cpp index ddfbc430387c..f7a84d770fc0 100644 --- a/contrib/kyua/model/test_program_test.cpp +++ b/contrib/kyua/model/test_program_test.cpp @@ -1,714 +1,714 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/test_program.hpp" extern "C" { #include #include } #include #include #include #include "model/exceptions.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_result.hpp" #include "utils/env.hpp" #include "utils/format/containers.ipp" #include "utils/format/macros.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" namespace fs = utils::fs; namespace { /// Test program that sets its test cases lazily. /// /// This test class exists to test the behavior of a test_program object when /// the class is extended to offer lazy loading of test cases. We simulate such /// lazy loading here by storing the list of test cases aside at construction /// time and later setting it lazily the first time test_cases() is called. class lazy_test_program : public model::test_program { /// Whether set_test_cases() has yet been called or not. mutable bool _set_test_cases_called; /// The list of test cases for this test program. /// /// Only use this in the call to set_test_cases(). All other reads of the /// test cases list should happen via the parent class' test_cases() method. model::test_cases_map _lazy_test_cases; public: /// Constructs a new test program. /// /// \param interface_name_ Name of the test program interface. /// \param binary_ The name of the test program binary relative to root_. /// \param root_ The root of the test suite containing the test program. /// \param test_suite_name_ The name of the test suite. /// \param metadata_ Metadata of the test program. /// \param test_cases_ The collection of test cases in the test program. lazy_test_program(const std::string& interface_name_, const utils::fs::path& binary_, const utils::fs::path& root_, const std::string& test_suite_name_, const model::metadata& metadata_, const model::test_cases_map& test_cases_) : test_program(interface_name_, binary_, root_, test_suite_name_, metadata_, model::test_cases_map()), _set_test_cases_called(false), _lazy_test_cases(test_cases_) { } /// Lazily sets the test cases on the parent and returns them. /// /// \return The list of test cases. const model::test_cases_map& test_cases(void) const { if (!_set_test_cases_called) { const_cast< lazy_test_program* >(this)->set_test_cases( _lazy_test_cases); _set_test_cases_called = true; } return test_program::test_cases(); } }; } // anonymous namespace /// Runs a ctor_and_getters test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_ctor_and_getters(void) { const model::metadata tp_md = model::metadata_builder() .add_custom("first", "foo") .add_custom("second", "bar") .build(); const model::metadata tc_md = model::metadata_builder() .add_custom("first", "baz") .build(); const TestProgram test_program( "mock", fs::path("binary"), fs::path("root"), "suite-name", tp_md, model::test_cases_map_builder().add("foo", tc_md).build()); ATF_REQUIRE_EQ("mock", test_program.interface_name()); ATF_REQUIRE_EQ(fs::path("binary"), test_program.relative_path()); ATF_REQUIRE_EQ(fs::current_path() / "root/binary", test_program.absolute_path()); ATF_REQUIRE_EQ(fs::path("root"), test_program.root()); ATF_REQUIRE_EQ("suite-name", test_program.test_suite_name()); ATF_REQUIRE_EQ(tp_md, test_program.get_metadata()); const model::metadata exp_tc_md = model::metadata_builder() .add_custom("first", "baz") .add_custom("second", "bar") .build(); const model::test_cases_map exp_tcs = model::test_cases_map_builder() .add("foo", exp_tc_md) .build(); ATF_REQUIRE_EQ(exp_tcs, test_program.test_cases()); } ATF_TEST_CASE_WITHOUT_HEAD(ctor_and_getters); ATF_TEST_CASE_BODY(ctor_and_getters) { check_ctor_and_getters< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__ctor_and_getters); ATF_TEST_CASE_BODY(derived__ctor_and_getters) { check_ctor_and_getters< lazy_test_program >(); } /// Runs a find_ok test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_find_ok(void) { const model::test_case test_case("main", model::metadata_builder().build()); const TestProgram test_program( "mock", fs::path("non-existent"), fs::path("."), "suite-name", model::metadata_builder().build(), model::test_cases_map_builder().add(test_case).build()); const model::test_case& found_test_case = test_program.find("main"); ATF_REQUIRE_EQ(test_case, found_test_case); } ATF_TEST_CASE_WITHOUT_HEAD(find__ok); ATF_TEST_CASE_BODY(find__ok) { check_find_ok< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__find__ok); ATF_TEST_CASE_BODY(derived__find__ok) { check_find_ok< lazy_test_program >(); } /// Runs a find_missing test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_find_missing(void) { const TestProgram test_program( "mock", fs::path("non-existent"), fs::path("."), "suite-name", model::metadata_builder().build(), model::test_cases_map_builder().add("main").build()); ATF_REQUIRE_THROW_RE(model::not_found_error, "case.*abc.*program.*non-existent", test_program.find("abc")); } ATF_TEST_CASE_WITHOUT_HEAD(find__missing); ATF_TEST_CASE_BODY(find__missing) { check_find_missing< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__find__missing); ATF_TEST_CASE_BODY(derived__find__missing) { check_find_missing< lazy_test_program >(); } /// Runs a metadata_inheritance test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_metadata_inheritance(void) { const model::test_cases_map test_cases = model::test_cases_map_builder() .add("inherit-all") .add("inherit-some", model::metadata_builder() .set_description("Overriden description") .build()) .add("inherit-none", model::metadata_builder() .add_allowed_architecture("overriden-arch") .add_allowed_platform("overriden-platform") .set_description("Overriden description") .build()) .build(); const model::metadata metadata = model::metadata_builder() .add_allowed_architecture("base-arch") .set_description("Base description") .build(); const TestProgram test_program( "plain", fs::path("non-existent"), fs::path("."), "suite-name", metadata, test_cases); { const model::metadata exp_metadata = model::metadata_builder() .add_allowed_architecture("base-arch") .set_description("Base description") .build(); ATF_REQUIRE_EQ(exp_metadata, test_program.find("inherit-all").get_metadata()); } { const model::metadata exp_metadata = model::metadata_builder() .add_allowed_architecture("base-arch") .set_description("Overriden description") .build(); ATF_REQUIRE_EQ(exp_metadata, test_program.find("inherit-some").get_metadata()); } { const model::metadata exp_metadata = model::metadata_builder() .add_allowed_architecture("overriden-arch") .add_allowed_platform("overriden-platform") .set_description("Overriden description") .build(); ATF_REQUIRE_EQ(exp_metadata, test_program.find("inherit-none").get_metadata()); } } ATF_TEST_CASE_WITHOUT_HEAD(metadata_inheritance); ATF_TEST_CASE_BODY(metadata_inheritance) { check_metadata_inheritance< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__metadata_inheritance); ATF_TEST_CASE_BODY(derived__metadata_inheritance) { check_metadata_inheritance< lazy_test_program >(); } /// Runs a operators_eq_and_ne__copy test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_operators_eq_and_ne__copy(void) { const TestProgram tp1( "plain", fs::path("non-existent"), fs::path("."), "suite-name", model::metadata_builder().build(), model::test_cases_map()); const TestProgram tp2 = tp1; ATF_REQUIRE( tp1 == tp2); ATF_REQUIRE(!(tp1 != tp2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__copy); ATF_TEST_CASE_BODY(operators_eq_and_ne__copy) { check_operators_eq_and_ne__copy< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__operators_eq_and_ne__copy); ATF_TEST_CASE_BODY(derived__operators_eq_and_ne__copy) { check_operators_eq_and_ne__copy< lazy_test_program >(); } /// Runs a operators_eq_and_ne__not_copy test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_operators_eq_and_ne__not_copy(void) { const std::string base_interface("plain"); const fs::path base_relative_path("the/test/program"); const fs::path base_root("/the/root"); const std::string base_test_suite("suite-name"); const model::metadata base_metadata = model::metadata_builder() .add_custom("foo", "bar") .build(); const model::test_cases_map base_tcs = model::test_cases_map_builder() .add("main", model::metadata_builder() .add_custom("second", "baz") .build()) .build(); const TestProgram base_tp( base_interface, base_relative_path, base_root, base_test_suite, base_metadata, base_tcs); // Construct with all same values. { const model::test_cases_map other_tcs = model::test_cases_map_builder() .add("main", model::metadata_builder() .add_custom("second", "baz") .build()) .build(); const TestProgram other_tp( base_interface, base_relative_path, base_root, base_test_suite, base_metadata, other_tcs); ATF_REQUIRE( base_tp == other_tp); ATF_REQUIRE(!(base_tp != other_tp)); } // Construct with same final metadata values but using a different // intermediate representation. The original test program has one property // in the base test program definition and another in the test case; here, // we put both definitions explicitly in the test case. { const model::test_cases_map other_tcs = model::test_cases_map_builder() .add("main", model::metadata_builder() .add_custom("foo", "bar") .add_custom("second", "baz") .build()) .build(); const TestProgram other_tp( base_interface, base_relative_path, base_root, base_test_suite, base_metadata, other_tcs); ATF_REQUIRE( base_tp == other_tp); ATF_REQUIRE(!(base_tp != other_tp)); } // Different interface. { const TestProgram other_tp( "atf", base_relative_path, base_root, base_test_suite, base_metadata, base_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } // Different relative path. { const TestProgram other_tp( base_interface, fs::path("a/b/c"), base_root, base_test_suite, base_metadata, base_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } // Different root. { const TestProgram other_tp( base_interface, base_relative_path, fs::path("."), base_test_suite, base_metadata, base_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } // Different test suite. { const TestProgram other_tp( base_interface, base_relative_path, base_root, "different-suite", base_metadata, base_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } // Different metadata. { const TestProgram other_tp( base_interface, base_relative_path, base_root, base_test_suite, model::metadata_builder().build(), base_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } // Different test cases. { const model::test_cases_map other_tcs = model::test_cases_map_builder() .add("foo").build(); const TestProgram other_tp( base_interface, base_relative_path, base_root, base_test_suite, base_metadata, other_tcs); ATF_REQUIRE(!(base_tp == other_tp)); ATF_REQUIRE( base_tp != other_tp); } } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__not_copy); ATF_TEST_CASE_BODY(operators_eq_and_ne__not_copy) { check_operators_eq_and_ne__not_copy< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__operators_eq_and_ne__not_copy); ATF_TEST_CASE_BODY(derived__operators_eq_and_ne__not_copy) { check_operators_eq_and_ne__not_copy< lazy_test_program >(); } /// Runs a operator_lt test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_operator_lt(void) { const TestProgram tp1( "plain", fs::path("a/b/c"), fs::path("/foo/bar"), "suite-name", model::metadata_builder().build(), model::test_cases_map()); const TestProgram tp2( "atf", fs::path("c"), fs::path("/foo/bar"), "suite-name", model::metadata_builder().build(), model::test_cases_map()); const TestProgram tp3( "plain", fs::path("a/b/c"), fs::path("/abc"), "suite-name", model::metadata_builder().build(), model::test_cases_map()); ATF_REQUIRE(!(tp1 < tp1)); ATF_REQUIRE( tp1 < tp2); ATF_REQUIRE(!(tp2 < tp1)); ATF_REQUIRE(!(tp1 < tp3)); ATF_REQUIRE( tp3 < tp1); // And now, test the actual reason why we want to have an < overload by // attempting to put the various programs in a set. std::set< TestProgram > programs; programs.insert(tp1); programs.insert(tp2); programs.insert(tp3); } ATF_TEST_CASE_WITHOUT_HEAD(operator_lt); ATF_TEST_CASE_BODY(operator_lt) { check_operator_lt< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__operator_lt); ATF_TEST_CASE_BODY(derived__operator_lt) { check_operator_lt< lazy_test_program >(); } /// Runs a output__no_test_cases test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_output__no_test_cases(void) { TestProgram tp( "plain", fs::path("binary/path"), fs::path("/the/root"), "suite-name", model::metadata_builder().add_allowed_architecture("a").build(), model::test_cases_map()); std::ostringstream str; str << tp; ATF_REQUIRE_EQ( "test_program{interface='plain', binary='binary/path', " "root='/the/root', test_suite='suite-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}, " "test_cases=map()}", str.str()); } ATF_TEST_CASE_WITHOUT_HEAD(output__no_test_cases); ATF_TEST_CASE_BODY(output__no_test_cases) { check_output__no_test_cases< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__output__no_test_cases); ATF_TEST_CASE_BODY(derived__output__no_test_cases) { check_output__no_test_cases< lazy_test_program >(); } /// Runs a output__some_test_cases test. /// /// \tparam TestProgram Either model::test_program or lazy_test_program. template< class TestProgram > static void check_output__some_test_cases(void) { const model::test_cases_map test_cases = model::test_cases_map_builder() .add("the-name", model::metadata_builder() .add_allowed_platform("foo") .add_custom("bar", "baz") .build()) .add("another-name") .build(); const TestProgram tp = TestProgram( "plain", fs::path("binary/path"), fs::path("/the/root"), "suite-name", model::metadata_builder().add_allowed_architecture("a").build(), test_cases); std::ostringstream str; str << tp; ATF_REQUIRE_EQ( "test_program{interface='plain', binary='binary/path', " "root='/the/root', test_suite='suite-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}, " "test_cases=map(" "another-name=test_case{name='another-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}}, " "the-name=test_case{name='the-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='foo', " "custom.bar='baz', description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}})}", str.str()); } ATF_TEST_CASE_WITHOUT_HEAD(output__some_test_cases); ATF_TEST_CASE_BODY(output__some_test_cases) { check_output__some_test_cases< model::test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(derived__output__some_test_cases); ATF_TEST_CASE_BODY(derived__output__some_test_cases) { check_output__some_test_cases< lazy_test_program >(); } ATF_TEST_CASE_WITHOUT_HEAD(builder__defaults); ATF_TEST_CASE_BODY(builder__defaults) { const model::test_program expected( "mock", fs::path("non-existent"), fs::path("."), "suite-name", model::metadata_builder().build(), model::test_cases_map()); const model::test_program built = model::test_program_builder( "mock", fs::path("non-existent"), fs::path("."), "suite-name") .build(); ATF_REQUIRE_EQ(built, expected); } ATF_TEST_CASE_WITHOUT_HEAD(builder__overrides); ATF_TEST_CASE_BODY(builder__overrides) { const model::metadata md = model::metadata_builder() .add_custom("foo", "bar") .build(); const model::test_cases_map tcs = model::test_cases_map_builder() .add("first") .add("second", md) .build(); const model::test_program expected( "mock", fs::path("binary"), fs::path("root"), "suite-name", md, tcs); const model::test_program built = model::test_program_builder( "mock", fs::path("binary"), fs::path("root"), "suite-name") .add_test_case("first") .add_test_case("second", md) .set_metadata(md) .build(); ATF_REQUIRE_EQ(built, expected); } ATF_TEST_CASE_WITHOUT_HEAD(builder__ptr); ATF_TEST_CASE_BODY(builder__ptr) { const model::test_program expected( "mock", fs::path("non-existent"), fs::path("."), "suite-name", model::metadata_builder().build(), model::test_cases_map()); const model::test_program_ptr built = model::test_program_builder( "mock", fs::path("non-existent"), fs::path("."), "suite-name") .build_ptr(); ATF_REQUIRE_EQ(*built, expected); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, ctor_and_getters); ATF_ADD_TEST_CASE(tcs, find__ok); ATF_ADD_TEST_CASE(tcs, find__missing); ATF_ADD_TEST_CASE(tcs, metadata_inheritance); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__copy); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__not_copy); ATF_ADD_TEST_CASE(tcs, operator_lt); ATF_ADD_TEST_CASE(tcs, output__no_test_cases); ATF_ADD_TEST_CASE(tcs, output__some_test_cases); ATF_ADD_TEST_CASE(tcs, derived__ctor_and_getters); ATF_ADD_TEST_CASE(tcs, derived__find__ok); ATF_ADD_TEST_CASE(tcs, derived__find__missing); ATF_ADD_TEST_CASE(tcs, derived__metadata_inheritance); ATF_ADD_TEST_CASE(tcs, derived__operators_eq_and_ne__copy); ATF_ADD_TEST_CASE(tcs, derived__operators_eq_and_ne__not_copy); ATF_ADD_TEST_CASE(tcs, derived__operator_lt); ATF_ADD_TEST_CASE(tcs, derived__output__no_test_cases); ATF_ADD_TEST_CASE(tcs, derived__output__some_test_cases); ATF_ADD_TEST_CASE(tcs, builder__defaults); ATF_ADD_TEST_CASE(tcs, builder__overrides); ATF_ADD_TEST_CASE(tcs, builder__ptr); } diff --git a/contrib/kyua/os/freebsd/main.cpp b/contrib/kyua/os/freebsd/main.cpp index 13e5dcf0e023..700284b64b78 100644 --- a/contrib/kyua/os/freebsd/main.cpp +++ b/contrib/kyua/os/freebsd/main.cpp @@ -1,54 +1,65 @@ // Copyright 2024 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "os/freebsd/main.hpp" #include "engine/execenv/execenv.hpp" #include "os/freebsd/execenv_jail_manager.hpp" +#include "engine/requirements.hpp" +#include "os/freebsd/reqs_checker_kmods.hpp" + namespace execenv = engine::execenv; /// FreeBSD related features initialization. /// /// \param argc The number of arguments passed on the command line. /// \param argv NULL-terminated array containing the command line arguments. /// /// \return 0 on success, some other integer on error. /// /// \throw std::exception This throws any uncaught exception. Such exceptions /// are bugs, but we let them propagate so that the runtime will abort and /// dump core. int freebsd::main(const int, const char* const* const) { execenv::register_execenv( std::shared_ptr< execenv::manager >(new freebsd::execenv_jail_manager()) ); +#ifdef __FreeBSD__ + engine::register_reqs_checker( + std::shared_ptr< engine::reqs_checker >( + new freebsd::reqs_checker_kmods() + ) + ); +#endif + return 0; } diff --git a/contrib/kyua/engine/requirements.hpp b/contrib/kyua/os/freebsd/reqs_checker_kmods.cpp similarity index 69% copy from contrib/kyua/engine/requirements.hpp copy to contrib/kyua/os/freebsd/reqs_checker_kmods.cpp index a36a938b3034..3ae3446a7815 100644 --- a/contrib/kyua/engine/requirements.hpp +++ b/contrib/kyua/os/freebsd/reqs_checker_kmods.cpp @@ -1,51 +1,50 @@ -// Copyright 2012 The Kyua Authors. +// Copyright 2025 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -/// \file engine/requirements.hpp -/// Handling of test case requirements. - -#if !defined(ENGINE_REQUIREMENTS_HPP) -#define ENGINE_REQUIREMENTS_HPP - -#include - -#include "model/metadata_fwd.hpp" -#include "utils/config/tree_fwd.hpp" -#include "utils/fs/path_fwd.hpp" - -namespace engine { - - -std::string check_reqs(const model::metadata&, const utils::config::tree&, - const std::string&, const utils::fs::path&); - - -} // namespace engine - - -#endif // !defined(ENGINE_REQUIREMENTS_HPP) +#include "os/freebsd/reqs_checker_kmods.hpp" + +#include "model/metadata.hpp" + +extern "C" { +#include "libutil.h" +} + +std::string +freebsd::reqs_checker_kmods::exec(const model::metadata& md, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const +{ + std::string reason = ""; + for (auto& kmod : md.required_kmods()) + if (!::kld_isloaded((kmod).c_str())) + reason += " " + kmod; + if (!reason.empty()) + reason = "Required kmods are not loaded:" + reason + "."; + return reason; +} diff --git a/contrib/kyua/engine/requirements.hpp b/contrib/kyua/os/freebsd/reqs_checker_kmods.hpp similarity index 73% copy from contrib/kyua/engine/requirements.hpp copy to contrib/kyua/os/freebsd/reqs_checker_kmods.hpp index a36a938b3034..8c7c69e35d07 100644 --- a/contrib/kyua/engine/requirements.hpp +++ b/contrib/kyua/os/freebsd/reqs_checker_kmods.hpp @@ -1,51 +1,54 @@ -// Copyright 2012 The Kyua Authors. +// Copyright 2025 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -/// \file engine/requirements.hpp -/// Handling of test case requirements. +/// \file os/freebsd/reqs_checker_kmods.hpp +/// FreeBSD kernel module requirement checker. -#if !defined(ENGINE_REQUIREMENTS_HPP) -#define ENGINE_REQUIREMENTS_HPP - -#include +#if !defined(FREEBSD_REQS_CHECKER_KMODS) +#define FREEBSD_REQS_CHECKER_KMODS +#include "engine/requirements.hpp" #include "model/metadata_fwd.hpp" #include "utils/config/tree_fwd.hpp" #include "utils/fs/path_fwd.hpp" -namespace engine { - +namespace freebsd { -std::string check_reqs(const model::metadata&, const utils::config::tree&, - const std::string&, const utils::fs::path&); +class reqs_checker_kmods : public engine::reqs_checker { +public: + std::string exec(const model::metadata&, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const; +}; -} // namespace engine +} // namespace freebsd -#endif // !defined(ENGINE_REQUIREMENTS_HPP) +#endif // !defined(FREEBSD_REQS_CHECKER_KMODS) diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile index 6db9da94e69c..d1cb2a51f667 100644 --- a/usr.bin/kyua/Makefile +++ b/usr.bin/kyua/Makefile @@ -1,225 +1,226 @@ .include KYUA_CONFDIR= /etc/kyua KYUA_DOCDIR= /usr/share/doc/kyua KYUA_EGDIR= /usr/share/examples/kyua KYUA_MISCDIR= /usr/share/kyua/misc KYUA_STOREDIR= /usr/share/kyua/store KYUA_VERSION= 0.13 KYUA_SRCDIR= ${SRCTOP}/contrib/kyua .PATH: ${KYUA_SRCDIR} PACKAGE= tests PROG_CXX= kyua SRCS= main.cpp LIBADD= lutok sqlite3 util MAN= kyua-about.1 \ kyua-config.1 \ kyua-db-exec.1 \ kyua-db-migrate.1 \ kyua-debug.1 \ kyua-help.1 \ kyua-list.1 \ kyua-report-html.1 \ kyua-report-junit.1 \ kyua-report.1 \ kyua-test.1 \ kyua.1 \ kyua.conf.5 \ kyuafile.5 CFLAGS+= -I${KYUA_SRCDIR} -I${.CURDIR} CFLAGS+= -I${SRCTOP}/contrib/lutok/include CFLAGS+= -I${SRCTOP}/contrib/sqlite3 # kyua uses auto_ptr CFLAGS+= -Wno-deprecated-declarations CXXSTD= c++11 CFLAGS+= -DHAVE_CONFIG_H # We compile the kyua libraries as part of the main executable as this saves # compile time and we don't install them anyway. CFLAGS+= -DGDB=\"/usr/local/bin/gdb\" \ -DKYUA_ARCHITECTURE=\"${MACHINE_ARCH}\" \ -DKYUA_CONFDIR=\"${KYUA_CONFDIR}\" \ -DKYUA_DOCDIR=\"${KYUA_DOCDIR}\" \ -DKYUA_MISCDIR=\"${KYUA_MISCDIR}\" \ -DKYUA_PLATFORM=\"${MACHINE}\" \ -DKYUA_STOREDIR=\"${KYUA_STOREDIR}\" \ -DPACKAGE=\"kyua\" \ -DPACKAGE_NAME=\"Kyua\" \ -DPACKAGE_VERSION=\"${KYUA_VERSION}\" \ -DVERSION=\"${KYUA_VERSION}\" SRCS+= utils/datetime.cpp \ utils/env.cpp \ utils/memory.cpp \ utils/passwd.cpp \ utils/sanity.cpp \ utils/stacktrace.cpp \ utils/stream.cpp \ utils/units.cpp \ utils/cmdline/base_command.cpp \ utils/cmdline/exceptions.cpp \ utils/cmdline/globals.cpp \ utils/cmdline/options.cpp \ utils/cmdline/parser.cpp \ utils/cmdline/ui.cpp \ utils/cmdline/ui_mock.cpp \ utils/config/exceptions.cpp \ utils/config/keys.cpp \ utils/config/lua_module.cpp \ utils/config/nodes.cpp \ utils/config/parser.cpp \ utils/config/tree.cpp \ utils/format/exceptions.cpp \ utils/format/formatter.cpp \ utils/fs/auto_cleaners.cpp \ utils/fs/directory.cpp \ utils/fs/exceptions.cpp \ utils/fs/lua_module.cpp \ utils/fs/operations.cpp \ utils/fs/path.cpp \ utils/logging/operations.cpp \ utils/process/child.cpp \ utils/process/deadline_killer.cpp \ utils/process/exceptions.cpp \ utils/process/executor.cpp \ utils/process/fdstream.cpp \ utils/process/isolation.cpp \ utils/process/operations.cpp \ utils/process/status.cpp \ utils/process/system.cpp \ utils/process/systembuf.cpp \ utils/signals/exceptions.cpp \ utils/signals/interrupts.cpp \ utils/signals/misc.cpp \ utils/signals/programmer.cpp \ utils/signals/timer.cpp \ utils/sqlite/c_gate.cpp \ utils/sqlite/database.cpp \ utils/sqlite/exceptions.cpp \ utils/sqlite/statement.cpp \ utils/sqlite/transaction.cpp \ utils/text/exceptions.cpp \ utils/text/operations.cpp \ utils/text/regex.cpp \ utils/text/table.cpp \ utils/text/templates.cpp SRCS+= model/context.cpp \ model/exceptions.cpp \ model/metadata.cpp \ model/test_case.cpp \ model/test_program.cpp \ model/test_result.cpp SRCS+= engine/atf.cpp \ engine/atf_list.cpp \ engine/atf_result.cpp \ engine/config.cpp \ engine/exceptions.cpp \ engine/filters.cpp \ engine/kyuafile.cpp \ engine/plain.cpp \ engine/requirements.cpp \ engine/scanner.cpp \ engine/tap.cpp \ engine/tap_parser.cpp \ engine/scheduler.cpp \ engine/execenv/execenv.cpp \ engine/execenv/execenv_host.cpp SRCS+= os/freebsd/execenv_jail_manager.cpp \ - os/freebsd/main.cpp + os/freebsd/main.cpp \ + os/freebsd/reqs_checker_kmods.cpp SRCS+= store/dbtypes.cpp \ store/exceptions.cpp \ store/layout.cpp \ store/metadata.cpp \ store/migrate.cpp \ store/read_backend.cpp \ store/read_transaction.cpp \ store/write_backend.cpp \ store/write_transaction.cpp SRCS+= drivers/debug_test.cpp \ drivers/list_tests.cpp \ drivers/report_junit.cpp \ drivers/run_tests.cpp \ drivers/scan_results.cpp SRCS+= cli/cmd_about.cpp \ cli/cmd_config.cpp \ cli/cmd_db_exec.cpp \ cli/cmd_db_migrate.cpp \ cli/cmd_debug.cpp \ cli/cmd_help.cpp \ cli/cmd_list.cpp \ cli/cmd_report.cpp \ cli/cmd_report_html.cpp \ cli/cmd_report_junit.cpp \ cli/cmd_test.cpp \ cli/common.cpp \ cli/config.cpp \ cli/main.cpp .if ${MK_JAIL} == "no" SRCS+= os/freebsd/execenv_jail_stub.cpp .else SRCS+= os/freebsd/execenv_jail.cpp \ os/freebsd/utils/jail.cpp LIBADD+= jail .endif FILESGROUPS= DOCS MISC STORE .if ${MK_EXAMPLES} != "no" FILESGROUPS+= EXAMPLES .endif # Install a minimal default config that uses the 'tests' user. # The examples config is not appropriate for general use. CONFS= kyua.conf-default CONFSDIR= ${KYUA_CONFDIR} CONFSNAME= kyua.conf CONFSDIRTAGS= package=tests DOCS= AUTHORS CONTRIBUTORS LICENSE DOCSDIR= ${KYUA_DOCDIR} DOCSTAGS= package=tests EXAMPLES= Kyuafile.top kyua.conf EXAMPLESDIR= ${KYUA_EGDIR} EXAMPLESTAGS= package=tests .PATH: ${KYUA_SRCDIR}/examples MISC= context.html index.html report.css test_result.html MISCDIR= ${KYUA_MISCDIR} MISCTAGS= package=tests .PATH: ${KYUA_SRCDIR}/misc STORE= migrate_v1_v2.sql migrate_v2_v3.sql schema_v3.sql STOREDIR= ${KYUA_STOREDIR} STORETAGS= package=tests .PATH: ${KYUA_SRCDIR}/store CLEANFILES+= ${MAN} .PATH: ${KYUA_SRCDIR}/doc .for man in ${MAN} ${man}: ${man}.in sh ${KYUA_SRCDIR}/doc/manbuild.sh \ -v "CONFDIR=${KYUA_CONFDIR}" \ -v "DOCDIR=${KYUA_DOCDIR}" \ -v "EGDIR=${KYUA_EGDIR}" \ -v "MISCDIR=${KYUA_MISCDIR}" \ -v "PACKAGE=kyua" \ -v "STOREDIR=${KYUA_STOREDIR}" \ -v "TESTSDIR=${TESTSBASE}" \ -v "VERSION=${KYUA_VERSION}" \ ${.ALLSRC} ${.TARGET} .endfor .include