| Index: sql/recover_unittest.cc
|
| diff --git a/sql/recover_unittest.cc b/sql/recover_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..918031dc578138267d296692ceba0a7a4eaf08b0
|
| --- /dev/null
|
| +++ b/sql/recover_unittest.cc
|
| @@ -0,0 +1,159 @@
|
| +// Copyright (c) 2012 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 "base/file_util.h"
|
| +#include "base/files/scoped_temp_dir.h"
|
| +#include "base/logging.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "sql/connection.h"
|
| +#include "sql/meta_table.h"
|
| +#include "sql/recovery.h"
|
| +#include "sql/statement.h"
|
| +#include "sql/test/scoped_error_ignorer.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "third_party/sqlite/sqlite3.h"
|
| +
|
| +namespace {
|
| +
|
| +// Helper to return the count of items in sqlite_master. Return -1 in
|
| +// case of error.
|
| +int SqliteMasterCount(sql::Connection* db) {
|
| + const char* kMasterCount = "SELECT COUNT(*) FROM sqlite_master";
|
| + sql::Statement s(db->GetUniqueStatement(kMasterCount));
|
| + return s.Step() ? s.ColumnInt(0) : -1;
|
| +}
|
| +
|
| +// Helper to execute a statement and return stringified results.
|
| +std::string ExecuteWithResults(sql::Connection* db,
|
| + const char* sql,
|
| + const char* column_sep,
|
| + const char* row_sep) {
|
| + sql::Statement s(db->GetUniqueStatement(sql));
|
| + std::string ret;
|
| + while (s.Step()) {
|
| + for (int i = 0; i < s.ColumnCount(); ++i) {
|
| + if (i > 0)
|
| + ret += column_sep;
|
| + ret += s.ColumnString(i);
|
| + }
|
| + ret += row_sep;
|
| + }
|
| + return ret;
|
| +}
|
| +
|
| +void DumpMaster(sql::Connection* db, const char* name) {
|
| + std::string kSql = base::StringPrintf(
|
| + "SELECT type, name, tbl_name, sql FROM %s ORDER BY 1,2,3,4",
|
| + name);
|
| + std::string ret = ExecuteWithResults(db, kSql.c_str(), "|", "\n");
|
| + if (ret.empty()) {
|
| + LOG(ERROR) << name << ": <empty>";
|
| + } else {
|
| + LOG(ERROR) << name << ":\n" << ret;
|
| + }
|
| +}
|
| +
|
| +std::string GetSchema(sql::Connection* db) {
|
| + const char kSql[] =
|
| + "SELECT COALESCE(sql, name) FROM sqlite_master ORDER BY 1";
|
| + return ExecuteWithResults(db, kSql, "|", "\n");
|
| +}
|
| +
|
| +class SQLRecoverTest : public testing::Test {
|
| + public:
|
| + SQLRecoverTest() {}
|
| +
|
| + virtual void SetUp() {
|
| + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
|
| + ASSERT_TRUE(db_.Open(db_path()));
|
| + }
|
| +
|
| + virtual void TearDown() {
|
| + db_.Close();
|
| + }
|
| +
|
| + sql::Connection& db() { return db_; }
|
| +
|
| + base::FilePath db_path() {
|
| + return temp_dir_.path().AppendASCII("SQLRecoverTest.db");
|
| + }
|
| +
|
| + bool Reopen() {
|
| + db_.Close();
|
| + return db_.Open(db_path());
|
| + }
|
| +
|
| + private:
|
| + base::ScopedTempDir temp_dir_;
|
| + sql::Connection db_;
|
| +};
|
| +
|
| +TEST_F(SQLRecoverTest, RecoverBasic) {
|
| + const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
|
| + const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
|
| + ASSERT_TRUE(db().Execute(kCreateSql));
|
| + ASSERT_TRUE(db().Execute(kInsertSql));
|
| + ASSERT_EQ(GetSchema(&db()), "CREATE TABLE x (t TEXT)\n");
|
| +
|
| + // If the Recovery object goes out of scope without being marked
|
| + // Recovered(), it clears the original database entirely.
|
| + {
|
| + sql::Recovery recovery(&db());
|
| + ASSERT_TRUE(recovery.Open(db_path()));
|
| + }
|
| +
|
| + // If the Recovery object goes out of scope without being marked
|
| + // Recovered(), it clears the original database entirely.
|
| + ASSERT_TRUE(Reopen());
|
| + ASSERT_EQ(0, SqliteMasterCount(&db()));
|
| +
|
| + ASSERT_TRUE(db().Execute(kCreateSql));
|
| + ASSERT_TRUE(db().Execute(kInsertSql));
|
| + ASSERT_EQ(GetSchema(&db()), "CREATE TABLE x (t TEXT)\n");
|
| +
|
| + // Unrecoverable() clears the original database entirely and poisons
|
| + // the connection.
|
| + {
|
| + sql::Recovery recovery(&db());
|
| + ASSERT_TRUE(recovery.Open(db_path()));
|
| + recovery.Unrecoverable();
|
| +
|
| + // TODO(shess): Test that calls to recover.db() start failing.
|
| + }
|
| +
|
| + // Unrecoverable() clears the original database entirely.
|
| + ASSERT_TRUE(Reopen());
|
| + ASSERT_EQ(0, SqliteMasterCount(&db()));
|
| +
|
| + ASSERT_TRUE(db().Execute(kCreateSql));
|
| + ASSERT_TRUE(db().Execute(kInsertSql));
|
| + ASSERT_EQ(GetSchema(&db()), "CREATE TABLE x (t TEXT)\n");
|
| +
|
| + // Successfully recover the database by copying from the existing
|
| + // database.
|
| + {
|
| + sql::Recovery recovery(&db());
|
| + ASSERT_TRUE(recovery.Open(db_path()));
|
| +
|
| + // Create the new version of the table.
|
| + ASSERT_TRUE(recovery.db()->Execute(kCreateSql));
|
| +
|
| + const char kRecoverSql[] = "INSERT INTO x SELECT t FROM corrupt.x";
|
| + ASSERT_TRUE(recovery.db()->Execute(kRecoverSql));
|
| +
|
| + // Successfully recovered.
|
| + ASSERT_TRUE(recovery.Recovered());
|
| + }
|
| +
|
| + // Since the database was not corrupt, the entire schema and all
|
| + // data should be recovered.
|
| + ASSERT_TRUE(Reopen());
|
| + ASSERT_EQ(GetSchema(&db()), "CREATE TABLE x (t TEXT)\n");
|
| +
|
| + const char* kXSql = "SELECT * FROM x ORDER BY 1";
|
| + ASSERT_EQ(ExecuteWithResults(&db(), kXSql, "|", "\n"),
|
| + "This is a test\n");
|
| +}
|
| +
|
| +} // namespace
|
|
|