Home | History | Annotate | Line # | Download | only in engine
      1 // Copyright 2010 Google Inc.
      2 // All rights reserved.
      3 //
      4 // Redistribution and use in source and binary forms, with or without
      5 // modification, are permitted provided that the following conditions are
      6 // met:
      7 //
      8 // * Redistributions of source code must retain the above copyright
      9 //   notice, this list of conditions and the following disclaimer.
     10 // * Redistributions in binary form must reproduce the above copyright
     11 //   notice, this list of conditions and the following disclaimer in the
     12 //   documentation and/or other materials provided with the distribution.
     13 // * Neither the name of Google Inc. nor the names of its contributors
     14 //   may be used to endorse or promote products derived from this software
     15 //   without specific prior written permission.
     16 //
     17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 #include "engine/test_case.hpp"
     30 
     31 extern "C" {
     32 #include <signal.h>
     33 }
     34 
     35 #include <fstream>
     36 
     37 #include "engine/config.hpp"
     38 #include "engine/exceptions.hpp"
     39 #include "engine/test_program.hpp"
     40 #include "engine/test_result.hpp"
     41 #include "engine/testers.hpp"
     42 #include "utils/config/tree.ipp"
     43 #include "utils/datetime.hpp"
     44 #include "utils/defs.hpp"
     45 #include "utils/format/macros.hpp"
     46 #include "utils/logging/operations.hpp"
     47 #include "utils/fs/auto_cleaners.hpp"
     48 #include "utils/fs/operations.hpp"
     49 #include "utils/fs/path.hpp"
     50 #include "utils/optional.ipp"
     51 #include "utils/passwd.hpp"
     52 #include "utils/text/operations.ipp"
     53 
     54 namespace config = utils::config;
     55 namespace fs = utils::fs;
     56 namespace logging = utils::logging;
     57 namespace passwd = utils::passwd;
     58 namespace text = utils::text;
     59 
     60 using utils::none;
     61 using utils::optional;
     62 
     63 
     64 namespace {
     65 
     66 
     67 /// Generates the set of configuration variables for the tester.
     68 ///
     69 /// \param metadata The metadata of the test.
     70 /// \param user_config The configuration variables provided by the user.
     71 /// \param test_suite The name of the test suite.
     72 ///
     73 /// \return The mapping of configuration variables for the tester.
     74 static config::properties_map
     75 generate_tester_config(const engine::metadata& metadata,
     76                        const config::tree& user_config,
     77                        const std::string& test_suite)
     78 {
     79     config::properties_map props;
     80 
     81     try {
     82         props = user_config.all_properties(F("test_suites.%s") % test_suite,
     83                                            true);
     84     } catch (const config::unknown_key_error& unused_error) {
     85         // Ignore: not all test suites have entries in the configuration.
     86     }
     87 
     88     if (user_config.is_set("unprivileged_user")) {
     89         const passwd::user& user =
     90             user_config.lookup< engine::user_node >("unprivileged_user");
     91         props["unprivileged-user"] = user.name;
     92     }
     93 
     94     // TODO(jmmv): This is an ugly hack to cope with an atf-specific
     95     // property.  We should not be doing this at all, so just consider this
     96     // a temporary optimization...
     97     if (metadata.has_cleanup())
     98         props["has.cleanup"] = "true";
     99     else
    100         props["has.cleanup"] = "false";
    101 
    102     return props;
    103 }
    104 
    105 
    106 /// Creates a tester.
    107 ///
    108 /// \param interface_name The name of the tester interface to use.
    109 /// \param metadata Metadata of the test case.
    110 /// \param user_config User-provided configuration variables.
    111 ///
    112 /// \return The created tester, on which the test() method can be executed.
    113 static engine::tester
    114 create_tester(const std::string& interface_name,
    115               const engine::metadata& metadata, const config::tree& user_config)
    116 {
    117     optional< passwd::user > user;
    118     if (user_config.is_set("unprivileged_user") &&
    119         metadata.required_user() == "unprivileged")
    120         user = user_config.lookup< engine::user_node >("unprivileged_user");
    121 
    122     return engine::tester(interface_name, user,
    123                           utils::make_optional(metadata.timeout()));
    124 }
    125 
    126 
    127 }  // anonymous namespace
    128 
    129 
    130 /// Destructor.
    131 engine::test_case_hooks::~test_case_hooks(void)
    132 {
    133 }
    134 
    135 
    136 /// Called once the test case's stdout is ready for processing.
    137 ///
    138 /// It is important to note that this file is only available within this
    139 /// callback.  Attempting to read the file once the execute function has
    140 /// returned will result in an error because the file might have been deleted.
    141 ///
    142 /// \param unused_file The path to the file containing the stdout.
    143 void
    144 engine::test_case_hooks::got_stdout(const fs::path& UTILS_UNUSED_PARAM(file))
    145 {
    146 }
    147 
    148 
    149 /// Called once the test case's stderr is ready for processing.
    150 ///
    151 /// It is important to note that this file is only available within this
    152 /// callback.  Attempting to read the file once the execute function has
    153 /// returned will result in an error because the file might have been deleted.
    154 ///
    155 /// \param unused_file The path to the file containing the stderr.
    156 void
    157 engine::test_case_hooks::got_stderr(const fs::path& UTILS_UNUSED_PARAM(file))
    158 {
    159 }
    160 
    161 
    162 /// Internal implementation for a test_case.
    163 struct engine::test_case::impl {
    164     /// Name of the interface implemented by the test program.
    165     const std::string interface_name;
    166 
    167     /// Test program this test case belongs to.
    168     const test_program& _test_program;
    169 
    170     /// Name of the test case; must be unique within the test program.
    171     std::string name;
    172 
    173     /// Test case metadata.
    174     metadata md;
    175 
    176     /// Fake result to return instead of running the test case.
    177     optional< test_result > fake_result;
    178 
    179     /// Constructor.
    180     ///
    181     /// \param interface_name_ Name of the interface implemented by the test
    182     ///     program.
    183     /// \param test_program_ The test program this test case belongs to.
    184     /// \param name_ The name of the test case within the test program.
    185     /// \param md_ Metadata of the test case.
    186     /// \param fake_result_ Fake result to return instead of running the test
    187     ///     case.
    188     impl(const std::string& interface_name_,
    189          const test_program& test_program_,
    190          const std::string& name_,
    191          const metadata& md_,
    192          const optional< test_result >& fake_result_) :
    193         interface_name(interface_name_),
    194         _test_program(test_program_),
    195         name(name_),
    196         md(md_),
    197         fake_result(fake_result_)
    198     {
    199     }
    200 
    201     /// Equality comparator.
    202     ///
    203     /// \param other The other object to compare this one to.
    204     ///
    205     /// \return True if this object and other are equal; false otherwise.
    206     bool
    207     operator==(const impl& other) const
    208     {
    209         return (interface_name == other.interface_name &&
    210                 (_test_program.absolute_path() ==
    211                  other._test_program.absolute_path()) &&
    212                 name == other.name &&
    213                 md == other.md &&
    214                 fake_result == other.fake_result);
    215     }
    216 };
    217 
    218 
    219 /// Constructs a new test case.
    220 ///
    221 /// \param interface_name_ Name of the interface implemented by the test
    222 ///     program.
    223 /// \param test_program_ The test program this test case belongs to.  This is a
    224 ///     static reference (instead of a test_program_ptr) because the test
    225 ///     program must exist in order for the test case to exist.
    226 /// \param name_ The name of the test case within the test program.  Must be
    227 ///     unique.
    228 /// \param md_ Metadata of the test case.
    229 engine::test_case::test_case(const std::string& interface_name_,
    230                              const test_program& test_program_,
    231                              const std::string& name_,
    232                              const metadata& md_) :
    233     _pimpl(new impl(interface_name_, test_program_, name_, md_, none))
    234 {
    235 }
    236 
    237 
    238 
    239 /// Constructs a new fake test case.
    240 ///
    241 /// A fake test case is a test case that is not really defined by the test
    242 /// program.  Such test cases have a name surrounded by '__' and, when executed,
    243 /// they return a fixed, pre-recorded result.
    244 ///
    245 /// This is necessary for the cases where listing the test cases of a test
    246 /// program fails.  In this scenario, we generate a single test case within
    247 /// the test program that unconditionally returns a failure.
    248 ///
    249 /// TODO(jmmv): Need to get rid of this.  We should be able to report the
    250 /// status of test programs independently of test cases, as some interfaces
    251 /// don't know about the latter at all.
    252 ///
    253 /// \param interface_name_ Name of the interface implemented by the test
    254 ///     program.
    255 /// \param test_program_ The test program this test case belongs to.
    256 /// \param name_ The name to give to this fake test case.  This name has to be
    257 ///     prefixed and suffixed by '__' to clearly denote that this is internal.
    258 /// \param description_ The description of the test case, if any.
    259 /// \param test_result_ The fake result to return when this test case is run.
    260 engine::test_case::test_case(
    261     const std::string& interface_name_,
    262     const test_program& test_program_,
    263     const std::string& name_,
    264     const std::string& description_,
    265     const engine::test_result& test_result_) :
    266     _pimpl(new impl(interface_name_, test_program_, name_,
    267                     metadata_builder().set_description(description_).build(),
    268                     utils::make_optional(test_result_)))
    269 {
    270     PRE_MSG(name_.length() > 4 && name_.substr(0, 2) == "__" &&
    271             name_.substr(name_.length() - 2) == "__",
    272             "Invalid fake name provided to fake test case");
    273 }
    274 
    275 
    276 /// Destroys a test case.
    277 engine::test_case::~test_case(void)
    278 {
    279 }
    280 
    281 
    282 /// Gets the name of the interface implemented by the test program.
    283 ///
    284 /// \return An interface name.
    285 const std::string&
    286 engine::test_case::interface_name(void) const
    287 {
    288     return _pimpl->interface_name;
    289 }
    290 
    291 
    292 /// Gets the test program this test case belongs to.
    293 ///
    294 /// \return A reference to the container test program.
    295 const engine::test_program&
    296 engine::test_case::container_test_program(void) const
    297 {
    298     return _pimpl->_test_program;
    299 }
    300 
    301 
    302 /// Gets the test case name.
    303 ///
    304 /// \return The test case name, relative to the test program.
    305 const std::string&
    306 engine::test_case::name(void) const
    307 {
    308     return _pimpl->name;
    309 }
    310 
    311 
    312 /// Gets the test case metadata.
    313 ///
    314 /// \return The test case metadata.
    315 const engine::metadata&
    316 engine::test_case::get_metadata(void) const
    317 {
    318     return _pimpl->md;
    319 }
    320 
    321 
    322 /// Gets the fake result pre-stored for this test case.
    323 ///
    324 /// \return A fake result, or none if not defined.
    325 optional< engine::test_result >
    326 engine::test_case::fake_result(void) const
    327 {
    328     return _pimpl->fake_result;
    329 }
    330 
    331 
    332 /// Equality comparator.
    333 ///
    334 /// \warning Because test cases reference their container test programs, and
    335 /// test programs include test cases, we cannot perform a full comparison here:
    336 /// otherwise, we'd enter an inifinte loop.  Therefore, out of necessity, this
    337 /// does NOT compare whether the container test programs of the affected test
    338 /// cases are the same.
    339 ///
    340 /// \param other The other object to compare this one to.
    341 ///
    342 /// \return True if this object and other are equal; false otherwise.
    343 bool
    344 engine::test_case::operator==(const test_case& other) const
    345 {
    346     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
    347 }
    348 
    349 
    350 /// Inequality comparator.
    351 ///
    352 /// \param other The other object to compare this one to.
    353 ///
    354 /// \return True if this object and other are different; false otherwise.
    355 bool
    356 engine::test_case::operator!=(const test_case& other) const
    357 {
    358     return !(*this == other);
    359 }
    360 
    361 
    362 /// Injects the object into a stream.
    363 ///
    364 /// \param output The stream into which to inject the object.
    365 /// \param object The object to format.
    366 ///
    367 /// \return The output stream.
    368 std::ostream&
    369 engine::operator<<(std::ostream& output, const test_case& object)
    370 {
    371     // We skip injecting container_test_program() on purpose to avoid a loop.
    372     output << F("test_case{interface=%s, name=%s, metadata=%s}")
    373         % text::quote(object.interface_name(), '\'')
    374         % text::quote(object.name(), '\'')
    375         % object.get_metadata();
    376     return output;
    377 }
    378 
    379 
    380 /// Runs the test case in debug mode.
    381 ///
    382 /// Debug mode gives the caller more control on the execution of the test.  It
    383 /// should not be used for normal execution of tests; instead, call run().
    384 ///
    385 /// \param test_case The test case to debug.
    386 /// \param user_config The user configuration that defines the execution of this
    387 ///     test case.
    388 /// \param hooks Hooks to introspect the execution of the test case.
    389 /// \param work_directory A directory that can be used to place temporary files.
    390 /// \param stdout_path The file to which to redirect the stdout of the test.
    391 ///     For interactive debugging, '/dev/stdout' is probably a reasonable value.
    392 /// \param stderr_path The file to which to redirect the stdout of the test.
    393 ///     For interactive debugging, '/dev/stderr' is probably a reasonable value.
    394 ///
    395 /// \return The result of the execution of the test case.
    396 engine::test_result
    397 engine::debug_test_case(const test_case* test_case,
    398                         const config::tree& user_config,
    399                         test_case_hooks& hooks,
    400                         const fs::path& work_directory,
    401                         const fs::path& stdout_path,
    402                         const fs::path& stderr_path)
    403 {
    404     if (test_case->fake_result())
    405         return test_case->fake_result().get();
    406 
    407     const std::string skip_reason = check_reqs(
    408         test_case->get_metadata(), user_config,
    409         test_case->container_test_program().test_suite_name());
    410     if (!skip_reason.empty())
    411         return test_result(test_result::skipped, skip_reason);
    412 
    413     if (!fs::exists(test_case->container_test_program().absolute_path()))
    414         return test_result(test_result::broken, "Test program does not exist");
    415 
    416     const fs::auto_file result_file(work_directory / "result.txt");
    417 
    418     const engine::test_program& test_program =
    419         test_case->container_test_program();
    420 
    421     try {
    422         const engine::tester tester = create_tester(
    423             test_program.interface_name(), test_case->get_metadata(),
    424             user_config);
    425         tester.test(test_program.absolute_path(), test_case->name(),
    426                     result_file.file(), stdout_path, stderr_path,
    427                     generate_tester_config(test_case->get_metadata(),
    428                                            user_config,
    429                                            test_program.test_suite_name()));
    430 
    431         hooks.got_stdout(stdout_path);
    432         hooks.got_stderr(stderr_path);
    433 
    434         std::ifstream result_input(result_file.file().c_str());
    435         return engine::test_result::parse(result_input);
    436     } catch (const std::runtime_error& e) {
    437         // One of the possible explanation for us getting here is if the tester
    438         // crashes or doesn't behave as expected.  We must record any output
    439         // from the process so that we can debug it further.
    440         hooks.got_stdout(stdout_path);
    441         hooks.got_stderr(stderr_path);
    442 
    443         return engine::test_result(
    444             engine::test_result::broken,
    445             F("Caught unexpected exception: %s") % e.what());
    446     }
    447 }
    448 
    449 
    450 /// Runs the test case.
    451 ///
    452 /// \param test_case The test case to run.
    453 /// \param user_config The user configuration that defines the execution of this
    454 ///     test case.
    455 /// \param hooks Hooks to introspect the execution of the test case.
    456 /// \param work_directory A directory that can be used to place temporary files.
    457 ///
    458 /// \return The result of the execution of the test case.
    459 engine::test_result
    460 engine::run_test_case(const test_case* test_case,
    461                       const config::tree& user_config,
    462                       test_case_hooks& hooks,
    463                       const fs::path& work_directory)
    464 {
    465     if (test_case->fake_result())
    466         return test_case->fake_result().get();
    467 
    468     const std::string skip_reason = check_reqs(
    469         test_case->get_metadata(), user_config,
    470         test_case->container_test_program().test_suite_name());
    471     if (!skip_reason.empty())
    472         return test_result(test_result::skipped, skip_reason);
    473 
    474     if (!fs::exists(test_case->container_test_program().absolute_path()))
    475         return test_result(test_result::broken, "Test program does not exist");
    476 
    477     const fs::auto_file stdout_file(work_directory / "stdout.txt");
    478     const fs::auto_file stderr_file(work_directory / "stderr.txt");
    479     const fs::auto_file result_file(work_directory / "result.txt");
    480 
    481     const engine::test_program& test_program =
    482         test_case->container_test_program();
    483 
    484     try {
    485         const engine::tester tester = create_tester(
    486             test_program.interface_name(), test_case->get_metadata(),
    487             user_config);
    488         tester.test(test_program.absolute_path(), test_case->name(),
    489                     result_file.file(), stdout_file.file(), stderr_file.file(),
    490                     generate_tester_config(test_case->get_metadata(),
    491                                            user_config,
    492                                            test_program.test_suite_name()));
    493 
    494         hooks.got_stdout(stdout_file.file());
    495         hooks.got_stderr(stderr_file.file());
    496 
    497         std::ifstream result_input(result_file.file().c_str());
    498         return engine::test_result::parse(result_input);
    499     } catch (const std::runtime_error& e) {
    500         // One of the possible explanation for us getting here is if the tester
    501         // crashes or doesn't behave as expected.  We must record any output
    502         // from the process so that we can debug it further.
    503         hooks.got_stdout(stdout_file.file());
    504         hooks.got_stderr(stderr_file.file());
    505 
    506         return engine::test_result(
    507             engine::test_result::broken,
    508             F("Caught unexpected exception: %s") % e.what());
    509     }
    510 }
    511