1 // Copyright 2010 The Kyua Authors. 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/kyuafile.hpp" 30 31 #include <algorithm> 32 #include <iterator> 33 #include <stdexcept> 34 35 #include <lutok/exceptions.hpp> 36 #include <lutok/operations.hpp> 37 #include <lutok/stack_cleaner.hpp> 38 #include <lutok/state.ipp> 39 40 #include "engine/exceptions.hpp" 41 #include "engine/scheduler.hpp" 42 #include "model/metadata.hpp" 43 #include "model/test_program.hpp" 44 #include "utils/config/exceptions.hpp" 45 #include "utils/config/tree.ipp" 46 #include "utils/datetime.hpp" 47 #include "utils/format/macros.hpp" 48 #include "utils/fs/lua_module.hpp" 49 #include "utils/fs/operations.hpp" 50 #include "utils/logging/macros.hpp" 51 #include "utils/noncopyable.hpp" 52 #include "utils/optional.ipp" 53 #include "utils/sanity.hpp" 54 55 namespace config = utils::config; 56 namespace datetime = utils::datetime; 57 namespace fs = utils::fs; 58 namespace scheduler = engine::scheduler; 59 60 using utils::none; 61 using utils::optional; 62 63 64 // History of Kyuafile file versions: 65 // 66 // 3 - DOES NOT YET EXIST. Pending changes for when this is introduced: 67 // 68 // * Revisit what to do about the test_suite definition. Support for 69 // per-test program overrides is deprecated and should be removed. 70 // But, maybe, the whole test_suite definition idea is wrong and we 71 // should instead be explicitly telling which configuration variables 72 // to "inject" into each test program. 73 // 74 // 2 - Changed the syntax() call to take only a version number, instead of the 75 // word 'config' as the first argument and the version as the second one. 76 // Files now start with syntax(2) instead of syntax('kyuafile', 1). 77 // 78 // 1 - Initial version. 79 80 81 namespace { 82 83 84 static int lua_current_kyuafile(lutok::state&); 85 static int lua_generic_test_program(lutok::state&); 86 static int lua_include(lutok::state&); 87 static int lua_syntax(lutok::state&); 88 static int lua_test_suite(lutok::state&); 89 90 91 /// Concatenates two paths while avoiding paths to start with './'. 92 /// 93 /// \param root Path to the directory containing the file. 94 /// \param file Path to concatenate to root. Cannot be absolute. 95 /// 96 /// \return The concatenated path. 97 static fs::path 98 relativize(const fs::path& root, const fs::path& file) 99 { 100 PRE(!file.is_absolute()); 101 102 if (root == fs::path(".")) 103 return file; 104 else 105 return root / file; 106 } 107 108 109 /// Implementation of a parser for Kyuafiles. 110 /// 111 /// The main purpose of having this as a class is to keep track of global state 112 /// within the Lua files and allowing the Lua callbacks to easily access such 113 /// data. 114 class parser : utils::noncopyable { 115 /// Lua state to parse a single Kyuafile file. 116 lutok::state _state; 117 118 /// Root directory of the test suite represented by the Kyuafile. 119 const fs::path _source_root; 120 121 /// Root directory of the test programs. 122 const fs::path _build_root; 123 124 /// Name of the Kyuafile to load relative to _source_root. 125 const fs::path _relative_filename; 126 127 /// Version of the Kyuafile file format requested by the parsed file. 128 /// 129 /// This is set once the Kyuafile invokes the syntax() call. 130 optional< int > _version; 131 132 /// Name of the test suite defined by the Kyuafile. 133 /// 134 /// This is set once the Kyuafile invokes the test_suite() call. 135 optional< std::string > _test_suite; 136 137 /// Collection of test programs defined by the Kyuafile. 138 /// 139 /// This acts as an accumulator for all the *_test_program() calls within 140 /// the Kyuafile. 141 model::test_programs_vector _test_programs; 142 143 /// Safely gets _test_suite and respects any test program overrides. 144 /// 145 /// \param program_override The test program-specific test suite name. May 146 /// be empty to indicate no override. 147 /// 148 /// \return The name of the test suite. 149 /// 150 /// \throw std::runtime_error If program_override is empty and the Kyuafile 151 /// did not yet define the global name of the test suite. 152 std::string 153 get_test_suite(const std::string& program_override) 154 { 155 std::string test_suite; 156 157 if (program_override.empty()) { 158 if (!_test_suite) { 159 throw std::runtime_error("No test suite defined in the " 160 "Kyuafile and no override provided in " 161 "the test_program definition"); 162 } 163 test_suite = _test_suite.get(); 164 } else { 165 test_suite = program_override; 166 } 167 168 return test_suite; 169 } 170 171 public: 172 /// Initializes the parser and the Lua state. 173 /// 174 /// \param source_root_ The root directory of the test suite represented by 175 /// the Kyuafile. 176 /// \param build_root_ The root directory of the test programs. 177 /// \param relative_filename_ Name of the Kyuafile to load relative to 178 /// source_root_. 179 /// \param user_config User configuration holding any test suite properties 180 /// to be passed to the list operation. 181 /// \param scheduler_handle The scheduler context to use for loading the 182 /// test case lists. 183 parser(const fs::path& source_root_, const fs::path& build_root_, 184 const fs::path& relative_filename_, 185 const config::tree& user_config, 186 scheduler::scheduler_handle& scheduler_handle) : 187 _source_root(source_root_), _build_root(build_root_), 188 _relative_filename(relative_filename_) 189 { 190 lutok::stack_cleaner cleaner(_state); 191 192 _state.push_cxx_function(lua_syntax); 193 _state.set_global("syntax"); 194 195 *_state.new_userdata< parser* >() = this; 196 _state.set_global("_parser"); 197 198 _state.push_cxx_function(lua_current_kyuafile); 199 _state.set_global("current_kyuafile"); 200 201 *_state.new_userdata< const config::tree* >() = &user_config; 202 *_state.new_userdata< scheduler::scheduler_handle* >() = 203 &scheduler_handle; 204 _state.push_cxx_closure(lua_include, 2); 205 _state.set_global("include"); 206 207 _state.push_cxx_function(lua_test_suite); 208 _state.set_global("test_suite"); 209 210 const std::set< std::string > interfaces = 211 scheduler::registered_interface_names(); 212 for (std::set< std::string >::const_iterator iter = interfaces.begin(); 213 iter != interfaces.end(); ++iter) { 214 const std::string& interface = *iter; 215 216 _state.push_string(interface); 217 *_state.new_userdata< const config::tree* >() = &user_config; 218 *_state.new_userdata< scheduler::scheduler_handle* >() = 219 &scheduler_handle; 220 _state.push_cxx_closure(lua_generic_test_program, 3); 221 _state.set_global(interface + "_test_program"); 222 } 223 224 _state.open_base(); 225 _state.open_string(); 226 _state.open_table(); 227 fs::open_fs(_state, callback_current_kyuafile().branch_path()); 228 } 229 230 /// Destructor. 231 ~parser(void) 232 { 233 } 234 235 /// Gets the parser object associated to a Lua state. 236 /// 237 /// \param state The Lua state from which to obtain the parser object. 238 /// 239 /// \return A pointer to the parser. 240 static parser* 241 get_from_state(lutok::state& state) 242 { 243 lutok::stack_cleaner cleaner(state); 244 state.get_global("_parser"); 245 return *state.to_userdata< parser* >(-1); 246 } 247 248 /// Callback for the Kyuafile current_kyuafile() function. 249 /// 250 /// \return Returns the absolute path to the current Kyuafile. 251 fs::path 252 callback_current_kyuafile(void) const 253 { 254 const fs::path file = relativize(_source_root, _relative_filename); 255 if (file.is_absolute()) 256 return file; 257 else 258 return file.to_absolute(); 259 } 260 261 /// Callback for the Kyuafile include() function. 262 /// 263 /// \post _test_programs is extended with the the test programs defined by 264 /// the included file. 265 /// 266 /// \param raw_file Path to the file to include. 267 /// \param user_config User configuration holding any test suite properties 268 /// to be passed to the list operation. 269 /// \param scheduler_handle Scheduler context to run test programs in. 270 void 271 callback_include(const fs::path& raw_file, 272 const config::tree& user_config, 273 scheduler::scheduler_handle& scheduler_handle) 274 { 275 const fs::path file = relativize(_relative_filename.branch_path(), 276 raw_file); 277 const model::test_programs_vector subtps = 278 parser(_source_root, _build_root, file, user_config, 279 scheduler_handle).parse(); 280 281 std::copy(subtps.begin(), subtps.end(), 282 std::back_inserter(_test_programs)); 283 } 284 285 /// Callback for the Kyuafile syntax() function. 286 /// 287 /// \post _version is set to the requested version. 288 /// 289 /// \param version Version of the Kyuafile syntax requested by the file. 290 /// 291 /// \throw std::runtime_error If the format or the version are invalid, or 292 /// if syntax() has already been called. 293 void 294 callback_syntax(const int version) 295 { 296 if (_version) 297 throw std::runtime_error("Can only call syntax() once"); 298 299 if (version < 1 || version > 2) 300 throw std::runtime_error(F("Unsupported file version %s") % 301 version); 302 303 _version = utils::make_optional(version); 304 } 305 306 /// Callback for the various Kyuafile *_test_program() functions. 307 /// 308 /// \post _test_programs is extended to include the newly defined test 309 /// program. 310 /// 311 /// \param interface Name of the test program interface. 312 /// \param raw_path Path to the test program, relative to the Kyuafile. 313 /// This has to be adjusted according to the relative location of this 314 /// Kyuafile to _source_root. 315 /// \param test_suite_override Name of the test suite this test program 316 /// belongs to, if explicitly defined at the test program level. 317 /// \param metadata Metadata variables passed to the test program. 318 /// \param user_config User configuration holding any test suite properties 319 /// to be passed to the list operation. 320 /// \param scheduler_handle Scheduler context to run test programs in. 321 /// 322 /// \throw std::runtime_error If the test program definition is invalid or 323 /// if the test program does not exist. 324 void 325 callback_test_program(const std::string& interface, 326 const fs::path& raw_path, 327 const std::string& test_suite_override, 328 const model::metadata& metadata, 329 const config::tree& user_config, 330 scheduler::scheduler_handle& scheduler_handle) 331 { 332 if (raw_path.is_absolute()) 333 throw std::runtime_error(F("Got unexpected absolute path for test " 334 "program '%s'") % raw_path); 335 else if (raw_path.str() != raw_path.leaf_name()) 336 throw std::runtime_error(F("Test program '%s' cannot contain path " 337 "components") % raw_path); 338 339 const fs::path path = relativize(_relative_filename.branch_path(), 340 raw_path); 341 342 if (!fs::exists(_build_root / path)) 343 throw std::runtime_error(F("Non-existent test program '%s'") % 344 path); 345 346 const std::string test_suite = get_test_suite(test_suite_override); 347 348 _test_programs.push_back(model::test_program_ptr( 349 new scheduler::lazy_test_program(interface, path, _build_root, 350 test_suite, metadata, user_config, 351 scheduler_handle))); 352 } 353 354 /// Callback for the Kyuafile test_suite() function. 355 /// 356 /// \post _version is set to the requested version. 357 /// 358 /// \param name Name of the test suite. 359 /// 360 /// \throw std::runtime_error If test_suite() has already been called. 361 void 362 callback_test_suite(const std::string& name) 363 { 364 if (_test_suite) 365 throw std::runtime_error("Can only call test_suite() once"); 366 _test_suite = utils::make_optional(name); 367 } 368 369 /// Parses the Kyuafile. 370 /// 371 /// \pre Can only be invoked once. 372 /// 373 /// \return The collection of test programs defined by the Kyuafile. 374 /// 375 /// \throw load_error If there is any problem parsing the file. 376 const model::test_programs_vector& 377 parse(void) 378 { 379 PRE(_test_programs.empty()); 380 381 const fs::path load_path = relativize(_source_root, _relative_filename); 382 try { 383 lutok::do_file(_state, load_path.str(), 0, 0, 0); 384 } catch (const std::runtime_error& e) { 385 // It is tempting to think that all of our various auxiliary 386 // functions above could raise load_error by themselves thus making 387 // this exception rewriting here unnecessary. Howver, that would 388 // not work because the helper functions above are executed within a 389 // Lua context, and we lose their type when they are propagated out 390 // of it. 391 throw engine::load_error(load_path, e.what()); 392 } 393 394 if (!_version) 395 throw engine::load_error(load_path, "syntax() never called"); 396 397 return _test_programs; 398 } 399 }; 400 401 402 /// Glue to invoke parser::callback_test_program() from Lua. 403 /// 404 /// This is a helper function for the various *_test_program() calls, as they 405 /// only differ in the interface of the defined test program. 406 /// 407 /// \pre state(-1) A table with the arguments that define the test program. The 408 /// special argument 'test_suite' provides an override to the global test suite 409 /// name. The rest of the arguments are part of the test program metadata. 410 /// \pre state(upvalue 1) String with the name of the interface. 411 /// \pre state(upvalue 2) User configuration with the per-test suite settings. 412 /// \pre state(upvalue 3) Scheduler context to run test programs in. 413 /// 414 /// \param state The Lua state that executed the function. 415 /// 416 /// \return Number of return values left on the Lua stack. 417 /// 418 /// \throw std::runtime_error If the arguments to the function are invalid. 419 static int 420 lua_generic_test_program(lutok::state& state) 421 { 422 if (!state.is_string(state.upvalue_index(1))) 423 throw std::runtime_error("Found corrupt state for test_program " 424 "function"); 425 const std::string interface = state.to_string(state.upvalue_index(1)); 426 427 if (!state.is_userdata(state.upvalue_index(2))) 428 throw std::runtime_error("Found corrupt state for test_program " 429 "function"); 430 const config::tree* user_config = *state.to_userdata< const config::tree* >( 431 state.upvalue_index(2)); 432 433 if (!state.is_userdata(state.upvalue_index(3))) 434 throw std::runtime_error("Found corrupt state for test_program " 435 "function"); 436 scheduler::scheduler_handle* scheduler_handle = 437 *state.to_userdata< scheduler::scheduler_handle* >( 438 state.upvalue_index(3)); 439 440 if (!state.is_table(-1)) 441 throw std::runtime_error( 442 F("%s_test_program expects a table of properties as its single " 443 "argument") % interface); 444 445 scheduler::ensure_valid_interface(interface); 446 447 lutok::stack_cleaner cleaner(state); 448 449 state.push_string("name"); 450 state.get_table(-2); 451 if (!state.is_string(-1)) 452 throw std::runtime_error("Test program name not defined or not a " 453 "string"); 454 const fs::path path(state.to_string(-1)); 455 state.pop(1); 456 457 state.push_string("test_suite"); 458 state.get_table(-2); 459 std::string test_suite; 460 if (state.is_nil(-1)) { 461 // Leave empty to use the global test-suite value. 462 } else if (state.is_string(-1)) { 463 test_suite = state.to_string(-1); 464 } else { 465 throw std::runtime_error(F("Found non-string value in the test_suite " 466 "property of test program '%s'") % path); 467 } 468 state.pop(1); 469 470 model::metadata_builder mdbuilder; 471 state.push_nil(); 472 while (state.next(-2)) { 473 if (!state.is_string(-2)) 474 throw std::runtime_error(F("Found non-string metadata property " 475 "name in test program '%s'") % 476 path); 477 const std::string property = state.to_string(-2); 478 479 if (property != "name" && property != "test_suite") { 480 std::string value; 481 if (state.is_boolean(-1)) { 482 value = F("%s") % state.to_boolean(-1); 483 } else if (state.is_number(-1)) { 484 value = F("%s") % state.to_integer(-1); 485 } else if (state.is_string(-1)) { 486 value = state.to_string(-1); 487 } else { 488 throw std::runtime_error( 489 F("Metadata property '%s' in test program '%s' cannot be " 490 "converted to a string") % property % path); 491 } 492 493 mdbuilder.set_string(property, value); 494 } 495 496 state.pop(1); 497 } 498 499 parser::get_from_state(state)->callback_test_program( 500 interface, path, test_suite, mdbuilder.build(), *user_config, 501 *scheduler_handle); 502 return 0; 503 } 504 505 506 /// Glue to invoke parser::callback_current_kyuafile() from Lua. 507 /// 508 /// \param state The Lua state that executed the function. 509 /// 510 /// \return Number of return values left on the Lua stack. 511 static int 512 lua_current_kyuafile(lutok::state& state) 513 { 514 state.push_string(parser::get_from_state(state)-> 515 callback_current_kyuafile().str()); 516 return 1; 517 } 518 519 520 /// Glue to invoke parser::callback_include() from Lua. 521 /// 522 /// \param state The Lua state that executed the function. 523 /// 524 /// \pre state(upvalue 1) User configuration with the per-test suite settings. 525 /// \pre state(upvalue 2) Scheduler context to run test programs in. 526 /// 527 /// \return Number of return values left on the Lua stack. 528 static int 529 lua_include(lutok::state& state) 530 { 531 if (!state.is_userdata(state.upvalue_index(1))) 532 throw std::runtime_error("Found corrupt state for test_program " 533 "function"); 534 const config::tree* user_config = *state.to_userdata< const config::tree* >( 535 state.upvalue_index(1)); 536 537 if (!state.is_userdata(state.upvalue_index(2))) 538 throw std::runtime_error("Found corrupt state for test_program " 539 "function"); 540 scheduler::scheduler_handle* scheduler_handle = 541 *state.to_userdata< scheduler::scheduler_handle* >( 542 state.upvalue_index(2)); 543 544 parser::get_from_state(state)->callback_include( 545 fs::path(state.to_string(-1)), *user_config, *scheduler_handle); 546 return 0; 547 } 548 549 550 /// Glue to invoke parser::callback_syntax() from Lua. 551 /// 552 /// \pre state(-2) The syntax format name, if a v1 file. 553 /// \pre state(-1) The syntax format version. 554 /// 555 /// \param state The Lua state that executed the function. 556 /// 557 /// \return Number of return values left on the Lua stack. 558 static int 559 lua_syntax(lutok::state& state) 560 { 561 if (!state.is_number(-1)) 562 throw std::runtime_error("Last argument to syntax must be a number"); 563 const int syntax_version = state.to_integer(-1); 564 565 if (syntax_version == 1) { 566 if (state.get_top() != 2) 567 throw std::runtime_error("Version 1 files need two arguments to " 568 "syntax()"); 569 if (!state.is_string(-2) || state.to_string(-2) != "kyuafile") 570 throw std::runtime_error("First argument to syntax must be " 571 "'kyuafile' for version 1 files"); 572 } else { 573 if (state.get_top() != 1) 574 throw std::runtime_error("syntax() only takes one argument"); 575 } 576 577 parser::get_from_state(state)->callback_syntax(syntax_version); 578 return 0; 579 } 580 581 582 /// Glue to invoke parser::callback_test_suite() from Lua. 583 /// 584 /// \param state The Lua state that executed the function. 585 /// 586 /// \return Number of return values left on the Lua stack. 587 static int 588 lua_test_suite(lutok::state& state) 589 { 590 parser::get_from_state(state)->callback_test_suite(state.to_string(-1)); 591 return 0; 592 } 593 594 595 } // anonymous namespace 596 597 598 /// Constructs a kyuafile form initialized data. 599 /// 600 /// Use load() to parse a test suite configuration file and construct a 601 /// kyuafile object. 602 /// 603 /// \param source_root_ The root directory for the test suite represented by the 604 /// Kyuafile. In other words, the directory containing the first Kyuafile 605 /// processed. 606 /// \param build_root_ The root directory for the test programs themselves. In 607 /// general, this will be the same as source_root_. If different, the 608 /// specified directory must follow the exact same layout of source_root_. 609 /// \param tps_ Collection of test programs that belong to this test suite. 610 engine::kyuafile::kyuafile(const fs::path& source_root_, 611 const fs::path& build_root_, 612 const model::test_programs_vector& tps_) : 613 _source_root(source_root_), 614 _build_root(build_root_), 615 _test_programs(tps_) 616 { 617 } 618 619 620 /// Destructor. 621 engine::kyuafile::~kyuafile(void) 622 { 623 } 624 625 626 /// Parses a test suite configuration file. 627 /// 628 /// \param file The file to parse. 629 /// \param user_build_root If not none, specifies a path to a directory 630 /// containing the test programs themselves. The layout of the build root 631 /// must match the layout of the source root (which is just the directory 632 /// from which the Kyuafile is being read). 633 /// \param user_config User configuration holding any test suite properties 634 /// to be passed to the list operation. 635 /// \param scheduler_handle The scheduler context to use for loading the test 636 /// case lists. 637 /// 638 /// \return High-level representation of the configuration file. 639 /// 640 /// \throw load_error If there is any problem loading the file. This includes 641 /// file access errors and syntax errors. 642 engine::kyuafile 643 engine::kyuafile::load(const fs::path& file, 644 const optional< fs::path > user_build_root, 645 const config::tree& user_config, 646 scheduler::scheduler_handle& scheduler_handle) 647 { 648 const fs::path source_root_ = file.branch_path(); 649 const fs::path build_root_ = user_build_root ? 650 user_build_root.get() : source_root_; 651 652 // test_program.absolute_path() uses the current work directory and that 653 // fails to resolve the correct path once we have used chdir to enter the 654 // test work directory. To prevent this causing issues down the road, 655 // force the build root to be absolute so that absolute_path() does not 656 // need to rely on the current work directory. 657 const fs::path abs_build_root = build_root_.is_absolute() ? 658 build_root_ : build_root_.to_absolute(); 659 660 return kyuafile(source_root_, build_root_, 661 parser(source_root_, abs_build_root, 662 fs::path(file.leaf_name()), user_config, 663 scheduler_handle).parse()); 664 } 665 666 667 /// Gets the root directory of the test suite. 668 /// 669 /// \return A path. 670 const fs::path& 671 engine::kyuafile::source_root(void) const 672 { 673 return _source_root; 674 } 675 676 677 /// Gets the root directory of the test programs. 678 /// 679 /// \return A path. 680 const fs::path& 681 engine::kyuafile::build_root(void) const 682 { 683 return _build_root; 684 } 685 686 687 /// Gets the collection of test programs that belong to this test suite. 688 /// 689 /// \return Collection of test program executable names. 690 const model::test_programs_vector& 691 engine::kyuafile::test_programs(void) const 692 { 693 return _test_programs; 694 } 695