#include #include #include "base.h" #include "cache.h" class CacheTransactionsTest : public testing::Test { protected: CacheTransactionsTest(): testing::Test(), c1(db->getCache("cache1")), c2(db->getCache("cache2")) {} ~CacheTransactionsTest() {} 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("cache1"); db->addStorage("cache2"); } db->open(); db->drop(); } static void TearDownTestSuite() { db->close(); db->removeDirectory(); delete db; db = nullptr; } static LMDBAL::Base* db; LMDBAL::Cache* c1; LMDBAL::Cache* c2; }; LMDBAL::Base* CacheTransactionsTest::db = nullptr; TEST_F(CacheTransactionsTest, Adding) { EXPECT_EQ(db->ready(), true); EXPECT_EQ(c1->count(), 0); EXPECT_EQ(c2->count(), 0); LMDBAL::WriteTransaction txn = db->beginTransaction(); c1->addRecord(5, 13, txn); c1->addRecord(-53, 782, txn); c1->addRecord(5892, -37829, txn); c2->addRecord("lorem", 481, txn); c2->addRecord("decallence", 8532.48, txn); c2->addRecord("prevent recovery", -64.64, txn); EXPECT_EQ(c1->count(), 0); EXPECT_EQ(c2->count(), 0); txn.commit(); EXPECT_EQ(c1->count(), 3); EXPECT_EQ(c1->getRecord(5), 13); EXPECT_EQ(c1->getRecord(-53), 782); EXPECT_EQ(c1->getRecord(5892), -37829); EXPECT_EQ(c2->count(), 3); EXPECT_FLOAT_EQ(c2->getRecord("lorem"), 481); EXPECT_FLOAT_EQ(c2->getRecord("decallence"), 8532.48); EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery"), -64.64); } TEST_F(CacheTransactionsTest, Aborting) { EXPECT_EQ(db->ready(), true); LMDBAL::SizeType s1 = c1->count(); LMDBAL::SizeType s2 = c2->count(); LMDBAL::WriteTransaction txn = db->beginTransaction(); c1->addRecord(18, 40, txn); c1->addRecord(85, -4, txn); c1->addRecord(-5, -3, txn); c2->addRecord("tapestry", .053, txn); c2->addRecord("pepper plants are beautifull", -7, txn); c2->addRecord("horrots", -23.976, txn); EXPECT_EQ(c1->count(), s1); EXPECT_EQ(c2->count(), s2); txn.abort(); EXPECT_EQ(c1->count(), s1); EXPECT_EQ(c2->count(), s2); } TEST_F(CacheTransactionsTest, Reading) { EXPECT_EQ(db->ready(), true); LMDBAL::Transaction txn = db->beginReadOnlyTransaction(); EXPECT_EQ(c1->count(txn), 3); EXPECT_EQ(c1->getRecord(5, txn), 13); EXPECT_EQ(c1->getRecord(-53, txn), 782); EXPECT_EQ(c1->getRecord(5892, txn), -37829); EXPECT_EQ(c2->count(txn), 3); EXPECT_FLOAT_EQ(c2->getRecord("lorem", txn), 481); EXPECT_FLOAT_EQ(c2->getRecord("decallence", txn), 8532.48); EXPECT_FLOAT_EQ(c2->getRecord("prevent recovery", txn), -64.64); txn.terminate(); } TEST_F(CacheTransactionsTest, ConcurentReading) { EXPECT_EQ(db->ready(), true); LMDBAL::SizeType size = c1->count(); LMDBAL::WriteTransaction txn = db->beginTransaction(); EXPECT_EQ(c1->getRecord(5, txn), 13); EXPECT_EQ(c1->getRecord(5), 13); c1->removeRecord(5, txn); EXPECT_FALSE(c1->checkRecord(5, txn)); EXPECT_EQ(c1->getRecord(5), 13); c1->addRecord(5, 571, txn); EXPECT_EQ(c1->getRecord(5, txn), 571); EXPECT_EQ(c1->getRecord(5), 13); c1->forceRecord(5, -472, txn); EXPECT_EQ(c1->getRecord(5, txn), -472); EXPECT_EQ(c1->getRecord(5), 13); c1->replaceAll({ {1, 75} }, txn); EXPECT_FALSE(c1->checkRecord(5, txn)); EXPECT_EQ(c1->getRecord(5), 13); EXPECT_EQ(c1->count(txn), 1); EXPECT_EQ(c1->count(), size); txn.commit(); EXPECT_FALSE(c1->checkRecord(5)); EXPECT_EQ(c1->count(), 1); } TEST_F(CacheTransactionsTest, 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(c1->getRecord(5, txn2), 812); std::cout << "forcing second transaction value" << std::endl; c1->forceRecord(5, -46, txn2); std::cout << "checking second transaction value" << std::endl; EXPECT_EQ(c1->getRecord(5, txn2), -46); std::cout << "checking value independently" << std::endl; EXPECT_EQ(c1->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; c1->addRecord(5, 812, txn1); std::cout << "checking first transaction value" << std::endl; EXPECT_EQ(c1->getRecord(5, txn1), 812); std::cout << "checking value independently" << std::endl; EXPECT_FALSE(c1->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 final result" << std::endl; EXPECT_EQ(c1->getRecord(5), -46); } TEST_F(CacheTransactionsTest, 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(c1->checkRecord(221, txn2)); std::cout << "performing modification from the child thread" << std::endl; c1->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; c1->addRecord(221, 320, txn1); std::cout << "checking value from parent thread using transaction" << std::endl; EXPECT_EQ(c1->getRecord(221, txn1), 320); std::cout << "checking value independently from parent thread" << std::endl; EXPECT_FALSE(c1->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(c1->getRecord(221), 14); } TEST_F(CacheTransactionsTest, TransactionTerminationOnClose) { LMDBAL::WriteTransaction txn = db->beginTransaction(); c1->addRecord(578, 4552, txn); EXPECT_EQ(c1->getRecord(578, txn), 4552); EXPECT_EQ(c1->checkRecord(578), false); db->close(); db->open(); EXPECT_EQ(txn.isActive(), false); EXPECT_THROW(c1->getRecord(578, txn), LMDBAL::TransactionTerminated); EXPECT_NO_THROW(txn.commit()); EXPECT_EQ(c1->checkRecord(578), false); txn = db->beginTransaction(); c1->addRecord(578, 4552, txn); txn.commit(); EXPECT_EQ(c1->getRecord(578), 4552); }