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
31 extern "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
58 namespace datetime = utils::datetime;
59 namespace fs = utils::fs;
60 namespace sqlite = utils::sqlite;
61
62 using utils::optional;
63
64
65 namespace {
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.
75 static std::map< std::string, std::string >
get_env_vars(sqlite::database & db)76 get_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.
99 static model::metadata
get_metadata(sqlite::database & db,const int64_t metadata_id)100 get_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.
126 static std::string
get_file(sqlite::database & db,const int64_t file_id)127 get_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.
160 static model::test_cases_map
get_test_cases(sqlite::database & db,const int64_t test_program_id)161 get_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.
192 static model::test_result
parse_result(sqlite::statement & stmt,const char * type_column,const char * reason_column)193 parse_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.
227 model::test_program_ptr
get_test_program(read_backend & backend_,const int64_t id)228 store::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.
254 struct 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.
implstore::results_iterator::impl271 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.
results_iterator(std::shared_ptr<impl> pimpl_)294 store::results_iterator::results_iterator(
295 std::shared_ptr< impl > pimpl_) :
296 _pimpl(pimpl_)
297 {
298 }
299
300
301 /// Destructor.
~results_iterator(void)302 store::results_iterator::~results_iterator(void)
303 {
304 }
305
306
307 /// Moves the iterator forward by one result.
308 ///
309 /// \return The iterator itself.
310 store::results_iterator&
operator ++(void)311 store::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.
operator bool(void) const321 store::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.
330 const model::test_program_ptr
test_program(void) const331 store::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.
351 std::string
test_case_name(void) const352 store::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.
361 model::test_result
result(void) const362 store::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.
371 datetime::timestamp
start_time(void) const372 store::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.
381 datetime::timestamp
end_time(void) const382 store::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.
398 static std::string
get_test_case_file(sqlite::database & db,const int64_t test_case_id,const char * filename)399 get_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.
418 std::string
stdout_contents(void) const419 store::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.
431 std::string
stderr_contents(void) const432 store::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.
441 struct 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.
implstore::read_transaction::impl454 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.
read_transaction(read_backend & backend_)466 store::read_transaction::read_transaction(read_backend& backend_) :
467 _pimpl(new impl(backend_))
468 {
469 }
470
471
472 /// Destructor.
~read_transaction(void)473 store::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.
485 void
finish(void)486 store::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.
501 model::context
get_context(void)502 store::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.
523 store::results_iterator
get_results(void)524 store::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