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_program.hpp"
     30 
     31 #include <algorithm>
     32 #include <map>
     33 #include <sstream>
     34 #include <stdexcept>
     35 
     36 #include <lutok/operations.hpp>
     37 #include <lutok/state.ipp>
     38 
     39 #include "engine/exceptions.hpp"
     40 #include "engine/test_result.hpp"
     41 #include "engine/testers.hpp"
     42 #include "utils/format/macros.hpp"
     43 #include "utils/logging/macros.hpp"
     44 #include "utils/logging/operations.hpp"
     45 #include "utils/optional.ipp"
     46 #include "utils/sanity.hpp"
     47 #include "utils/text/operations.ipp"
     48 
     49 namespace fs = utils::fs;
     50 namespace logging = utils::logging;
     51 namespace text = utils::text;
     52 
     53 using utils::none;
     54 using utils::optional;
     55 
     56 
     57 namespace {
     58 
     59 
     60 /// Lua hook for the test_case function.
     61 ///
     62 /// \pre state(-1) contains the arguments to the function.
     63 ///
     64 /// \param state The Lua state in which we are running.
     65 ///
     66 /// \return The number of return values, which is always 0.
     67 static int
     68 lua_test_case(lutok::state& state)
     69 {
     70     if (!state.is_table())
     71         throw std::runtime_error("Oh noes"); // XXX
     72 
     73     state.get_global("_test_cases");
     74     engine::test_cases_vector* test_cases =
     75         *state.to_userdata< engine::test_cases_vector* >();
     76     state.pop(1);
     77 
     78     state.get_global("_test_program");
     79     const engine::test_program* test_program =
     80         *state.to_userdata< engine::test_program* >();
     81     state.pop(1);
     82 
     83     state.push_string("name");
     84     state.get_table(-2);
     85     const std::string name = state.to_string();
     86     state.pop(1);
     87 
     88     engine::metadata_builder mdbuilder(test_program->get_metadata());
     89 
     90     state.push_nil();
     91     while (state.next(-2)) {
     92         if (!state.is_string(-2))
     93             throw std::runtime_error("Oh oh");  // XXX
     94         const std::string property = state.to_string(-2);
     95 
     96         if (!state.is_string(-1))
     97             throw std::runtime_error("Oh oh");  // XXX
     98         const std::string value = state.to_string(-1);
     99 
    100         if (property != "name")
    101             mdbuilder.set_string(property, value);
    102 
    103         state.pop(1);
    104     }
    105     state.pop(1);
    106 
    107     engine::test_case_ptr test_case(
    108         new engine::test_case(test_program->interface_name(), *test_program,
    109                               name, mdbuilder.build()));
    110     test_cases->push_back(test_case);
    111 
    112     return 0;
    113 }
    114 
    115 
    116 /// Sets up the Lua state to process the output of a test case list.
    117 ///
    118 /// \param [in,out] state The Lua state to configure.
    119 /// \param test_program Pointer to the test program being loaded.
    120 /// \param [out] test_cases Vector that will contain the list of test cases.
    121 static void
    122 setup_lua_state(lutok::state& state, const engine::test_program* test_program,
    123                 engine::test_cases_vector* test_cases)
    124 {
    125     *state.new_userdata< engine::test_cases_vector* >() = test_cases;
    126     state.set_global("_test_cases");
    127 
    128     *state.new_userdata< const engine::test_program* >() = test_program;
    129     state.set_global("_test_program");
    130 
    131     state.push_cxx_function(lua_test_case);
    132     state.set_global("test_case");
    133 }
    134 
    135 
    136 /// Loads the list of test cases from a test program.
    137 ///
    138 /// \param test_program Representation of the test program to load.
    139 ///
    140 /// \return A list of test cases.
    141 static engine::test_cases_vector
    142 load_test_cases(const engine::test_program& test_program)
    143 {
    144     const engine::tester tester(test_program.interface_name(), none, none);
    145     const std::string output = tester.list(test_program.absolute_path());
    146 
    147     engine::test_cases_vector test_cases;
    148     lutok::state state;
    149     setup_lua_state(state, &test_program, &test_cases);
    150     lutok::do_string(state, output, 0);
    151     return test_cases;
    152 }
    153 
    154 
    155 /// Predicate to compare two test cases via pointers to them.
    156 ///
    157 /// \param tc1 Entry in a map of test case names to test case pointers.
    158 /// \param tc2 Entry in a map of test case names to test case pointers.
    159 ///
    160 /// \return True if the test case in tc1 is the same as in tc2.  Note that the
    161 /// container test programs are NOT compared.
    162 static bool
    163 compare_test_case(const std::pair< std::string, engine::test_case_ptr >& tc1,
    164                   const std::pair< std::string, engine::test_case_ptr >& tc2)
    165 {
    166     return tc1.first == tc2.first && *tc1.second == *tc2.second;
    167 }
    168 
    169 
    170 /// Compares if two sets of test cases hold the same values.
    171 ///
    172 /// \param tests1 First collection of test cases.
    173 /// \param tests2 Second collection of test cases.
    174 ///
    175 /// \return True if both collections hold the same test cases (value-wise, not
    176 /// pointer-wise); false otherwise.
    177 static bool
    178 compare_test_cases(const optional< engine::test_cases_vector >& tests1,
    179                    const optional< engine::test_cases_vector >& tests2)
    180 {
    181     if (!tests1 && !tests2)
    182         return true;
    183     else if ((tests1 && !tests2) || (!tests1 && tests2))
    184         return false;
    185     INV(tests1 && tests2);
    186 
    187     // This is very inefficient, but because it should only be used in our own
    188     // tests, it doesn't matter.
    189     std::map< std::string, engine::test_case_ptr > map1, map2;
    190     for (engine::test_cases_vector::const_iterator iter = tests1.get().begin();
    191          iter != tests1.get().end(); ++iter)
    192         map1.insert(make_pair((*iter)->name(), *iter));
    193     for (engine::test_cases_vector::const_iterator iter = tests2.get().begin();
    194          iter != tests2.get().end(); ++iter)
    195         map2.insert(make_pair((*iter)->name(), *iter));
    196     return std::equal(map1.begin(), map1.end(), map2.begin(),
    197                       compare_test_case);
    198 }
    199 
    200 
    201 }  // anonymous namespace
    202 
    203 
    204 /// Internal implementation of a test_program.
    205 struct engine::test_program::impl {
    206     /// Name of the test program interface.
    207     std::string interface_name;
    208 
    209     /// Name of the test program binary relative to root.
    210     fs::path binary;
    211 
    212     /// Root of the test suite containing the test program.
    213     fs::path root;
    214 
    215     /// Name of the test suite this program belongs to.
    216     std::string test_suite_name;
    217 
    218     /// Metadata of the test program.
    219     metadata md;
    220 
    221     /// List of test cases in the test program; lazily initialized.
    222     optional< test_cases_vector > test_cases;
    223 
    224     /// Constructor.
    225     ///
    226     /// \param interface_name_ Name of the test program interface.
    227     /// \param binary_ The name of the test program binary relative to root_.
    228     /// \param root_ The root of the test suite containing the test program.
    229     /// \param test_suite_name_ The name of the test suite this program
    230     ///     belongs to.
    231     /// \param md_ Metadata of the test program.
    232     impl(const std::string& interface_name_, const fs::path& binary_,
    233          const fs::path& root_, const std::string& test_suite_name_,
    234          const metadata& md_) :
    235         interface_name(interface_name_),
    236         binary(binary_),
    237         root(root_),
    238         test_suite_name(test_suite_name_),
    239         md(md_)
    240     {
    241         PRE_MSG(!binary.is_absolute(),
    242                 F("The program '%s' must be relative to the root of the test "
    243                   "suite '%s'") % binary % root);
    244     }
    245 
    246     /// Equality comparator.
    247     ///
    248     /// \param other The other object to compare this one to.
    249     ///
    250     /// \return True if this object and other are equal; false otherwise.
    251     bool
    252     operator==(const impl& other) const
    253     {
    254         return (interface_name == other.interface_name &&
    255                 binary == other.binary &&
    256                 root == other.root &&
    257                 test_suite_name == other.test_suite_name &&
    258                 md == other.md &&
    259                 compare_test_cases(test_cases, other.test_cases));
    260     }
    261 };
    262 
    263 
    264 /// Constructs a new test program.
    265 ///
    266 /// \param interface_name_ Name of the test program interface.
    267 /// \param binary_ The name of the test program binary relative to root_.
    268 /// \param root_ The root of the test suite containing the test program.
    269 /// \param test_suite_name_ The name of the test suite this program belongs to.
    270 /// \param md_ Metadata of the test program.
    271 engine::test_program::test_program(const std::string& interface_name_,
    272                                    const fs::path& binary_,
    273                                    const fs::path& root_,
    274                                    const std::string& test_suite_name_,
    275                                    const metadata& md_) :
    276     _pimpl(new impl(interface_name_, binary_, root_, test_suite_name_, md_))
    277 {
    278 }
    279 
    280 
    281 /// Destroys a test program.
    282 engine::test_program::~test_program(void)
    283 {
    284 }
    285 
    286 
    287 /// Gets the name of the test program interface.
    288 ///
    289 /// \return An interface name.
    290 const std::string&
    291 engine::test_program::interface_name(void) const
    292 {
    293     return _pimpl->interface_name;
    294 }
    295 
    296 
    297 /// Gets the path to the test program relative to the root of the test suite.
    298 ///
    299 /// \return The relative path to the test program binary.
    300 const fs::path&
    301 engine::test_program::relative_path(void) const
    302 {
    303     return _pimpl->binary;
    304 }
    305 
    306 
    307 /// Gets the absolute path to the test program.
    308 ///
    309 /// \return The absolute path to the test program binary.
    310 const fs::path
    311 engine::test_program::absolute_path(void) const
    312 {
    313     const fs::path full_path = _pimpl->root / _pimpl->binary;
    314     return full_path.is_absolute() ? full_path : full_path.to_absolute();
    315 }
    316 
    317 
    318 /// Gets the root of the test suite containing this test program.
    319 ///
    320 /// \return The path to the root of the test suite.
    321 const fs::path&
    322 engine::test_program::root(void) const
    323 {
    324     return _pimpl->root;
    325 }
    326 
    327 
    328 /// Gets the name of the test suite containing this test program.
    329 ///
    330 /// \return The name of the test suite.
    331 const std::string&
    332 engine::test_program::test_suite_name(void) const
    333 {
    334     return _pimpl->test_suite_name;
    335 }
    336 
    337 
    338 /// Gets the metadata of the test program.
    339 ///
    340 /// \return The metadata.
    341 const engine::metadata&
    342 engine::test_program::get_metadata(void) const
    343 {
    344     return _pimpl->md;
    345 }
    346 
    347 
    348 /// Gets a test case by its name.
    349 ///
    350 /// \param name The name of the test case to locate.
    351 ///
    352 /// \return The requested test case.
    353 ///
    354 /// \throw not_found_error If the specified test case is not in the test
    355 ///     program.
    356 const engine::test_case_ptr&
    357 engine::test_program::find(const std::string& name) const
    358 {
    359     // TODO(jmmv): Should use a test_cases_map instead of a vector to optimize
    360     // lookups.
    361     const test_cases_vector& tcs = test_cases();
    362     for (test_cases_vector::const_iterator iter = tcs.begin();
    363          iter != tcs.end(); iter++) {
    364         if ((*iter)->name() == name)
    365             return *iter;
    366     }
    367     throw not_found_error(F("Unknown test case %s in test program %s") % name %
    368                           relative_path());
    369 }
    370 
    371 
    372 /// Gets the list of test cases from the test program.
    373 ///
    374 /// Note that this operation may be expensive because it may lazily load the
    375 /// test cases list from the test program.  Errors during the processing of the
    376 /// test case list are represented as a single test case describing the failure.
    377 ///
    378 /// \return The list of test cases provided by the test program.
    379 const engine::test_cases_vector&
    380 engine::test_program::test_cases(void) const
    381 {
    382     if (!_pimpl->test_cases) {
    383         try {
    384             _pimpl->test_cases = load_test_cases(*this);
    385         } catch (const std::runtime_error& e) {
    386             // TODO(jmmv): This is a very ugly workaround for the fact that we
    387             // cannot report failures at the test-program level.  We should
    388             // either address this, or move this reporting to the testers
    389             // themselves.
    390             LW(F("Failed to load test cases list: %s") % e.what());
    391             engine::test_cases_vector fake_test_cases;
    392             fake_test_cases.push_back(test_case_ptr(new test_case(
    393                 _pimpl->interface_name, *this, "__test_cases_list__",
    394                 "Represents the correct processing of the test cases list",
    395                 test_result(engine::test_result::broken, e.what()))));
    396             _pimpl->test_cases = fake_test_cases;
    397         }
    398     }
    399     return _pimpl->test_cases.get();
    400 }
    401 
    402 
    403 /// Sets the collection of test cases included in this test program.
    404 ///
    405 /// This function is provided so that when we load test programs from the
    406 /// database we can populate them with the test cases they include.  We don't
    407 /// want such test programs to be executed to gather this information.
    408 ///
    409 /// We cannot provide this collection of tests in the constructor of the test
    410 /// program because the test cases have to point to their test programs.
    411 ///
    412 /// \pre The test program must not have attempted to load its test cases yet.
    413 ///     I.e. test_cases() has not been called.
    414 ///
    415 /// \param test_cases_ The test cases to add to this test program.
    416 void
    417 engine::test_program::set_test_cases(const test_cases_vector& test_cases_)
    418 {
    419     PRE(!_pimpl->test_cases);
    420     _pimpl->test_cases = test_cases_;
    421 }
    422 
    423 
    424 /// Equality comparator.
    425 ///
    426 /// \param other The other object to compare this one to.
    427 ///
    428 /// \return True if this object and other are equal; false otherwise.
    429 bool
    430 engine::test_program::operator==(const test_program& other) const
    431 {
    432     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
    433 }
    434 
    435 
    436 /// Inequality comparator.
    437 ///
    438 /// \param other The other object to compare this one to.
    439 ///
    440 /// \return True if this object and other are different; false otherwise.
    441 bool
    442 engine::test_program::operator!=(const test_program& other) const
    443 {
    444     return !(*this == other);
    445 }
    446 
    447 
    448 /// Injects the object into a stream.
    449 ///
    450 /// \param output The stream into which to inject the object.
    451 /// \param object The object to format.
    452 ///
    453 /// \return The output stream.
    454 std::ostream&
    455 engine::operator<<(std::ostream& output, const test_cases_vector& object)
    456 {
    457     output << "[";
    458     for (test_cases_vector::size_type i = 0; i < object.size(); ++i) {
    459         if (i != 0)
    460             output << ", ";
    461         output << *object[i];
    462     }
    463     output << "]";
    464     return output;
    465 }
    466 
    467 
    468 /// Injects the object into a stream.
    469 ///
    470 /// \param output The stream into which to inject the object.
    471 /// \param object The object to format.
    472 ///
    473 /// \return The output stream.
    474 std::ostream&
    475 engine::operator<<(std::ostream& output, const test_program& object)
    476 {
    477     output << F("test_program{interface=%s, binary=%s, root=%s, test_suite=%s, "
    478                 "metadata=%s, test_cases=%s}")
    479         % text::quote(object.interface_name(), '\'')
    480         % text::quote(object.relative_path().str(), '\'')
    481         % text::quote(object.root().str(), '\'')
    482         % text::quote(object.test_suite_name(), '\'')
    483         % object.get_metadata()
    484         % object.test_cases();
    485     return output;
    486 }
    487