#include #include #include "base.h" #include "storage.h" class StorageTransactionsTest : public testing::Test { protected: StorageTransactionsTest(): testing::Test(), t1(db->getStorage("table1")), t2(db->getStorage("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("table1"); db->addStorage("table2"); } db->open(); db->drop(); } static void TearDownTestSuite() { db->close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; LMDBAL::Storage* t1; LMDBAL::Storage* 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(5); 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(10); 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(5); 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(10); 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); }