xref: /minix3/external/bsd/kyua-cli/dist/engine/test_case.cpp (revision 84d9c625bfea59e274550651111ae9edfdc40fbd)
111be35a1SLionel Sambuc // Copyright 2010 Google Inc.
211be35a1SLionel Sambuc // All rights reserved.
311be35a1SLionel Sambuc //
411be35a1SLionel Sambuc // Redistribution and use in source and binary forms, with or without
511be35a1SLionel Sambuc // modification, are permitted provided that the following conditions are
611be35a1SLionel Sambuc // met:
711be35a1SLionel Sambuc //
811be35a1SLionel Sambuc // * Redistributions of source code must retain the above copyright
911be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer.
1011be35a1SLionel Sambuc // * Redistributions in binary form must reproduce the above copyright
1111be35a1SLionel Sambuc //   notice, this list of conditions and the following disclaimer in the
1211be35a1SLionel Sambuc //   documentation and/or other materials provided with the distribution.
1311be35a1SLionel Sambuc // * Neither the name of Google Inc. nor the names of its contributors
1411be35a1SLionel Sambuc //   may be used to endorse or promote products derived from this software
1511be35a1SLionel Sambuc //   without specific prior written permission.
1611be35a1SLionel Sambuc //
1711be35a1SLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1811be35a1SLionel Sambuc // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1911be35a1SLionel Sambuc // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2011be35a1SLionel Sambuc // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2111be35a1SLionel Sambuc // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2211be35a1SLionel Sambuc // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2311be35a1SLionel Sambuc // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2411be35a1SLionel Sambuc // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2511be35a1SLionel Sambuc // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2611be35a1SLionel Sambuc // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2711be35a1SLionel Sambuc // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2811be35a1SLionel Sambuc 
2911be35a1SLionel Sambuc #include "engine/test_case.hpp"
3011be35a1SLionel Sambuc 
3111be35a1SLionel Sambuc extern "C" {
3211be35a1SLionel Sambuc #include <signal.h>
3311be35a1SLionel Sambuc }
3411be35a1SLionel Sambuc 
3511be35a1SLionel Sambuc #include <fstream>
3611be35a1SLionel Sambuc 
3711be35a1SLionel Sambuc #include "engine/config.hpp"
3811be35a1SLionel Sambuc #include "engine/exceptions.hpp"
3911be35a1SLionel Sambuc #include "engine/test_program.hpp"
4011be35a1SLionel Sambuc #include "engine/test_result.hpp"
4111be35a1SLionel Sambuc #include "engine/testers.hpp"
4211be35a1SLionel Sambuc #include "utils/config/tree.ipp"
4311be35a1SLionel Sambuc #include "utils/datetime.hpp"
4411be35a1SLionel Sambuc #include "utils/defs.hpp"
4511be35a1SLionel Sambuc #include "utils/format/macros.hpp"
4611be35a1SLionel Sambuc #include "utils/logging/operations.hpp"
4711be35a1SLionel Sambuc #include "utils/fs/auto_cleaners.hpp"
4811be35a1SLionel Sambuc #include "utils/fs/operations.hpp"
4911be35a1SLionel Sambuc #include "utils/fs/path.hpp"
5011be35a1SLionel Sambuc #include "utils/optional.ipp"
5111be35a1SLionel Sambuc #include "utils/passwd.hpp"
5211be35a1SLionel Sambuc #include "utils/text/operations.ipp"
5311be35a1SLionel Sambuc 
5411be35a1SLionel Sambuc namespace config = utils::config;
5511be35a1SLionel Sambuc namespace fs = utils::fs;
5611be35a1SLionel Sambuc namespace logging = utils::logging;
5711be35a1SLionel Sambuc namespace passwd = utils::passwd;
5811be35a1SLionel Sambuc namespace text = utils::text;
5911be35a1SLionel Sambuc 
6011be35a1SLionel Sambuc using utils::none;
6111be35a1SLionel Sambuc using utils::optional;
6211be35a1SLionel Sambuc 
6311be35a1SLionel Sambuc 
6411be35a1SLionel Sambuc namespace {
6511be35a1SLionel Sambuc 
6611be35a1SLionel Sambuc 
6711be35a1SLionel Sambuc /// Generates the set of configuration variables for the tester.
6811be35a1SLionel Sambuc ///
6911be35a1SLionel Sambuc /// \param metadata The metadata of the test.
7011be35a1SLionel Sambuc /// \param user_config The configuration variables provided by the user.
7111be35a1SLionel Sambuc /// \param test_suite The name of the test suite.
7211be35a1SLionel Sambuc ///
7311be35a1SLionel Sambuc /// \return The mapping of configuration variables for the tester.
7411be35a1SLionel Sambuc static config::properties_map
generate_tester_config(const engine::metadata & metadata,const config::tree & user_config,const std::string & test_suite)7511be35a1SLionel Sambuc generate_tester_config(const engine::metadata& metadata,
7611be35a1SLionel Sambuc                        const config::tree& user_config,
7711be35a1SLionel Sambuc                        const std::string& test_suite)
7811be35a1SLionel Sambuc {
7911be35a1SLionel Sambuc     config::properties_map props;
8011be35a1SLionel Sambuc 
8111be35a1SLionel Sambuc     try {
8211be35a1SLionel Sambuc         props = user_config.all_properties(F("test_suites.%s") % test_suite,
8311be35a1SLionel Sambuc                                            true);
8411be35a1SLionel Sambuc     } catch (const config::unknown_key_error& unused_error) {
8511be35a1SLionel Sambuc         // Ignore: not all test suites have entries in the configuration.
8611be35a1SLionel Sambuc     }
8711be35a1SLionel Sambuc 
8811be35a1SLionel Sambuc     if (user_config.is_set("unprivileged_user")) {
8911be35a1SLionel Sambuc         const passwd::user& user =
9011be35a1SLionel Sambuc             user_config.lookup< engine::user_node >("unprivileged_user");
9111be35a1SLionel Sambuc         props["unprivileged-user"] = user.name;
9211be35a1SLionel Sambuc     }
9311be35a1SLionel Sambuc 
9411be35a1SLionel Sambuc     // TODO(jmmv): This is an ugly hack to cope with an atf-specific
9511be35a1SLionel Sambuc     // property.  We should not be doing this at all, so just consider this
9611be35a1SLionel Sambuc     // a temporary optimization...
9711be35a1SLionel Sambuc     if (metadata.has_cleanup())
9811be35a1SLionel Sambuc         props["has.cleanup"] = "true";
9911be35a1SLionel Sambuc     else
10011be35a1SLionel Sambuc         props["has.cleanup"] = "false";
10111be35a1SLionel Sambuc 
10211be35a1SLionel Sambuc     return props;
10311be35a1SLionel Sambuc }
10411be35a1SLionel Sambuc 
10511be35a1SLionel Sambuc 
10611be35a1SLionel Sambuc /// Creates a tester.
10711be35a1SLionel Sambuc ///
10811be35a1SLionel Sambuc /// \param interface_name The name of the tester interface to use.
10911be35a1SLionel Sambuc /// \param metadata Metadata of the test case.
11011be35a1SLionel Sambuc /// \param user_config User-provided configuration variables.
11111be35a1SLionel Sambuc ///
11211be35a1SLionel Sambuc /// \return The created tester, on which the test() method can be executed.
11311be35a1SLionel Sambuc static engine::tester
create_tester(const std::string & interface_name,const engine::metadata & metadata,const config::tree & user_config)11411be35a1SLionel Sambuc create_tester(const std::string& interface_name,
11511be35a1SLionel Sambuc               const engine::metadata& metadata, const config::tree& user_config)
11611be35a1SLionel Sambuc {
11711be35a1SLionel Sambuc     optional< passwd::user > user;
11811be35a1SLionel Sambuc     if (user_config.is_set("unprivileged_user") &&
11911be35a1SLionel Sambuc         metadata.required_user() == "unprivileged")
12011be35a1SLionel Sambuc         user = user_config.lookup< engine::user_node >("unprivileged_user");
12111be35a1SLionel Sambuc 
12211be35a1SLionel Sambuc     return engine::tester(interface_name, user,
12311be35a1SLionel Sambuc                           utils::make_optional(metadata.timeout()));
12411be35a1SLionel Sambuc }
12511be35a1SLionel Sambuc 
12611be35a1SLionel Sambuc 
12711be35a1SLionel Sambuc }  // anonymous namespace
12811be35a1SLionel Sambuc 
12911be35a1SLionel Sambuc 
13011be35a1SLionel Sambuc /// Destructor.
~test_case_hooks(void)13111be35a1SLionel Sambuc engine::test_case_hooks::~test_case_hooks(void)
13211be35a1SLionel Sambuc {
13311be35a1SLionel Sambuc }
13411be35a1SLionel Sambuc 
13511be35a1SLionel Sambuc 
13611be35a1SLionel Sambuc /// Called once the test case's stdout is ready for processing.
13711be35a1SLionel Sambuc ///
13811be35a1SLionel Sambuc /// It is important to note that this file is only available within this
13911be35a1SLionel Sambuc /// callback.  Attempting to read the file once the execute function has
14011be35a1SLionel Sambuc /// returned will result in an error because the file might have been deleted.
14111be35a1SLionel Sambuc ///
14211be35a1SLionel Sambuc /// \param unused_file The path to the file containing the stdout.
14311be35a1SLionel Sambuc void
got_stdout(const fs::path & UTILS_UNUSED_PARAM (file))14411be35a1SLionel Sambuc engine::test_case_hooks::got_stdout(const fs::path& UTILS_UNUSED_PARAM(file))
14511be35a1SLionel Sambuc {
14611be35a1SLionel Sambuc }
14711be35a1SLionel Sambuc 
14811be35a1SLionel Sambuc 
14911be35a1SLionel Sambuc /// Called once the test case's stderr is ready for processing.
15011be35a1SLionel Sambuc ///
15111be35a1SLionel Sambuc /// It is important to note that this file is only available within this
15211be35a1SLionel Sambuc /// callback.  Attempting to read the file once the execute function has
15311be35a1SLionel Sambuc /// returned will result in an error because the file might have been deleted.
15411be35a1SLionel Sambuc ///
15511be35a1SLionel Sambuc /// \param unused_file The path to the file containing the stderr.
15611be35a1SLionel Sambuc void
got_stderr(const fs::path & UTILS_UNUSED_PARAM (file))15711be35a1SLionel Sambuc engine::test_case_hooks::got_stderr(const fs::path& UTILS_UNUSED_PARAM(file))
15811be35a1SLionel Sambuc {
15911be35a1SLionel Sambuc }
16011be35a1SLionel Sambuc 
16111be35a1SLionel Sambuc 
16211be35a1SLionel Sambuc /// Internal implementation for a test_case.
16311be35a1SLionel Sambuc struct engine::test_case::impl {
16411be35a1SLionel Sambuc     /// Name of the interface implemented by the test program.
16511be35a1SLionel Sambuc     const std::string interface_name;
16611be35a1SLionel Sambuc 
16711be35a1SLionel Sambuc     /// Test program this test case belongs to.
16811be35a1SLionel Sambuc     const test_program& _test_program;
16911be35a1SLionel Sambuc 
17011be35a1SLionel Sambuc     /// Name of the test case; must be unique within the test program.
17111be35a1SLionel Sambuc     std::string name;
17211be35a1SLionel Sambuc 
17311be35a1SLionel Sambuc     /// Test case metadata.
17411be35a1SLionel Sambuc     metadata md;
17511be35a1SLionel Sambuc 
17611be35a1SLionel Sambuc     /// Fake result to return instead of running the test case.
17711be35a1SLionel Sambuc     optional< test_result > fake_result;
17811be35a1SLionel Sambuc 
17911be35a1SLionel Sambuc     /// Constructor.
18011be35a1SLionel Sambuc     ///
18111be35a1SLionel Sambuc     /// \param interface_name_ Name of the interface implemented by the test
18211be35a1SLionel Sambuc     ///     program.
18311be35a1SLionel Sambuc     /// \param test_program_ The test program this test case belongs to.
18411be35a1SLionel Sambuc     /// \param name_ The name of the test case within the test program.
18511be35a1SLionel Sambuc     /// \param md_ Metadata of the test case.
18611be35a1SLionel Sambuc     /// \param fake_result_ Fake result to return instead of running the test
18711be35a1SLionel Sambuc     ///     case.
implengine::test_case::impl18811be35a1SLionel Sambuc     impl(const std::string& interface_name_,
18911be35a1SLionel Sambuc          const test_program& test_program_,
19011be35a1SLionel Sambuc          const std::string& name_,
19111be35a1SLionel Sambuc          const metadata& md_,
19211be35a1SLionel Sambuc          const optional< test_result >& fake_result_) :
19311be35a1SLionel Sambuc         interface_name(interface_name_),
19411be35a1SLionel Sambuc         _test_program(test_program_),
19511be35a1SLionel Sambuc         name(name_),
19611be35a1SLionel Sambuc         md(md_),
19711be35a1SLionel Sambuc         fake_result(fake_result_)
19811be35a1SLionel Sambuc     {
19911be35a1SLionel Sambuc     }
20011be35a1SLionel Sambuc 
20111be35a1SLionel Sambuc     /// Equality comparator.
20211be35a1SLionel Sambuc     ///
20311be35a1SLionel Sambuc     /// \param other The other object to compare this one to.
20411be35a1SLionel Sambuc     ///
20511be35a1SLionel Sambuc     /// \return True if this object and other are equal; false otherwise.
20611be35a1SLionel Sambuc     bool
operator ==engine::test_case::impl20711be35a1SLionel Sambuc     operator==(const impl& other) const
20811be35a1SLionel Sambuc     {
20911be35a1SLionel Sambuc         return (interface_name == other.interface_name &&
21011be35a1SLionel Sambuc                 (_test_program.absolute_path() ==
21111be35a1SLionel Sambuc                  other._test_program.absolute_path()) &&
21211be35a1SLionel Sambuc                 name == other.name &&
21311be35a1SLionel Sambuc                 md == other.md &&
21411be35a1SLionel Sambuc                 fake_result == other.fake_result);
21511be35a1SLionel Sambuc     }
21611be35a1SLionel Sambuc };
21711be35a1SLionel Sambuc 
21811be35a1SLionel Sambuc 
21911be35a1SLionel Sambuc /// Constructs a new test case.
22011be35a1SLionel Sambuc ///
22111be35a1SLionel Sambuc /// \param interface_name_ Name of the interface implemented by the test
22211be35a1SLionel Sambuc ///     program.
22311be35a1SLionel Sambuc /// \param test_program_ The test program this test case belongs to.  This is a
22411be35a1SLionel Sambuc ///     static reference (instead of a test_program_ptr) because the test
22511be35a1SLionel Sambuc ///     program must exist in order for the test case to exist.
22611be35a1SLionel Sambuc /// \param name_ The name of the test case within the test program.  Must be
22711be35a1SLionel Sambuc ///     unique.
22811be35a1SLionel Sambuc /// \param md_ Metadata of the test case.
test_case(const std::string & interface_name_,const test_program & test_program_,const std::string & name_,const metadata & md_)22911be35a1SLionel Sambuc engine::test_case::test_case(const std::string& interface_name_,
23011be35a1SLionel Sambuc                              const test_program& test_program_,
23111be35a1SLionel Sambuc                              const std::string& name_,
23211be35a1SLionel Sambuc                              const metadata& md_) :
23311be35a1SLionel Sambuc     _pimpl(new impl(interface_name_, test_program_, name_, md_, none))
23411be35a1SLionel Sambuc {
23511be35a1SLionel Sambuc }
23611be35a1SLionel Sambuc 
23711be35a1SLionel Sambuc 
23811be35a1SLionel Sambuc 
23911be35a1SLionel Sambuc /// Constructs a new fake test case.
24011be35a1SLionel Sambuc ///
24111be35a1SLionel Sambuc /// A fake test case is a test case that is not really defined by the test
24211be35a1SLionel Sambuc /// program.  Such test cases have a name surrounded by '__' and, when executed,
24311be35a1SLionel Sambuc /// they return a fixed, pre-recorded result.
24411be35a1SLionel Sambuc ///
24511be35a1SLionel Sambuc /// This is necessary for the cases where listing the test cases of a test
24611be35a1SLionel Sambuc /// program fails.  In this scenario, we generate a single test case within
24711be35a1SLionel Sambuc /// the test program that unconditionally returns a failure.
24811be35a1SLionel Sambuc ///
24911be35a1SLionel Sambuc /// TODO(jmmv): Need to get rid of this.  We should be able to report the
25011be35a1SLionel Sambuc /// status of test programs independently of test cases, as some interfaces
25111be35a1SLionel Sambuc /// don't know about the latter at all.
25211be35a1SLionel Sambuc ///
25311be35a1SLionel Sambuc /// \param interface_name_ Name of the interface implemented by the test
25411be35a1SLionel Sambuc ///     program.
25511be35a1SLionel Sambuc /// \param test_program_ The test program this test case belongs to.
25611be35a1SLionel Sambuc /// \param name_ The name to give to this fake test case.  This name has to be
25711be35a1SLionel Sambuc ///     prefixed and suffixed by '__' to clearly denote that this is internal.
25811be35a1SLionel Sambuc /// \param description_ The description of the test case, if any.
25911be35a1SLionel Sambuc /// \param test_result_ The fake result to return when this test case is run.
test_case(const std::string & interface_name_,const test_program & test_program_,const std::string & name_,const std::string & description_,const engine::test_result & test_result_)26011be35a1SLionel Sambuc engine::test_case::test_case(
26111be35a1SLionel Sambuc     const std::string& interface_name_,
26211be35a1SLionel Sambuc     const test_program& test_program_,
26311be35a1SLionel Sambuc     const std::string& name_,
26411be35a1SLionel Sambuc     const std::string& description_,
26511be35a1SLionel Sambuc     const engine::test_result& test_result_) :
26611be35a1SLionel Sambuc     _pimpl(new impl(interface_name_, test_program_, name_,
26711be35a1SLionel Sambuc                     metadata_builder().set_description(description_).build(),
26811be35a1SLionel Sambuc                     utils::make_optional(test_result_)))
26911be35a1SLionel Sambuc {
27011be35a1SLionel Sambuc     PRE_MSG(name_.length() > 4 && name_.substr(0, 2) == "__" &&
27111be35a1SLionel Sambuc             name_.substr(name_.length() - 2) == "__",
27211be35a1SLionel Sambuc             "Invalid fake name provided to fake test case");
27311be35a1SLionel Sambuc }
27411be35a1SLionel Sambuc 
27511be35a1SLionel Sambuc 
27611be35a1SLionel Sambuc /// Destroys a test case.
~test_case(void)27711be35a1SLionel Sambuc engine::test_case::~test_case(void)
27811be35a1SLionel Sambuc {
27911be35a1SLionel Sambuc }
28011be35a1SLionel Sambuc 
28111be35a1SLionel Sambuc 
28211be35a1SLionel Sambuc /// Gets the name of the interface implemented by the test program.
28311be35a1SLionel Sambuc ///
28411be35a1SLionel Sambuc /// \return An interface name.
28511be35a1SLionel Sambuc const std::string&
interface_name(void) const28611be35a1SLionel Sambuc engine::test_case::interface_name(void) const
28711be35a1SLionel Sambuc {
28811be35a1SLionel Sambuc     return _pimpl->interface_name;
28911be35a1SLionel Sambuc }
29011be35a1SLionel Sambuc 
29111be35a1SLionel Sambuc 
29211be35a1SLionel Sambuc /// Gets the test program this test case belongs to.
29311be35a1SLionel Sambuc ///
29411be35a1SLionel Sambuc /// \return A reference to the container test program.
29511be35a1SLionel Sambuc const engine::test_program&
container_test_program(void) const29611be35a1SLionel Sambuc engine::test_case::container_test_program(void) const
29711be35a1SLionel Sambuc {
29811be35a1SLionel Sambuc     return _pimpl->_test_program;
29911be35a1SLionel Sambuc }
30011be35a1SLionel Sambuc 
30111be35a1SLionel Sambuc 
30211be35a1SLionel Sambuc /// Gets the test case name.
30311be35a1SLionel Sambuc ///
30411be35a1SLionel Sambuc /// \return The test case name, relative to the test program.
30511be35a1SLionel Sambuc const std::string&
name(void) const30611be35a1SLionel Sambuc engine::test_case::name(void) const
30711be35a1SLionel Sambuc {
30811be35a1SLionel Sambuc     return _pimpl->name;
30911be35a1SLionel Sambuc }
31011be35a1SLionel Sambuc 
31111be35a1SLionel Sambuc 
31211be35a1SLionel Sambuc /// Gets the test case metadata.
31311be35a1SLionel Sambuc ///
31411be35a1SLionel Sambuc /// \return The test case metadata.
31511be35a1SLionel Sambuc const engine::metadata&
get_metadata(void) const31611be35a1SLionel Sambuc engine::test_case::get_metadata(void) const
31711be35a1SLionel Sambuc {
31811be35a1SLionel Sambuc     return _pimpl->md;
31911be35a1SLionel Sambuc }
32011be35a1SLionel Sambuc 
32111be35a1SLionel Sambuc 
32211be35a1SLionel Sambuc /// Gets the fake result pre-stored for this test case.
32311be35a1SLionel Sambuc ///
32411be35a1SLionel Sambuc /// \return A fake result, or none if not defined.
32511be35a1SLionel Sambuc optional< engine::test_result >
fake_result(void) const32611be35a1SLionel Sambuc engine::test_case::fake_result(void) const
32711be35a1SLionel Sambuc {
32811be35a1SLionel Sambuc     return _pimpl->fake_result;
32911be35a1SLionel Sambuc }
33011be35a1SLionel Sambuc 
33111be35a1SLionel Sambuc 
33211be35a1SLionel Sambuc /// Equality comparator.
33311be35a1SLionel Sambuc ///
33411be35a1SLionel Sambuc /// \warning Because test cases reference their container test programs, and
33511be35a1SLionel Sambuc /// test programs include test cases, we cannot perform a full comparison here:
33611be35a1SLionel Sambuc /// otherwise, we'd enter an inifinte loop.  Therefore, out of necessity, this
33711be35a1SLionel Sambuc /// does NOT compare whether the container test programs of the affected test
33811be35a1SLionel Sambuc /// cases are the same.
33911be35a1SLionel Sambuc ///
34011be35a1SLionel Sambuc /// \param other The other object to compare this one to.
34111be35a1SLionel Sambuc ///
34211be35a1SLionel Sambuc /// \return True if this object and other are equal; false otherwise.
34311be35a1SLionel Sambuc bool
operator ==(const test_case & other) const34411be35a1SLionel Sambuc engine::test_case::operator==(const test_case& other) const
34511be35a1SLionel Sambuc {
34611be35a1SLionel Sambuc     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
34711be35a1SLionel Sambuc }
34811be35a1SLionel Sambuc 
34911be35a1SLionel Sambuc 
35011be35a1SLionel Sambuc /// Inequality comparator.
35111be35a1SLionel Sambuc ///
35211be35a1SLionel Sambuc /// \param other The other object to compare this one to.
35311be35a1SLionel Sambuc ///
35411be35a1SLionel Sambuc /// \return True if this object and other are different; false otherwise.
35511be35a1SLionel Sambuc bool
operator !=(const test_case & other) const35611be35a1SLionel Sambuc engine::test_case::operator!=(const test_case& other) const
35711be35a1SLionel Sambuc {
35811be35a1SLionel Sambuc     return !(*this == other);
35911be35a1SLionel Sambuc }
36011be35a1SLionel Sambuc 
36111be35a1SLionel Sambuc 
36211be35a1SLionel Sambuc /// Injects the object into a stream.
36311be35a1SLionel Sambuc ///
36411be35a1SLionel Sambuc /// \param output The stream into which to inject the object.
36511be35a1SLionel Sambuc /// \param object The object to format.
36611be35a1SLionel Sambuc ///
36711be35a1SLionel Sambuc /// \return The output stream.
36811be35a1SLionel Sambuc std::ostream&
operator <<(std::ostream & output,const test_case & object)36911be35a1SLionel Sambuc engine::operator<<(std::ostream& output, const test_case& object)
37011be35a1SLionel Sambuc {
37111be35a1SLionel Sambuc     // We skip injecting container_test_program() on purpose to avoid a loop.
37211be35a1SLionel Sambuc     output << F("test_case{interface=%s, name=%s, metadata=%s}")
37311be35a1SLionel Sambuc         % text::quote(object.interface_name(), '\'')
37411be35a1SLionel Sambuc         % text::quote(object.name(), '\'')
37511be35a1SLionel Sambuc         % object.get_metadata();
37611be35a1SLionel Sambuc     return output;
37711be35a1SLionel Sambuc }
37811be35a1SLionel Sambuc 
37911be35a1SLionel Sambuc 
38011be35a1SLionel Sambuc /// Runs the test case in debug mode.
38111be35a1SLionel Sambuc ///
38211be35a1SLionel Sambuc /// Debug mode gives the caller more control on the execution of the test.  It
38311be35a1SLionel Sambuc /// should not be used for normal execution of tests; instead, call run().
38411be35a1SLionel Sambuc ///
38511be35a1SLionel Sambuc /// \param test_case The test case to debug.
38611be35a1SLionel Sambuc /// \param user_config The user configuration that defines the execution of this
38711be35a1SLionel Sambuc ///     test case.
38811be35a1SLionel Sambuc /// \param hooks Hooks to introspect the execution of the test case.
38911be35a1SLionel Sambuc /// \param work_directory A directory that can be used to place temporary files.
39011be35a1SLionel Sambuc /// \param stdout_path The file to which to redirect the stdout of the test.
39111be35a1SLionel Sambuc ///     For interactive debugging, '/dev/stdout' is probably a reasonable value.
39211be35a1SLionel Sambuc /// \param stderr_path The file to which to redirect the stdout of the test.
39311be35a1SLionel Sambuc ///     For interactive debugging, '/dev/stderr' is probably a reasonable value.
39411be35a1SLionel Sambuc ///
39511be35a1SLionel Sambuc /// \return The result of the execution of the test case.
39611be35a1SLionel Sambuc engine::test_result
debug_test_case(const test_case * test_case,const config::tree & user_config,test_case_hooks & hooks,const fs::path & work_directory,const fs::path & stdout_path,const fs::path & stderr_path)39711be35a1SLionel Sambuc engine::debug_test_case(const test_case* test_case,
39811be35a1SLionel Sambuc                         const config::tree& user_config,
39911be35a1SLionel Sambuc                         test_case_hooks& hooks,
40011be35a1SLionel Sambuc                         const fs::path& work_directory,
40111be35a1SLionel Sambuc                         const fs::path& stdout_path,
40211be35a1SLionel Sambuc                         const fs::path& stderr_path)
40311be35a1SLionel Sambuc {
40411be35a1SLionel Sambuc     if (test_case->fake_result())
40511be35a1SLionel Sambuc         return test_case->fake_result().get();
40611be35a1SLionel Sambuc 
40711be35a1SLionel Sambuc     const std::string skip_reason = check_reqs(
40811be35a1SLionel Sambuc         test_case->get_metadata(), user_config,
40911be35a1SLionel Sambuc         test_case->container_test_program().test_suite_name());
41011be35a1SLionel Sambuc     if (!skip_reason.empty())
41111be35a1SLionel Sambuc         return test_result(test_result::skipped, skip_reason);
41211be35a1SLionel Sambuc 
41311be35a1SLionel Sambuc     if (!fs::exists(test_case->container_test_program().absolute_path()))
41411be35a1SLionel Sambuc         return test_result(test_result::broken, "Test program does not exist");
41511be35a1SLionel Sambuc 
41611be35a1SLionel Sambuc     const fs::auto_file result_file(work_directory / "result.txt");
41711be35a1SLionel Sambuc 
41811be35a1SLionel Sambuc     const engine::test_program& test_program =
41911be35a1SLionel Sambuc         test_case->container_test_program();
42011be35a1SLionel Sambuc 
421*84d9c625SLionel Sambuc     try {
42211be35a1SLionel Sambuc         const engine::tester tester = create_tester(
423*84d9c625SLionel Sambuc             test_program.interface_name(), test_case->get_metadata(),
424*84d9c625SLionel Sambuc             user_config);
42511be35a1SLionel Sambuc         tester.test(test_program.absolute_path(), test_case->name(),
42611be35a1SLionel Sambuc                     result_file.file(), stdout_path, stderr_path,
427*84d9c625SLionel Sambuc                     generate_tester_config(test_case->get_metadata(),
428*84d9c625SLionel Sambuc                                            user_config,
42911be35a1SLionel Sambuc                                            test_program.test_suite_name()));
43011be35a1SLionel Sambuc 
43111be35a1SLionel Sambuc         hooks.got_stdout(stdout_path);
43211be35a1SLionel Sambuc         hooks.got_stderr(stderr_path);
43311be35a1SLionel Sambuc 
43411be35a1SLionel Sambuc         std::ifstream result_input(result_file.file().c_str());
43511be35a1SLionel Sambuc         return engine::test_result::parse(result_input);
436*84d9c625SLionel Sambuc     } catch (const std::runtime_error& e) {
437*84d9c625SLionel Sambuc         // One of the possible explanation for us getting here is if the tester
438*84d9c625SLionel Sambuc         // crashes or doesn't behave as expected.  We must record any output
439*84d9c625SLionel Sambuc         // from the process so that we can debug it further.
440*84d9c625SLionel Sambuc         hooks.got_stdout(stdout_path);
441*84d9c625SLionel Sambuc         hooks.got_stderr(stderr_path);
442*84d9c625SLionel Sambuc 
443*84d9c625SLionel Sambuc         return engine::test_result(
444*84d9c625SLionel Sambuc             engine::test_result::broken,
445*84d9c625SLionel Sambuc             F("Caught unexpected exception: %s") % e.what());
446*84d9c625SLionel Sambuc     }
44711be35a1SLionel Sambuc }
44811be35a1SLionel Sambuc 
44911be35a1SLionel Sambuc 
45011be35a1SLionel Sambuc /// Runs the test case.
45111be35a1SLionel Sambuc ///
45211be35a1SLionel Sambuc /// \param test_case The test case to run.
45311be35a1SLionel Sambuc /// \param user_config The user configuration that defines the execution of this
45411be35a1SLionel Sambuc ///     test case.
45511be35a1SLionel Sambuc /// \param hooks Hooks to introspect the execution of the test case.
45611be35a1SLionel Sambuc /// \param work_directory A directory that can be used to place temporary files.
45711be35a1SLionel Sambuc ///
45811be35a1SLionel Sambuc /// \return The result of the execution of the test case.
45911be35a1SLionel Sambuc engine::test_result
run_test_case(const test_case * test_case,const config::tree & user_config,test_case_hooks & hooks,const fs::path & work_directory)46011be35a1SLionel Sambuc engine::run_test_case(const test_case* test_case,
46111be35a1SLionel Sambuc                       const config::tree& user_config,
46211be35a1SLionel Sambuc                       test_case_hooks& hooks,
46311be35a1SLionel Sambuc                       const fs::path& work_directory)
46411be35a1SLionel Sambuc {
46511be35a1SLionel Sambuc     if (test_case->fake_result())
46611be35a1SLionel Sambuc         return test_case->fake_result().get();
46711be35a1SLionel Sambuc 
46811be35a1SLionel Sambuc     const std::string skip_reason = check_reqs(
46911be35a1SLionel Sambuc         test_case->get_metadata(), user_config,
47011be35a1SLionel Sambuc         test_case->container_test_program().test_suite_name());
47111be35a1SLionel Sambuc     if (!skip_reason.empty())
47211be35a1SLionel Sambuc         return test_result(test_result::skipped, skip_reason);
47311be35a1SLionel Sambuc 
47411be35a1SLionel Sambuc     if (!fs::exists(test_case->container_test_program().absolute_path()))
47511be35a1SLionel Sambuc         return test_result(test_result::broken, "Test program does not exist");
47611be35a1SLionel Sambuc 
47711be35a1SLionel Sambuc     const fs::auto_file stdout_file(work_directory / "stdout.txt");
47811be35a1SLionel Sambuc     const fs::auto_file stderr_file(work_directory / "stderr.txt");
47911be35a1SLionel Sambuc     const fs::auto_file result_file(work_directory / "result.txt");
48011be35a1SLionel Sambuc 
48111be35a1SLionel Sambuc     const engine::test_program& test_program =
48211be35a1SLionel Sambuc         test_case->container_test_program();
48311be35a1SLionel Sambuc 
484*84d9c625SLionel Sambuc     try {
48511be35a1SLionel Sambuc         const engine::tester tester = create_tester(
486*84d9c625SLionel Sambuc             test_program.interface_name(), test_case->get_metadata(),
487*84d9c625SLionel Sambuc             user_config);
48811be35a1SLionel Sambuc         tester.test(test_program.absolute_path(), test_case->name(),
48911be35a1SLionel Sambuc                     result_file.file(), stdout_file.file(), stderr_file.file(),
490*84d9c625SLionel Sambuc                     generate_tester_config(test_case->get_metadata(),
491*84d9c625SLionel Sambuc                                            user_config,
49211be35a1SLionel Sambuc                                            test_program.test_suite_name()));
49311be35a1SLionel Sambuc 
49411be35a1SLionel Sambuc         hooks.got_stdout(stdout_file.file());
49511be35a1SLionel Sambuc         hooks.got_stderr(stderr_file.file());
49611be35a1SLionel Sambuc 
49711be35a1SLionel Sambuc         std::ifstream result_input(result_file.file().c_str());
49811be35a1SLionel Sambuc         return engine::test_result::parse(result_input);
499*84d9c625SLionel Sambuc     } catch (const std::runtime_error& e) {
500*84d9c625SLionel Sambuc         // One of the possible explanation for us getting here is if the tester
501*84d9c625SLionel Sambuc         // crashes or doesn't behave as expected.  We must record any output
502*84d9c625SLionel Sambuc         // from the process so that we can debug it further.
503*84d9c625SLionel Sambuc         hooks.got_stdout(stdout_file.file());
504*84d9c625SLionel Sambuc         hooks.got_stderr(stderr_file.file());
505*84d9c625SLionel Sambuc 
506*84d9c625SLionel Sambuc         return engine::test_result(
507*84d9c625SLionel Sambuc             engine::test_result::broken,
508*84d9c625SLionel Sambuc             F("Caught unexpected exception: %s") % e.what());
509*84d9c625SLionel Sambuc     }
51011be35a1SLionel Sambuc }
511