forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_helpers.cc
362 lines (310 loc) · 12.3 KB
/
test_helpers.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sql/test/test_helpers.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <memory>
#include <string>
#include <tuple>
#include "base/big_endian.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql::test {
namespace {
size_t CountSQLItemsOfType(sql::Database* db, const char* type) {
static const char kTypeSQL[] =
"SELECT COUNT(*) FROM sqlite_schema WHERE type = ?";
sql::Statement s(db->GetUniqueStatement(kTypeSQL));
s.BindCString(0, type);
EXPECT_TRUE(s.Step());
return s.ColumnInt(0);
}
// Read the number of the root page of a B-tree (index/table).
//
// Returns a 0-indexed page number, not the raw SQLite page number.
absl::optional<int> GetRootPage(sql::Database& db,
base::StringPiece tree_name) {
sql::Statement select(
db.GetUniqueStatement("SELECT rootpage FROM sqlite_schema WHERE name=?"));
select.BindString(0, tree_name);
if (!select.Step())
return absl::nullopt;
int sqlite_page_number = select.ColumnInt(0);
if (!sqlite_page_number)
return absl::nullopt;
return sqlite_page_number - 1;
}
[[nodiscard]] bool IsWalDatabase(const base::FilePath& db_path) {
// See http://www.sqlite.org/fileformat2.html#database_header
constexpr size_t kHeaderSize = 100;
constexpr int64_t kHeaderOffset = 0;
uint8_t header[kHeaderSize];
base::File file(db_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid())
return false;
if (!file.ReadAndCheck(kHeaderOffset, header))
return false;
// See https://www.sqlite.org/fileformat2.html#file_format_version_numbers
constexpr int kWriteVersionHeaderOffset = 18;
constexpr int kReadVersionHeaderOffset = 19;
// If the read version is unsupported, we can't rely on our ability to
// interpret anything else in the header.
DCHECK_LE(int{header[kReadVersionHeaderOffset]}, 2)
<< "Unsupported SQLite file format";
return header[kWriteVersionHeaderOffset] == 2;
}
[[nodiscard]] bool CorruptSizeInHeaderMemory(uint8_t* header, int64_t db_size) {
// See https://www.sqlite.org/fileformat2.html#page_size
constexpr size_t kPageSizeOffset = 16;
constexpr uint16_t kMinPageSize = 512;
uint16_t raw_page_size;
base::ReadBigEndian(header + kPageSizeOffset, &raw_page_size);
const int page_size = (raw_page_size == 1) ? 65536 : raw_page_size;
// Sanity-check that the page size is valid.
if (page_size < kMinPageSize || (page_size & (page_size - 1)) != 0)
return false;
// Set the page count to exceed the file size.
// See https://www.sqlite.org/fileformat2.html#in_header_database_size
constexpr size_t kPageCountOffset = 28;
const int64_t page_count = (db_size + page_size * 2 - 1) / page_size;
if (page_count > std::numeric_limits<uint32_t>::max())
return false;
base::WriteBigEndian(reinterpret_cast<char*>(header + kPageCountOffset),
static_cast<uint32_t>(page_count));
// Update change count so outstanding readers know the info changed.
// See https://www.sqlite.org/fileformat2.html#file_change_counter
// and
// https://www.sqlite.org/fileformat2.html#write_library_version_number_and_version_valid_for_number
constexpr size_t kFileChangeCountOffset = 24;
constexpr size_t kVersionValidForOffset = 92;
uint32_t old_change_count;
base::ReadBigEndian(header + kFileChangeCountOffset, &old_change_count);
const uint32_t new_change_count = old_change_count + 1;
base::WriteBigEndian(reinterpret_cast<char*>(header + kFileChangeCountOffset),
new_change_count);
base::WriteBigEndian(reinterpret_cast<char*>(header + kVersionValidForOffset),
new_change_count);
return true;
}
} // namespace
absl::optional<int> ReadDatabasePageSize(const base::FilePath& db_path) {
// See https://www.sqlite.org/fileformat2.html#page_size
constexpr size_t kPageSizeOffset = 16;
uint8_t raw_page_size_bytes[2];
base::File file(db_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid())
return absl::nullopt;
if (!file.ReadAndCheck(kPageSizeOffset, raw_page_size_bytes))
return absl::nullopt;
uint16_t raw_page_size;
base::ReadBigEndian(raw_page_size_bytes, &raw_page_size);
// The SQLite database format initially allocated a 16 bits for storing the
// page size. This worked out until SQLite wanted to support 64kb pages,
// because 65536 (64kb) doesn't fit in a 16-bit unsigned integer.
//
// Currently, the page_size field value of 1 is a special case for 64kb pages.
// The documentation hints at the path for future expansion -- the page_size
// field may become a litte-endian number that indicates the database page
// size divided by 256. This happens to work out because the smallest
// supported page size is 512.
const int page_size = (raw_page_size == 1) ? 65536 : raw_page_size;
// Sanity-check that the page size is valid.
constexpr uint16_t kMinPageSize = 512;
if (page_size < kMinPageSize || (page_size & (page_size - 1)) != 0)
return absl::nullopt;
return page_size;
}
bool CorruptSizeInHeader(const base::FilePath& db_path) {
if (IsWalDatabase(db_path)) {
// Checkpoint the WAL file in Truncate mode before corrupting to ensure that
// any future transaction always touches the DB file and not just the WAL
// file.
base::ScopedAllowBlockingForTesting allow_blocking;
// TODO: This function doesn't reliably work if connections to the DB are
// still open. Change any uses to ensure that we close all database
// connections before calling this function.
sql::Database db({.exclusive_locking = false, .wal_mode = true});
if (!db.Open(db_path))
return false;
int wal_log_size = 0;
int checkpointed_frame_count = 0;
int truncate_result = sqlite3_wal_checkpoint_v2(
db.db(InternalApiToken()), /*zDb=*/nullptr, SQLITE_CHECKPOINT_TRUNCATE,
&wal_log_size, &checkpointed_frame_count);
// A successful checkpoint in truncate mode sets these to zero.
DCHECK(wal_log_size == 0);
DCHECK(checkpointed_frame_count == 0);
if (truncate_result != SQLITE_OK)
return false;
db.Close();
}
base::File file(db_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE);
if (!file.IsValid())
return false;
int64_t db_size = file.GetLength();
if (db_size < 0)
return false;
// Read the entire database header, corrupt it, and write it back.
// See http://www.sqlite.org/fileformat2.html#database_header
constexpr size_t kHeaderSize = 100;
constexpr int64_t kHeaderOffset = 0;
uint8_t header[kHeaderSize];
if (!file.ReadAndCheck(kHeaderOffset, header))
return false;
if (!CorruptSizeInHeaderMemory(header, db_size))
return false;
return file.WriteAndCheck(kHeaderOffset, header);
}
bool CorruptSizeInHeaderWithLock(const base::FilePath& db_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
sql::Database db;
if (!db.Open(db_path))
return false;
// Prevent anyone else from using the database. The transaction is
// rolled back when |db| is destroyed.
if (!db.Execute("BEGIN EXCLUSIVE"))
return false;
return CorruptSizeInHeader(db_path);
}
bool CorruptIndexRootPage(const base::FilePath& db_path,
base::StringPiece index_name) {
absl::optional<int> page_size = ReadDatabasePageSize(db_path);
if (!page_size.has_value())
return false;
sql::Database db;
if (!db.Open(db_path))
return false;
absl::optional<int> page_number = GetRootPage(db, index_name);
db.Close();
if (!page_number.has_value())
return false;
std::vector<uint8_t> page_buffer(page_size.value());
const int64_t page_offset = int64_t{page_number.value()} * page_size.value();
base::File file(db_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE);
if (!file.IsValid())
return false;
return file.WriteAndCheck(page_offset, page_buffer);
}
size_t CountSQLTables(sql::Database* db) {
return CountSQLItemsOfType(db, "table");
}
size_t CountSQLIndices(sql::Database* db) {
return CountSQLItemsOfType(db, "index");
}
size_t CountTableColumns(sql::Database* db, const char* table) {
// TODO(shess): sql::Database::QuoteForSQL() would make sense.
std::string quoted_table;
{
static const char kQuoteSQL[] = "SELECT quote(?)";
sql::Statement s(db->GetUniqueStatement(kQuoteSQL));
s.BindCString(0, table);
EXPECT_TRUE(s.Step());
quoted_table = s.ColumnString(0);
}
std::string sql = "PRAGMA table_info(" + quoted_table + ")";
sql::Statement s(db->GetUniqueStatement(sql.c_str()));
size_t rows = 0;
while (s.Step()) {
++rows;
}
EXPECT_TRUE(s.Succeeded());
return rows;
}
bool CountTableRows(sql::Database* db, const char* table, size_t* count) {
// TODO(shess): Table should probably be quoted with [] or "". See
// http://www.sqlite.org/lang_keywords.html . Meanwhile, odd names
// will throw an error.
std::string sql = "SELECT COUNT(*) FROM ";
sql += table;
sql::Statement s(db->GetUniqueStatement(sql.c_str()));
if (!s.Step())
return false;
*count = static_cast<size_t>(s.ColumnInt64(0));
return true;
}
bool CreateDatabaseFromSQL(const base::FilePath& db_path,
const base::FilePath& sql_path) {
if (base::PathExists(db_path) || !base::PathExists(sql_path))
return false;
std::string sql;
if (!base::ReadFileToString(sql_path, &sql))
return false;
sql::Database db;
if (!db.Open(db_path))
return false;
// TODO(shess): Android defaults to auto_vacuum mode.
// Unfortunately, this makes certain kinds of tests which manipulate
// the raw database hard/impossible to write.
// http://crbug.com/307303 is for exploring this test issue.
std::ignore = db.Execute("PRAGMA auto_vacuum = 0");
return db.Execute(sql.c_str());
}
std::string IntegrityCheck(sql::Database& db) {
std::vector<std::string> messages;
EXPECT_TRUE(db.FullIntegrityCheck(&messages));
return base::JoinString(messages, "\n");
}
std::string ExecuteWithResult(sql::Database* db, const char* sql) {
sql::Statement s(db->GetUniqueStatement(sql));
return s.Step() ? s.ColumnString(0) : std::string();
}
std::string ExecuteWithResults(sql::Database* db,
const char* sql,
const char* column_sep,
const char* row_sep) {
sql::Statement s(db->GetUniqueStatement(sql));
std::string ret;
while (s.Step()) {
if (!ret.empty())
ret += row_sep;
for (int i = 0; i < s.ColumnCount(); ++i) {
if (i > 0)
ret += column_sep;
ret += s.ColumnString(i);
}
}
return ret;
}
int GetPageCount(sql::Database* db) {
sql::Statement statement(db->GetUniqueStatement("PRAGMA page_count"));
CHECK(statement.Step());
return statement.ColumnInt(0);
}
// static
ColumnInfo ColumnInfo::Create(sql::Database* db,
const std::string& db_name,
const std::string& table_name,
const std::string& column_name) {
sqlite3* const sqlite3_db = db->db(InternalApiToken());
const char* data_type;
const char* collation_sequence;
int not_null;
int primary_key;
int auto_increment;
int status = sqlite3_table_column_metadata(
sqlite3_db, db_name.c_str(), table_name.c_str(), column_name.c_str(),
&data_type, &collation_sequence, ¬_null, &primary_key,
&auto_increment);
CHECK_EQ(status, SQLITE_OK) << "SQLite error: " << sqlite3_errmsg(sqlite3_db);
// This happens when virtual tables report no type information.
if (data_type == nullptr)
data_type = "(nullptr)";
return {std::string(data_type), std::string(collation_sequence),
not_null != 0, primary_key != 0, auto_increment != 0};
}
} // namespace sql::test