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