1
0
forked from blue/lmdbal
lmdbal/test/storagetransaction.cpp

304 lines
9.5 KiB
C++

#include <gtest/gtest.h>
#include <iostream>
#include "base.h"
#include "storage.h"
class StorageTransactionsTest : public testing::Test {
protected:
StorageTransactionsTest():
testing::Test(),
t1(db->getStorage<int16_t, int64_t>("table1")),
t2(db->getStorage<std::string, float>("table2")) {}
~StorageTransactionsTest() {}
int waitForChildFork(int pid) {
int status;
if (0 > waitpid(pid, &status, 0)) {
std::cerr << "[----------] Waitpid error!" << std::endl;
return (-1);
}
if (WIFEXITED(status)) {
const int exit_status = WEXITSTATUS(status);
if (exit_status != 0)
std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl;
return exit_status;
} else {
std::cerr << "[----------] Non-normal exit from child!" << std::endl;
return (-2);
}
}
static void SetUpTestSuite() {
if (db == nullptr) {
db = new LMDBAL::Base("storageTrnansactionsTestBase");
db->addStorage<int16_t, int64_t>("table1");
db->addStorage<std::string, float>("table2");
}
db->open();
db->drop();
}
static void TearDownTestSuite() {
db->close();
db->removeDirectory();
delete db;
db = nullptr;
}
static LMDBAL::Base* db;
LMDBAL::Storage<int16_t, int64_t>* t1;
LMDBAL::Storage<std::string, float>* t2;
};
LMDBAL::Base* StorageTransactionsTest::db = nullptr;
TEST_F(StorageTransactionsTest, Adding) {
EXPECT_EQ(db->ready(), true);
EXPECT_EQ(t1->count(), 0);
EXPECT_EQ(t2->count(), 0);
LMDBAL::WriteTransaction txn = db->beginTransaction();
t1->addRecord(5, 13, txn);
t1->addRecord(-53, 782, txn);
t1->addRecord(5892, -37829, txn);
t2->addRecord("lorem", 481, txn);
t2->addRecord("decallence", 8532.48, txn);
t2->addRecord("prevent recovery", -64.64, txn);
EXPECT_EQ(t1->count(), 0);
EXPECT_EQ(t2->count(), 0);
txn.commit();
EXPECT_EQ(t1->count(), 3);
EXPECT_EQ(t1->getRecord(5), 13);
EXPECT_EQ(t1->getRecord(-53), 782);
EXPECT_EQ(t1->getRecord(5892), -37829);
EXPECT_EQ(t2->count(), 3);
EXPECT_FLOAT_EQ(t2->getRecord("lorem"), 481);
EXPECT_FLOAT_EQ(t2->getRecord("decallence"), 8532.48);
EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery"), -64.64);
}
TEST_F(StorageTransactionsTest, Aborting) {
EXPECT_EQ(db->ready(), true);
LMDBAL::SizeType s1 = t1->count();
LMDBAL::SizeType s2 = t2->count();
LMDBAL::WriteTransaction txn = db->beginTransaction();
t1->addRecord(18, 40, txn);
t1->addRecord(85, -4, txn);
t1->addRecord(-5, -3, txn);
t2->addRecord("tapestry", .053, txn);
t2->addRecord("pepper plants are beautifull", -7, txn);
t2->addRecord("horrots", -23.976, txn);
EXPECT_EQ(t1->count(), s1);
EXPECT_EQ(t2->count(), s2);
txn.abort();
EXPECT_EQ(t1->count(), s1);
EXPECT_EQ(t2->count(), s2);
}
TEST_F(StorageTransactionsTest, Reading) {
EXPECT_EQ(db->ready(), true);
LMDBAL::Transaction txn = db->beginReadOnlyTransaction();
EXPECT_EQ(t1->count(txn), 3);
EXPECT_EQ(t1->getRecord(5, txn), 13);
EXPECT_EQ(t1->getRecord(-53, txn), 782);
EXPECT_EQ(t1->getRecord(5892, txn), -37829);
EXPECT_EQ(t2->count(txn), 3);
EXPECT_FLOAT_EQ(t2->getRecord("lorem", txn), 481);
EXPECT_FLOAT_EQ(t2->getRecord("decallence", txn), 8532.48);
EXPECT_FLOAT_EQ(t2->getRecord("prevent recovery", txn), -64.64);
txn.terminate();
}
TEST_F(StorageTransactionsTest, ConcurentReading) {
EXPECT_EQ(db->ready(), true);
LMDBAL::SizeType size = t1->count();
LMDBAL::WriteTransaction txn = db->beginTransaction();
EXPECT_EQ(t1->getRecord(5, txn), 13);
EXPECT_EQ(t1->getRecord(5), 13);
t1->removeRecord(5, txn);
EXPECT_FALSE(t1->checkRecord(5, txn));
EXPECT_EQ(t1->getRecord(5), 13);
t1->addRecord(5, 571, txn);
EXPECT_EQ(t1->getRecord(5, txn), 571);
EXPECT_EQ(t1->getRecord(5), 13);
t1->forceRecord(5, -472, txn);
EXPECT_EQ(t1->getRecord(5, txn), -472);
EXPECT_EQ(t1->getRecord(5), 13);
t1->replaceAll({
{1, 75}
}, txn);
EXPECT_FALSE(t1->checkRecord(5, txn));
EXPECT_EQ(t1->getRecord(5), 13);
EXPECT_EQ(t1->count(txn), 1);
EXPECT_EQ(t1->count(), size);
txn.commit();
EXPECT_FALSE(t1->checkRecord(5));
EXPECT_EQ(t1->count(), 1);
}
TEST_F(StorageTransactionsTest, ConcurentModification) {
EXPECT_EQ(db->ready(), true);
//if you start one writable transaction after another
//in a single thread like so:
//
//LMDBAL::TransactionID txn1 = db->beginTransaction();
//LMDBAL::TransactionID txn2 = db->beginTransaction();
//
//the execution should block on the second transaction
//so this test should preform in a sequence
//first the parent, then the child
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
std::cout << "beggining second transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
std::cout << "checking result of the first transaction value" << std::endl;
EXPECT_EQ(t1->getRecord(5, txn2), 812);
std::cout << "forcing second transaction value" << std::endl;
t1->forceRecord(5, -46, txn2);
std::cout << "checking second transaction value" << std::endl;
EXPECT_EQ(t1->getRecord(5, txn2), -46);
std::cout << "checking value independently" << std::endl;
EXPECT_EQ(t1->getRecord(5), 812);
std::cout << "commiting second transaction" << std::endl;
txn2.commit();
std::cout << "quitting child thread" << std::endl;
exit(testing::Test::HasFailure());
} else { // I am the parent
std::cout << "beggining first transaction" << std::endl;
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
std::cout << "adding first transaction value" << std::endl;
t1->addRecord(5, 812, txn1);
std::cout << "checking first transaction value" << std::endl;
EXPECT_EQ(t1->getRecord(5, txn1), 812);
std::cout << "checking value independently" << std::endl;
EXPECT_FALSE(t1->checkRecord(5));
std::cout << "commiting first transaction" << std::endl;
txn1.commit();
std::cout << "waiting for the other thread to finish" << std::endl;
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
}
std::cout << "checking the final result" << std::endl;
EXPECT_EQ(t1->getRecord(5), -46);
}
TEST_F(StorageTransactionsTest, RAIIResourceFree) {
EXPECT_EQ(db->ready(), true);
int pid = fork();
if (pid == 0) { // I am the child
usleep(1);
std::cout << "beggining child transaction" << std::endl;
LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
//and wait for the first transaction to get finished
std::cout << "checking result of the parent transaction value" << std::endl;
EXPECT_FALSE(t1->checkRecord(221, txn2));
std::cout << "performing modification from the child thread" << std::endl;
t1->addRecord(221, 14, txn2);
std::cout << "commiting child transaction" << std::endl;
txn2.commit();
std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl;
exit(testing::Test::HasFailure());
} else { // I am the parent
std::cout << "beggining parent transaction" << std::endl;
{
LMDBAL::WriteTransaction txn1 = db->beginTransaction();
std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
usleep(5);
std::cout << "parent thread woke up" << std::endl;
std::cout << "adding value from parent thread" << std::endl;
t1->addRecord(221, 320, txn1);
std::cout << "checking value from parent thread using transaction" << std::endl;
EXPECT_EQ(t1->getRecord(221, txn1), 320);
std::cout << "checking value independently from parent thread" << std::endl;
EXPECT_FALSE(t1->checkRecord(221));
std::cout << "implicitly aborting transaction by leaving the scope" << std::endl;
}
std::cout << "child thread should resume after this line" << std::endl;
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
}
std::cout << "checking the final result" << std::endl;
EXPECT_EQ(t1->getRecord(221), 14);
}
TEST_F(StorageTransactionsTest, TransactionTerminationOnClose) {
LMDBAL::WriteTransaction txn = db->beginTransaction();
t1->addRecord(543, 229, txn);
EXPECT_EQ(t1->getRecord(543, txn), 229);
EXPECT_EQ(t1->checkRecord(543), false);
db->close();
db->open();
EXPECT_EQ(txn.isActive(), false);
EXPECT_THROW(t1->getRecord(543, txn), LMDBAL::TransactionTerminated);
EXPECT_NO_THROW(txn.commit());
EXPECT_EQ(t1->checkRecord(543), false);
txn = db->beginTransaction();
t1->addRecord(543, 229, txn);
txn.commit();
EXPECT_EQ(t1->getRecord(543), 229);
}