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 "model/test_program.hpp" 30 31 #include <map> 32 #include <sstream> 33 34 #include "model/exceptions.hpp" 35 #include "model/metadata.hpp" 36 #include "model/test_case.hpp" 37 #include "model/test_result.hpp" 38 #include "utils/format/containers.ipp" 39 #include "utils/format/macros.hpp" 40 #include "utils/fs/path.hpp" 41 #include "utils/noncopyable.hpp" 42 #include "utils/sanity.hpp" 43 #include "utils/text/operations.ipp" 44 45 namespace fs = utils::fs; 46 namespace text = utils::text; 47 48 using utils::none; 49 50 51 /// Internal implementation of a test_program. 52 struct model::test_program::impl : utils::noncopyable { 53 /// Name of the test program interface. 54 std::string interface_name; 55 56 /// Name of the test program binary relative to root. 57 fs::path binary; 58 59 /// Root of the test suite containing the test program. 60 fs::path root; 61 62 /// Name of the test suite this program belongs to. 63 std::string test_suite_name; 64 65 /// Metadata of the test program. 66 model::metadata md; 67 68 /// List of test cases in the test program. 69 /// 70 /// Must be queried via the test_program::test_cases() method. 71 model::test_cases_map test_cases; 72 73 /// Constructor. 74 /// 75 /// \param interface_name_ Name of the test program interface. 76 /// \param binary_ The name of the test program binary relative to root_. 77 /// \param root_ The root of the test suite containing the test program. 78 /// \param test_suite_name_ The name of the test suite this program 79 /// belongs to. 80 /// \param md_ Metadata of the test program. 81 /// \param test_cases_ The collection of test cases in the test program. 82 impl(const std::string& interface_name_, const fs::path& binary_, 83 const fs::path& root_, const std::string& test_suite_name_, 84 const model::metadata& md_, const model::test_cases_map& test_cases_) : 85 interface_name(interface_name_), 86 binary(binary_), 87 root(root_), 88 test_suite_name(test_suite_name_), 89 md(md_) 90 { 91 PRE_MSG(!binary.is_absolute(), 92 F("The program '%s' must be relative to the root of the test " 93 "suite '%s'") % binary % root); 94 95 set_test_cases(test_cases_); 96 } 97 98 /// Sets the list of test cases of the test program. 99 /// 100 /// \param test_cases_ The new list of test cases. 101 void 102 set_test_cases(const model::test_cases_map& test_cases_) 103 { 104 for (model::test_cases_map::const_iterator iter = test_cases_.begin(); 105 iter != test_cases_.end(); ++iter) { 106 const std::string& name = (*iter).first; 107 const model::test_case& test_case = (*iter).second; 108 109 PRE_MSG(name == test_case.name(), 110 F("The test case '%s' has been registered with the " 111 "non-matching name '%s'") % name % test_case.name()); 112 113 test_cases.insert(model::test_cases_map::value_type( 114 name, test_case.apply_metadata_defaults(&md))); 115 } 116 INV(test_cases.size() == test_cases_.size()); 117 } 118 }; 119 120 121 /// Constructs a new test program. 122 /// 123 /// \param interface_name_ Name of the test program interface. 124 /// \param binary_ The name of the test program binary relative to root_. 125 /// \param root_ The root of the test suite containing the test program. 126 /// \param test_suite_name_ The name of the test suite this program belongs to. 127 /// \param md_ Metadata of the test program. 128 /// \param test_cases_ The collection of test cases in the test program. 129 model::test_program::test_program(const std::string& interface_name_, 130 const fs::path& binary_, 131 const fs::path& root_, 132 const std::string& test_suite_name_, 133 const model::metadata& md_, 134 const model::test_cases_map& test_cases_) : 135 _pimpl(new impl(interface_name_, binary_, root_, test_suite_name_, md_, 136 test_cases_)) 137 { 138 } 139 140 141 /// Destroys a test program. 142 model::test_program::~test_program(void) 143 { 144 } 145 146 147 /// Gets the name of the test program interface. 148 /// 149 /// \return An interface name. 150 const std::string& 151 model::test_program::interface_name(void) const 152 { 153 return _pimpl->interface_name; 154 } 155 156 157 /// Gets the path to the test program relative to the root of the test suite. 158 /// 159 /// \return The relative path to the test program binary. 160 const fs::path& 161 model::test_program::relative_path(void) const 162 { 163 return _pimpl->binary; 164 } 165 166 167 /// Gets the absolute path to the test program. 168 /// 169 /// \return The absolute path to the test program binary. 170 const fs::path 171 model::test_program::absolute_path(void) const 172 { 173 const fs::path full_path = _pimpl->root / _pimpl->binary; 174 return full_path.is_absolute() ? full_path : full_path.to_absolute(); 175 } 176 177 178 /// Gets the root of the test suite containing this test program. 179 /// 180 /// \return The path to the root of the test suite. 181 const fs::path& 182 model::test_program::root(void) const 183 { 184 return _pimpl->root; 185 } 186 187 188 /// Gets the name of the test suite containing this test program. 189 /// 190 /// \return The name of the test suite. 191 const std::string& 192 model::test_program::test_suite_name(void) const 193 { 194 return _pimpl->test_suite_name; 195 } 196 197 198 /// Gets the metadata of the test program. 199 /// 200 /// \return The metadata. 201 const model::metadata& 202 model::test_program::get_metadata(void) const 203 { 204 return _pimpl->md; 205 } 206 207 208 /// Gets a test case by its name. 209 /// 210 /// \param name The name of the test case to locate. 211 /// 212 /// \return The requested test case. 213 /// 214 /// \throw not_found_error If the specified test case is not in the test 215 /// program. 216 const model::test_case& 217 model::test_program::find(const std::string& name) const 218 { 219 const test_cases_map& tcs = test_cases(); 220 221 const test_cases_map::const_iterator iter = tcs.find(name); 222 if (iter == tcs.end()) 223 throw not_found_error(F("Unknown test case %s in test program %s") % 224 name % relative_path()); 225 return (*iter).second; 226 } 227 228 229 /// Gets the list of test cases from the test program. 230 /// 231 /// \return The list of test cases provided by the test program. 232 const model::test_cases_map& 233 model::test_program::test_cases(void) const 234 { 235 return _pimpl->test_cases; 236 } 237 238 239 /// Sets the list of test cases of the test program. 240 /// 241 /// This can only be called once and it may only be called from within 242 /// overridden test_cases() before that method ever returns a value for the 243 /// first time. Any other invocations will result in inconsistent program 244 /// state. 245 /// 246 /// \param test_cases_ The new list of test cases. 247 void 248 model::test_program::set_test_cases(const model::test_cases_map& test_cases_) 249 { 250 PRE(_pimpl->test_cases.empty()); 251 _pimpl->set_test_cases(test_cases_); 252 } 253 254 255 /// Equality comparator. 256 /// 257 /// \param other The other object to compare this one to. 258 /// 259 /// \return True if this object and other are equal; false otherwise. 260 bool 261 model::test_program::operator==(const test_program& other) const 262 { 263 return _pimpl == other._pimpl || ( 264 _pimpl->interface_name == other._pimpl->interface_name && 265 _pimpl->binary == other._pimpl->binary && 266 _pimpl->root == other._pimpl->root && 267 _pimpl->test_suite_name == other._pimpl->test_suite_name && 268 _pimpl->md == other._pimpl->md && 269 test_cases() == other.test_cases()); 270 } 271 272 273 /// Inequality comparator. 274 /// 275 /// \param other The other object to compare this one to. 276 /// 277 /// \return True if this object and other are different; false otherwise. 278 bool 279 model::test_program::operator!=(const test_program& other) const 280 { 281 return !(*this == other); 282 } 283 284 285 /// Less-than comparator. 286 /// 287 /// A test program is considered to be less than another if and only if the 288 /// former's absolute path is less than the absolute path of the latter. In 289 /// other words, the absolute path is used here as the test program's 290 /// identifier. 291 /// 292 /// This simplistic less-than operator overload is provided so that test 293 /// programs can be held in sets and other containers. 294 /// 295 /// \param other The other object to compare this one to. 296 /// 297 /// \return True if this object sorts before the other object; false otherwise. 298 bool 299 model::test_program::operator<(const test_program& other) const 300 { 301 return absolute_path() < other.absolute_path(); 302 } 303 304 305 /// Injects the object into a stream. 306 /// 307 /// \param output The stream into which to inject the object. 308 /// \param object The object to format. 309 /// 310 /// \return The output stream. 311 std::ostream& 312 model::operator<<(std::ostream& output, const test_program& object) 313 { 314 output << F("test_program{interface=%s, binary=%s, root=%s, test_suite=%s, " 315 "metadata=%s, test_cases=%s}") 316 % text::quote(object.interface_name(), '\'') 317 % text::quote(object.relative_path().str(), '\'') 318 % text::quote(object.root().str(), '\'') 319 % text::quote(object.test_suite_name(), '\'') 320 % object.get_metadata() 321 % object.test_cases(); 322 return output; 323 } 324 325 326 /// Internal implementation of the test_program_builder class. 327 struct model::test_program_builder::impl : utils::noncopyable { 328 /// Partially-constructed program with only the required properties. 329 model::test_program prototype; 330 331 /// Optional metadata for the test program. 332 model::metadata metadata; 333 334 /// Collection of test cases. 335 model::test_cases_map test_cases; 336 337 /// Whether we have created a test_program object or not. 338 bool built; 339 340 /// Constructor. 341 /// 342 /// \param prototype_ The partially constructed program with only the 343 /// required properties. 344 impl(const model::test_program& prototype_) : 345 prototype(prototype_), 346 metadata(model::metadata_builder().build()), 347 built(false) 348 { 349 } 350 }; 351 352 353 /// Constructs a new builder with non-optional values. 354 /// 355 /// \param interface_name_ Name of the test program interface. 356 /// \param binary_ The name of the test program binary relative to root_. 357 /// \param root_ The root of the test suite containing the test program. 358 /// \param test_suite_name_ The name of the test suite this program belongs to. 359 model::test_program_builder::test_program_builder( 360 const std::string& interface_name_, const fs::path& binary_, 361 const fs::path& root_, const std::string& test_suite_name_) : 362 _pimpl(new impl(model::test_program(interface_name_, binary_, root_, 363 test_suite_name_, 364 model::metadata_builder().build(), 365 model::test_cases_map()))) 366 { 367 } 368 369 370 /// Destructor. 371 model::test_program_builder::~test_program_builder(void) 372 { 373 } 374 375 376 /// Accumulates an additional test case with default metadata. 377 /// 378 /// \param test_case_name The name of the test case. 379 /// 380 /// \return A reference to this builder. 381 model::test_program_builder& 382 model::test_program_builder::add_test_case(const std::string& test_case_name) 383 { 384 return add_test_case(test_case_name, model::metadata_builder().build()); 385 } 386 387 388 /// Accumulates an additional test case. 389 /// 390 /// \param test_case_name The name of the test case. 391 /// \param metadata The test case metadata. 392 /// 393 /// \return A reference to this builder. 394 model::test_program_builder& 395 model::test_program_builder::add_test_case(const std::string& test_case_name, 396 const model::metadata& metadata) 397 { 398 const model::test_case test_case(test_case_name, metadata); 399 PRE_MSG(_pimpl->test_cases.find(test_case_name) == _pimpl->test_cases.end(), 400 F("Attempted to re-register test case '%s'") % test_case_name); 401 _pimpl->test_cases.insert(model::test_cases_map::value_type( 402 test_case_name, test_case)); 403 return *this; 404 } 405 406 407 /// Sets the test program metadata. 408 /// 409 /// \return metadata The metadata for the test program. 410 /// 411 /// \return A reference to this builder. 412 model::test_program_builder& 413 model::test_program_builder::set_metadata(const model::metadata& metadata) 414 { 415 _pimpl->metadata = metadata; 416 return *this; 417 } 418 419 420 /// Creates a new test_program object. 421 /// 422 /// \pre This has not yet been called. We only support calling this function 423 /// once. 424 /// 425 /// \return The constructed test_program object. 426 model::test_program 427 model::test_program_builder::build(void) const 428 { 429 PRE(!_pimpl->built); 430 _pimpl->built = true; 431 432 return test_program(_pimpl->prototype.interface_name(), 433 _pimpl->prototype.relative_path(), 434 _pimpl->prototype.root(), 435 _pimpl->prototype.test_suite_name(), 436 _pimpl->metadata, 437 _pimpl->test_cases); 438 } 439 440 441 /// Creates a new dynamically-allocated test_program object. 442 /// 443 /// \pre This has not yet been called. We only support calling this function 444 /// once. 445 /// 446 /// \return The constructed test_program object. 447 model::test_program_ptr 448 model::test_program_builder::build_ptr(void) const 449 { 450 const test_program result = build(); 451 return test_program_ptr(new test_program(result)); 452 } 453