diff --git a/unittest/BUILD.gn b/unittest/BUILD.gn index d32df9e0f2357418455fbee7f9c4960736504e05..8bd8b89481674bb57af274677f050e21c09dbfd8 100644 --- a/unittest/BUILD.gn +++ b/unittest/BUILD.gn @@ -34,7 +34,9 @@ ohos_unittest("libsqlittest") { module_out_path = module_output_path sources = [ + "./common.cpp", "./sqlite_test.cpp", + "./sqlite_multi_thread_test.cpp", ] configs = [ ":module_private_config" ] diff --git a/unittest/common.cpp b/unittest/common.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6bdb82e8bdbee09c19ed280de40fb74f4cf67645 --- /dev/null +++ b/unittest/common.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +int Common::RemoveDir(const char *dir) +{ + if (dir == nullptr) { + return TEST_STATUS_ERR; + } + if (access(dir, F_OK) != 0) { + return TEST_STATUS_OK; + } + struct stat dirStat; + if (stat(dir, &dirStat) < 0) { + return TEST_STATUS_ERR; + } + char dirName[PATH_MAX] = {0}; + DIR *dirPtr = nullptr; + struct dirent *dr = nullptr; + if (S_ISREG(dirStat.st_mode)) { + remove(dir); + } else if (S_ISDIR(dirStat.st_mode)) { + dirPtr = opendir(dir); + while ((dr = readdir(dirPtr)) != nullptr) { + if ((strcmp(".", dr->d_name) == 0) || (strcmp("..", dr->d_name) == 0)) { + continue; + } + if (sprintf(dirName, "%s/%s", dir, dr->d_name) <= 0) { + closedir(dirPtr); + return TEST_STATUS_ERR; + } + RemoveDir(dirName); + } + closedir(dirPtr); + rmdir(dir); + } else { + return TEST_STATUS_ERR; + } + return TEST_STATUS_OK; +} + +int Common::MakeDir(const char *dir) +{ + if (dir == nullptr) { + return TEST_STATUS_ERR; + } + if (strlen(dir) > PATH_MAX) { + return TEST_STATUS_ERR; + } + if (access(dir, 0) != -1) { + return TEST_STATUS_OK; + } + char tmpPath[PATH_MAX + 1] = {0}; + const char *pcur = dir; + int pos = 0; + while (*pcur++ != '\0') { + tmpPath[pos++] = *(char *)((uintptr_t)pcur - 1); + if ((*pcur == '/' || *pcur == '\0') && access(tmpPath, 0) != 0 && strlen(tmpPath) > 0) { + if (mkdir(tmpPath, (S_IRUSR | S_IWUSR | S_IXUSR)) != 0) { + return TEST_STATUS_ERR; + } + } + } + return TEST_STATUS_OK; +} \ No newline at end of file diff --git a/unittest/common.h b/unittest/common.h new file mode 100644 index 0000000000000000000000000000000000000000..61263602f01b74e2a84c1eb92ee188e65785bdaf --- /dev/null +++ b/unittest/common.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef COMMON_H +#define COMMON_H + +#define TEST_STATUS_OK 0 +#define TEST_STATUS_ERR (-1) + +class Common { +public: + static int RemoveDir(const char *dir); + static int MakeDir(const char *dir); +}; + +#endif /* COMMON_H */ diff --git a/unittest/sqlite_multi_thread_test.cpp b/unittest/sqlite_multi_thread_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b5334cc0366aa5305b186e6654bcb3d3f9bd846e --- /dev/null +++ b/unittest/sqlite_multi_thread_test.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include "common.h" +#include "sqlite3sym.h" + +#define UT_DIR "./sqlitemultithreadtest" +#define UT_DB (UT_DIR "/test.db") +#define UT_PRESET_DATA_COUNT 1000 + +using namespace testing::ext; + +static void UtSqliteLogPrint(const void *data, int err, const char *msg) +{ + std::cout << "LibSQLiteTest SQLite xLog err:" << err << ", msg:" << msg << std::endl; +} + +static void UtExecSql(sqlite3 *database, const char *sql, int expectRet) +{ + ASSERT_EQ(sqlite3_exec(database, sql, NULL, NULL, NULL), expectRet); +} + +static void UtPresetDb(const char *dbFile) +{ + /** + * @tc.steps: step1. Prepare db used to simulate corrupted + * @tc.expected: step1. Execute successfully + */ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open(dbFile, &db), SQLITE_OK); + /** + * @tc.steps: step1. Enable cksumvfs using PRAGMA checksum_persist_enable, + * @tc.expected: step1. Execute successfully + */ + static const char *UT_PRAGMA_CKSUM_ENABLE = "PRAGMA checksum_persist_enable=ON"; + UtExecSql(db, UT_PRAGMA_CKSUM_ENABLE, SQLITE_OK); + static const char *UT_PRAGMA_WAL_MODE = "PRAGMA journal_mode=wal;"; + UtExecSql(db, UT_PRAGMA_WAL_MODE, SQLITE_OK); + /** + * @tc.steps: step1. Create table and fill it, make db size big enough + * @tc.expected: step1. Execute successfully + */ + static const char *UT_DDL_CREATE_TABLE = "CREATE TABLE salary(" + "entryId INTEGER PRIMARY KEY," + "entryName Text," + "salary REAL," + "class INTEGER);"; + EXPECT_EQ(sqlite3_exec(db, UT_DDL_CREATE_TABLE, NULL, NULL, NULL), SQLITE_OK); + static const char *UT_SQL_INSERT_DATA = "INSERT INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *insertStmt = NULL; + EXPECT_EQ(sqlite3_prepare_v2(db, UT_SQL_INSERT_DATA, -1, &insertStmt, NULL), SQLITE_OK); + for (int i = 0; i < UT_PRESET_DATA_COUNT; i++) { + // bind parameters, 1, 2, 3, 4 are sequence number of fields + std::string entryName = "xxxxxxxxxxxxxxxxxxxxxxx"; + entryName += std::to_string(i); + sqlite3_bind_int(insertStmt, 1, i + 1); + sqlite3_bind_text(insertStmt, 2, entryName.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_double(insertStmt, 3, i * 1.0); + sqlite3_bind_int(insertStmt, 4, i + 1); + EXPECT_EQ(sqlite3_step(insertStmt), SQLITE_DONE); + sqlite3_reset(insertStmt); + } + sqlite3_finalize(insertStmt); + sqlite3_close(db); +} + +class LibSQLiteMultiThreadTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void LibSQLiteMultiThreadTest::SetUpTestCase(void) +{ + Common::MakeDir(UT_DIR); +} + +void LibSQLiteMultiThreadTest::TearDownTestCase(void) +{ + Common::RemoveDir(UT_DIR); +} + +void LibSQLiteMultiThreadTest::SetUp(void) +{ + unlink(UT_DB); + sqlite3_config(SQLITE_CONFIG_LOG, &UtSqliteLogPrint, NULL); + EXPECT_EQ(sqlite3_register_cksumvfs(NULL), SQLITE_OK); + UtPresetDb(UT_DB); +} + +void LibSQLiteMultiThreadTest::TearDown(void) +{ + EXPECT_EQ(sqlite3_unregister_cksumvfs(), SQLITE_OK); + sqlite3_config(SQLITE_CONFIG_LOG, NULL, NULL); +} + +static void ThreadQuery(void) +{ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open(UT_DB, &db), SQLITE_OK); + static const char *UT_SQL_SELECT = "SELECT entryId, entryName, salary, class FROM salary WHERE entryId=?;"; + sqlite3_stmt *sqlStmt = nullptr; + int errCode = sqlite3_prepare_v2(db, UT_SQL_SELECT, -1, &sqlStmt, NULL); + EXPECT_TRUE(errCode == SQLITE_OK || errCode == SQLITE_BUSY); + if (errCode != SQLITE_OK) { + return; + } + for (int i = 0; i < UT_PRESET_DATA_COUNT; i++) { + sqlite3_bind_int(sqlStmt, 1, i + 1); + EXPECT_EQ(sqlite3_step(sqlStmt), SQLITE_ROW); + EXPECT_EQ(sqlite3_step(sqlStmt), SQLITE_DONE); + sqlite3_reset(sqlStmt); + sqlite3_sleep(1); + } + sqlite3_finalize(sqlStmt); + sqlite3_close(db); +} + +static void ThreadUpsert(void) +{ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open(UT_DB, &db), SQLITE_OK); + static const char *UT_SQL_UPSERT = "INSERT OR REPLACE INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *upsertStmt = nullptr; + int errCode = sqlite3_prepare_v2(db, UT_SQL_UPSERT, -1, &upsertStmt, NULL); + EXPECT_TRUE(errCode == SQLITE_OK || errCode == SQLITE_BUSY); + if (errCode != SQLITE_OK) { + return; + } + for (int i = 0; i < 100; i++) { // 100 is data set's size + std::string entryName = "uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu"; + entryName += std::to_string(i); + sqlite3_bind_int(upsertStmt, 1, i + 1); + sqlite3_bind_text(upsertStmt, 2, entryName.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_double(upsertStmt, 3, i * 1.0); + sqlite3_bind_int(upsertStmt, 4, i + 1); + EXPECT_EQ(sqlite3_step(upsertStmt), SQLITE_DONE); + sqlite3_reset(upsertStmt); + sqlite3_sleep(1); + } + sqlite3_finalize(upsertStmt); + sqlite3_close(db); +} + +static void ThreadDelete(void) +{ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open(UT_DB, &db), SQLITE_OK); + static const char *UT_SQL_DELETE = "DELETE FROM salary WHERE entryId=?;"; + sqlite3_stmt *deleteStmt = nullptr; + int errCode = sqlite3_prepare_v2(db, UT_SQL_DELETE, -1, &deleteStmt, NULL); + EXPECT_TRUE(errCode == SQLITE_OK || errCode == SQLITE_BUSY); + if (errCode != SQLITE_OK) { + return; + } + for (int i = 100; i < 10; i++) { // 100 is delete entry's begin position, 10 is the size going to delete + sqlite3_bind_int(deleteStmt, 1, i + 1); + EXPECT_EQ(sqlite3_step(deleteStmt), SQLITE_DONE); + sqlite3_reset(deleteStmt); + sqlite3_sleep(1); + } + sqlite3_finalize(deleteStmt); + sqlite3_close(db); +} + +/** + * @tc.name: Concurrent_Test_001 + * @tc.desc: Test to execute CRUD on different thread. + * @tc.type: FUNC + */ +HWTEST_F(LibSQLiteMultiThreadTest, Concurrent_Test_001, TestSize.Level2) +{ + /** + * @tc.steps: step1. Execute CRUD on concurrent + * @tc.expected: step1. Execute successfully + */ + std::vector threads; + threads.emplace_back(ThreadQuery); + threads.emplace_back(ThreadUpsert); + threads.emplace_back(ThreadDelete); + for (auto &thread : threads) { + thread.join(); + } + + /** + * @tc.steps: step2. Check result + * @tc.expected: step2. Execute successfully + */ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open(UT_DB, &db), SQLITE_OK); + static const char *UT_SQL_SELECT = "SELECT COUNT(1) FROM salary;"; + sqlite3_stmt *sqlStmt = nullptr; + EXPECT_EQ(sqlite3_prepare_v2(db, UT_SQL_SELECT, -1, &sqlStmt, NULL), SQLITE_OK); + EXPECT_EQ(sqlite3_step(sqlStmt), SQLITE_ROW); + int count = sqlite3_column_int(sqlStmt, 0); + EXPECT_EQ(count, UT_PRESET_DATA_COUNT - 10); // 10 is number of delete entry + EXPECT_EQ(sqlite3_step(sqlStmt), SQLITE_DONE); + sqlite3_finalize(sqlStmt); + sqlite3_close(db); +} \ No newline at end of file