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