1// Copyright 2011 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 "store/read_transaction.hpp" 30 31extern "C" { 32#include <stdint.h> 33} 34 35#include <map> 36#include <utility> 37 38#include "model/context.hpp" 39#include "model/metadata.hpp" 40#include "model/test_case.hpp" 41#include "model/test_program.hpp" 42#include "model/test_result.hpp" 43#include "store/dbtypes.hpp" 44#include "store/exceptions.hpp" 45#include "store/read_backend.hpp" 46#include "utils/datetime.hpp" 47#include "utils/format/macros.hpp" 48#include "utils/fs/path.hpp" 49#include "utils/logging/macros.hpp" 50#include "utils/noncopyable.hpp" 51#include "utils/optional.ipp" 52#include "utils/sanity.hpp" 53#include "utils/sqlite/database.hpp" 54#include "utils/sqlite/exceptions.hpp" 55#include "utils/sqlite/statement.ipp" 56#include "utils/sqlite/transaction.hpp" 57 58namespace datetime = utils::datetime; 59namespace fs = utils::fs; 60namespace sqlite = utils::sqlite; 61 62using utils::optional; 63 64 65namespace { 66 67 68/// Retrieves the environment variables of the context. 69/// 70/// \param db The SQLite database. 71/// 72/// \return The environment variables of the specified context. 73/// 74/// \throw sqlite::error If there is a problem loading the variables. 75static std::map< std::string, std::string > 76get_env_vars(sqlite::database& db) 77{ 78 std::map< std::string, std::string > env; 79 80 sqlite::statement stmt = db.create_statement( 81 "SELECT var_name, var_value FROM env_vars"); 82 83 while (stmt.step()) { 84 const std::string name = stmt.safe_column_text("var_name"); 85 const std::string value = stmt.safe_column_text("var_value"); 86 env[name] = value; 87 } 88 89 return env; 90} 91 92 93/// Retrieves a metadata object. 94/// 95/// \param db The SQLite database. 96/// \param metadata_id The identifier of the metadata. 97/// 98/// \return A new metadata object. 99static model::metadata 100get_metadata(sqlite::database& db, const int64_t metadata_id) 101{ 102 model::metadata_builder builder; 103 104 sqlite::statement stmt = db.create_statement( 105 "SELECT * FROM metadatas WHERE metadata_id == :metadata_id"); 106 stmt.bind(":metadata_id", metadata_id); 107 while (stmt.step()) { 108 const std::string name = stmt.safe_column_text("property_name"); 109 const std::string value = stmt.safe_column_text("property_value"); 110 builder.set_string(name, value); 111 } 112 113 return builder.build(); 114} 115 116 117/// Gets a file from the database. 118/// 119/// \param db The database to query the file from. 120/// \param file_id The identifier of the file to be queried. 121/// 122/// \return A textual representation of the file contents. 123/// 124/// \throw integrity_error If there is any problem in the loaded data or if the 125/// file cannot be found. 126static std::string 127get_file(sqlite::database& db, const int64_t file_id) 128{ 129 sqlite::statement stmt = db.create_statement( 130 "SELECT contents FROM files WHERE file_id == :file_id"); 131 stmt.bind(":file_id", file_id); 132 if (!stmt.step()) 133 throw store::integrity_error(F("Cannot find referenced file %s") % 134 file_id); 135 136 try { 137 const sqlite::blob raw_contents = stmt.safe_column_blob("contents"); 138 const std::string contents( 139 static_cast< const char *>(raw_contents.memory), raw_contents.size); 140 141 const bool more = stmt.step(); 142 INV(!more); 143 144 return contents; 145 } catch (const sqlite::error& e) { 146 throw store::integrity_error(e.what()); 147 } 148} 149 150 151/// Gets all the test cases within a particular test program. 152/// 153/// \param db The database to query the information from. 154/// \param test_program_id The identifier of the test program whose test cases 155/// to query. 156/// 157/// \return The collection of loaded test cases. 158/// 159/// \throw integrity_error If there is any problem in the loaded data. 160static model::test_cases_map 161get_test_cases(sqlite::database& db, const int64_t test_program_id) 162{ 163 model::test_cases_map_builder test_cases; 164 165 sqlite::statement stmt = db.create_statement( 166 "SELECT name, metadata_id " 167 "FROM test_cases WHERE test_program_id == :test_program_id"); 168 stmt.bind(":test_program_id", test_program_id); 169 while (stmt.step()) { 170 const std::string name = stmt.safe_column_text("name"); 171 const int64_t metadata_id = stmt.safe_column_int64("metadata_id"); 172 173 const model::metadata metadata = get_metadata(db, metadata_id); 174 LD(F("Loaded test case '%s'") % name); 175 test_cases.add(name, metadata); 176 } 177 178 return test_cases.build(); 179} 180 181 182/// Retrieves a result from the database. 183/// 184/// \param stmt The statement with the data for the result to load. 185/// \param type_column The name of the column containing the type of the result. 186/// \param reason_column The name of the column containing the reason for the 187/// result, if any. 188/// 189/// \return The loaded result. 190/// 191/// \throw integrity_error If the data in the database is invalid. 192static model::test_result 193parse_result(sqlite::statement& stmt, const char* type_column, 194 const char* reason_column) 195{ 196 try { 197 const model::test_result_type type = 198 store::column_test_result_type(stmt, type_column); 199 if (type == model::test_result_passed) { 200 if (stmt.column_type(stmt.column_id(reason_column)) != 201 sqlite::type_null) 202 throw store::integrity_error("Result of type 'passed' has a " 203 "non-NULL reason"); 204 return model::test_result(type); 205 } else { 206 return model::test_result(type, 207 stmt.safe_column_text(reason_column)); 208 } 209 } catch (const sqlite::error& e) { 210 throw store::integrity_error(e.what()); 211 } 212} 213 214 215} // anonymous namespace 216 217 218/// Loads a specific test program from the database. 219/// 220/// \param backend_ The store backend we are dealing with. 221/// \param id The identifier of the test program to load. 222/// 223/// \return The instantiated test program. 224/// 225/// \throw integrity_error If the data read from the database cannot be properly 226/// interpreted. 227model::test_program_ptr 228store::detail::get_test_program(read_backend& backend_, const int64_t id) 229{ 230 sqlite::database& db = backend_.database(); 231 232 model::test_program_ptr test_program; 233 sqlite::statement stmt = db.create_statement( 234 "SELECT * FROM test_programs WHERE test_program_id == :id"); 235 stmt.bind(":id", id); 236 stmt.step(); 237 const std::string interface = stmt.safe_column_text("interface"); 238 test_program.reset(new model::test_program( 239 interface, 240 fs::path(stmt.safe_column_text("relative_path")), 241 fs::path(stmt.safe_column_text("root")), 242 stmt.safe_column_text("test_suite_name"), 243 get_metadata(db, stmt.safe_column_int64("metadata_id")), 244 get_test_cases(db, id))); 245 const bool more = stmt.step(); 246 INV(!more); 247 248 LD(F("Loaded test program '%s'") % test_program->relative_path()); 249 return test_program; 250} 251 252 253/// Internal implementation for a results iterator. 254struct store::results_iterator::impl : utils::noncopyable { 255 /// The store backend we are dealing with. 256 store::read_backend _backend; 257 258 /// The statement to iterate on. 259 sqlite::statement _stmt; 260 261 /// A cache for the last loaded test program. 262 optional< std::pair< int64_t, model::test_program_ptr > > 263 _last_test_program; 264 265 /// Whether the iterator is still valid or not. 266 bool _valid; 267 268 /// Constructor. 269 /// 270 /// \param backend_ The store backend implementation. 271 impl(store::read_backend& backend_) : 272 _backend(backend_), 273 _stmt(backend_.database().create_statement( 274 "SELECT test_programs.test_program_id, " 275 " test_programs.interface, " 276 " test_cases.test_case_id, test_cases.name, " 277 " test_results.result_type, test_results.result_reason, " 278 " test_results.start_time, test_results.end_time " 279 "FROM test_programs " 280 " JOIN test_cases " 281 " ON test_programs.test_program_id = test_cases.test_program_id " 282 " JOIN test_results " 283 " ON test_cases.test_case_id = test_results.test_case_id " 284 "ORDER BY test_programs.absolute_path, test_cases.name")) 285 { 286 _valid = _stmt.step(); 287 } 288}; 289 290 291/// Constructor. 292/// 293/// \param pimpl_ The internal implementation details of the iterator. 294store::results_iterator::results_iterator( 295 std::shared_ptr< impl > pimpl_) : 296 _pimpl(pimpl_) 297{ 298} 299 300 301/// Destructor. 302store::results_iterator::~results_iterator(void) 303{ 304} 305 306 307/// Moves the iterator forward by one result. 308/// 309/// \return The iterator itself. 310store::results_iterator& 311store::results_iterator::operator++(void) 312{ 313 _pimpl->_valid = _pimpl->_stmt.step(); 314 return *this; 315} 316 317 318/// Checks whether the iterator is still valid. 319/// 320/// \return True if there is more elements to iterate on, false otherwise. 321store::results_iterator::operator bool(void) const 322{ 323 return _pimpl->_valid; 324} 325 326 327/// Gets the test program this result belongs to. 328/// 329/// \return The representation of a test program. 330const model::test_program_ptr 331store::results_iterator::test_program(void) const 332{ 333 const int64_t id = _pimpl->_stmt.safe_column_int64("test_program_id"); 334 if (!_pimpl->_last_test_program || 335 _pimpl->_last_test_program.get().first != id) 336 { 337 const model::test_program_ptr tp = detail::get_test_program( 338 _pimpl->_backend, id); 339 _pimpl->_last_test_program = std::make_pair(id, tp); 340 } 341 return _pimpl->_last_test_program.get().second; 342} 343 344 345/// Gets the name of the test case pointed by the iterator. 346/// 347/// The caller can look up the test case data by using the find() method on the 348/// test program returned by test_program(). 349/// 350/// \return A test case name, unique within the test program. 351std::string 352store::results_iterator::test_case_name(void) const 353{ 354 return _pimpl->_stmt.safe_column_text("name"); 355} 356 357 358/// Gets the result of the test case pointed by the iterator. 359/// 360/// \return A test case result. 361model::test_result 362store::results_iterator::result(void) const 363{ 364 return parse_result(_pimpl->_stmt, "result_type", "result_reason"); 365} 366 367 368/// Gets the start time of the test case execution. 369/// 370/// \return The time when the test started execution. 371datetime::timestamp 372store::results_iterator::start_time(void) const 373{ 374 return column_timestamp(_pimpl->_stmt, "start_time"); 375} 376 377 378/// Gets the end time of the test case execution. 379/// 380/// \return The time when the test finished execution. 381datetime::timestamp 382store::results_iterator::end_time(void) const 383{ 384 return column_timestamp(_pimpl->_stmt, "end_time"); 385} 386 387 388/// Gets a file from a test case. 389/// 390/// \param db The database to query the file from. 391/// \param test_case_id The identifier of the test case. 392/// \param filename The name of the file to be retrieved. 393/// 394/// \return A textual representation of the file contents. 395/// 396/// \throw integrity_error If there is any problem in the loaded data or if the 397/// file cannot be found. 398static std::string 399get_test_case_file(sqlite::database& db, const int64_t test_case_id, 400 const char* filename) 401{ 402 sqlite::statement stmt = db.create_statement( 403 "SELECT file_id FROM test_case_files " 404 "WHERE test_case_id == :test_case_id AND file_name == :file_name"); 405 stmt.bind(":test_case_id", test_case_id); 406 stmt.bind(":file_name", filename); 407 if (stmt.step()) 408 return get_file(db, stmt.safe_column_int64("file_id")); 409 else 410 return ""; 411} 412 413 414/// Gets the contents of stdout of a test case. 415/// 416/// \return A textual representation of the stdout contents of the test case. 417/// This may of course be empty if the test case didn't print anything. 418std::string 419store::results_iterator::stdout_contents(void) const 420{ 421 return get_test_case_file(_pimpl->_backend.database(), 422 _pimpl->_stmt.safe_column_int64("test_case_id"), 423 "__STDOUT__"); 424} 425 426 427/// Gets the contents of stderr of a test case. 428/// 429/// \return A textual representation of the stderr contents of the test case. 430/// This may of course be empty if the test case didn't print anything. 431std::string 432store::results_iterator::stderr_contents(void) const 433{ 434 return get_test_case_file(_pimpl->_backend.database(), 435 _pimpl->_stmt.safe_column_int64("test_case_id"), 436 "__STDERR__"); 437} 438 439 440/// Internal implementation for a store read-only transaction. 441struct store::read_transaction::impl : utils::noncopyable { 442 /// The backend instance. 443 store::read_backend& _backend; 444 445 /// The SQLite database this transaction deals with. 446 sqlite::database _db; 447 448 /// The backing SQLite transaction. 449 sqlite::transaction _tx; 450 451 /// Opens a transaction. 452 /// 453 /// \param backend_ The backend this transaction is connected to. 454 impl(read_backend& backend_) : 455 _backend(backend_), 456 _db(backend_.database()), 457 _tx(backend_.database().begin_transaction()) 458 { 459 } 460}; 461 462 463/// Creates a new read-only transaction. 464/// 465/// \param backend_ The backend this transaction belongs to. 466store::read_transaction::read_transaction(read_backend& backend_) : 467 _pimpl(new impl(backend_)) 468{ 469} 470 471 472/// Destructor. 473store::read_transaction::~read_transaction(void) 474{ 475} 476 477 478/// Finishes the transaction. 479/// 480/// This actually commits the result of the transaction, but because the 481/// transaction is read-only, we use a different term to denote that there is no 482/// distinction between commit and rollback. 483/// 484/// \throw error If there is any problem when talking to the database. 485void 486store::read_transaction::finish(void) 487{ 488 try { 489 _pimpl->_tx.commit(); 490 } catch (const sqlite::error& e) { 491 throw error(e.what()); 492 } 493} 494 495 496/// Retrieves an context from the database. 497/// 498/// \return The retrieved context. 499/// 500/// \throw error If there is a problem loading the context. 501model::context 502store::read_transaction::get_context(void) 503{ 504 try { 505 sqlite::statement stmt = _pimpl->_db.create_statement( 506 "SELECT cwd FROM contexts"); 507 if (!stmt.step()) 508 throw error("Error loading context: no data"); 509 510 return model::context(fs::path(stmt.safe_column_text("cwd")), 511 get_env_vars(_pimpl->_db)); 512 } catch (const sqlite::error& e) { 513 throw error(F("Error loading context: %s") % e.what()); 514 } 515} 516 517 518/// Creates a new iterator to scan tests results. 519/// 520/// \return The constructed iterator. 521/// 522/// \throw error If there is any problem constructing the iterator. 523store::results_iterator 524store::read_transaction::get_results(void) 525{ 526 try { 527 return results_iterator(std::shared_ptr< results_iterator::impl >( 528 new results_iterator::impl(_pimpl->_backend))); 529 } catch (const sqlite::error& e) { 530 throw error(e.what()); 531 } 532} 533