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