Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/kyua/store/read_transaction.cpp
39478 views
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 >
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
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
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
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
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
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.
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.
294
store::results_iterator::results_iterator(
295
std::shared_ptr< impl > pimpl_) :
296
_pimpl(pimpl_)
297
{
298
}
299
300
301
/// Destructor.
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&
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.
321
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
331
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
352
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
362
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
372
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
382
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
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
419
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
432
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.
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.
466
store::read_transaction::read_transaction(read_backend& backend_) :
467
_pimpl(new impl(backend_))
468
{
469
}
470
471
472
/// Destructor.
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
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
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
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
534