diff options
Diffstat (limited to 'tests/auto/qkeyvaluestore/tst_qkeyvaluestore.cpp')
-rw-r--r-- | tests/auto/qkeyvaluestore/tst_qkeyvaluestore.cpp | 1403 |
1 files changed, 1403 insertions, 0 deletions
diff --git a/tests/auto/qkeyvaluestore/tst_qkeyvaluestore.cpp b/tests/auto/qkeyvaluestore/tst_qkeyvaluestore.cpp new file mode 100644 index 0000000..6c67bf5 --- /dev/null +++ b/tests/auto/qkeyvaluestore/tst_qkeyvaluestore.cpp @@ -0,0 +1,1403 @@ +#include <QtCore/QString> +#include <QtTest/QtTest> + +#include <QFile> +#include <QString> +#include <QByteArray> +#include <QHash> +#include <unistd.h> +#include <string.h> + +#include "qkeyvaluestore.h" +#include "qkeyvaluestoretxn.h" +#include "qkeyvaluestorecursor.h" +#include "qkeyvaluestorefile.h" +#include "qkeyvaluestoreentry.h" + +char testString[] = "this is a test"; +quint32 testStringSize = 14; + +class QKeyValueStoreTest : public QObject +{ + Q_OBJECT + QString m_dbName; + QString m_journal; + QString m_tree; + QString m_treeCopy; + +public: + QKeyValueStoreTest(); + void removeTemporaryFiles(); +private Q_SLOTS: + void sanityCheck(); + void storeAndLoad(); + void transactionLifetime(); + void autoSync(); + void manualSync(); + void compactionAfterRemove(); + void compactionAfterPut(); + void compactionAutoTrigger(); + void compactionContinuation(); + void fastOpen(); + void testNonAsciiChars(); + void buildBTreeFromScratch(); + void buildBTreeFromKnownState(); + void cursorSanityCheck(); + void cursorAscendingOrder(); + void cursorSeek(); + void cursorSeekRange(); + void fileSanityCheck(); + void fileWrite(); + void fileRead(); + void fileOffset(); + void fileSync(); + void fileTruncate(); +#if 0 +#endif +}; + +QKeyValueStoreTest::QKeyValueStoreTest() +{ + m_dbName = "db"; + m_journal = m_dbName + ".dat"; + m_tree = m_dbName + ".btr"; + m_treeCopy = m_dbName + ".old"; +} + +void QKeyValueStoreTest::removeTemporaryFiles() +{ + QFile::remove(m_journal); + QFile::remove(m_tree); + QFile::remove(m_treeCopy); +} + +/* + * In a way this is a useless test, however it fulfills a purpose. + * All this operations should succeed unless something is really wrong. + * So despite not testing the functionality, we test that the API returns + * the right values. + */ +void QKeyValueStoreTest::sanityCheck() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + bool test = false; + test = storage.open(); + QCOMPARE(test, true); + + QKeyValueStoreTxn *readTxn = storage.beginRead(); + QVERIFY2(readTxn, "Read txn is NULL"); + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "Write txn is NULL"); + + delete readTxn; + delete writeTxn; + + test = storage.close(); + QCOMPARE(test, true); + removeTemporaryFiles(); +} + +/* + * Simple test. We store some values, then read the values back. + */ +void QKeyValueStoreTest::storeAndLoad() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + bool test = false; + test = storage.open(); + QCOMPARE(test, true); + + // Set the sync threshold very low + storage.setSyncThreshold(5); + + QByteArray key("key"); + QByteArray value("value"); + + // Create a write transaction + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "Write txn is NULL"); + test = writeTxn->put(key, value); + QCOMPARE(test, true); + test = writeTxn->commit(100); + QCOMPARE(test, true); + + // Create a read transaction + QKeyValueStoreTxn *readTxn = storage.beginRead(); + QVERIFY2(readTxn, "Read txn is NULL"); + QByteArray retrievedValue; + test = readTxn->get(key, &retrievedValue); + QCOMPARE(test, true); + QCOMPARE(value, retrievedValue); + delete readTxn; + + test = storage.close(); + QCOMPARE(test, true); + removeTemporaryFiles(); +} + +/* + * We create a write transaction and store some values into the db. + * Afterwards we create two transactions, one read and one write and + * start writing on the db. The read transaction shouldn't see the changes. + * After commiting the changes we create a new read transaction and see the + * new values. + */ +void QKeyValueStoreTest::transactionLifetime() +{ + QByteArray key0("key0"), value0("value0"); + QByteArray key1("key1"), value1("value1"); + QByteArray key2("key2"), value2("value2"); + QByteArray key3("key3"), value3("value3"); + + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + + // Set the sync threshold very low + storage.setSyncThreshold(5); + + // Create a write transaction + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "Write txn is NULL"); + QVERIFY(writeTxn->put(key0, value0)); + QVERIFY(writeTxn->commit(100)); +#if defined(JSONDB_USE_KVS_MULTIPLE_TRANSACTIONS) + // Now, the transaction is commited. Let's create the other two. + QKeyValueStoreTxn *readTxn = storage.beginRead(); + QVERIFY2(readTxn, "Read txn is NULL"); + QKeyValueStoreTxn *newWriteTxn = storage.beginWrite(); + QVERIFY2(newWriteTxn, "newWrite txn is NULL"); + + // Write some more entries + QVERIFY(newWriteTxn->put(key1, value1)); + QVERIFY(newWriteTxn->put(key2, value2)); + QVERIFY(newWriteTxn->put(key3, value3)); + + // Try to read those entries + QByteArray recoveredValue; + QVERIFY(readTxn->get(key0, &recoveredValue)); + QCOMPARE(recoveredValue, value0); + QVERIFY(!readTxn->get(key1, &recoveredValue)); + QVERIFY(!readTxn->get(key2, &recoveredValue)); + QVERIFY(!readTxn->get(key3, &recoveredValue)); + + // However the write transaction can see it + QVERIFY(newWriteTxn->get(key1, &recoveredValue)); + QCOMPARE(value1, recoveredValue); + + // Commit the writes + QVERIFY(newWriteTxn->commit(101)); + + // Try to get them, once more + QVERIFY(!readTxn->get(key1, &recoveredValue)); + QVERIFY(!readTxn->get(key2, &recoveredValue)); + QVERIFY(!readTxn->get(key3, &recoveredValue)); + + // Create a new read transaction and get them + QKeyValueStoreTxn *newReadTxn = storage.beginRead(); + QByteArray recoveredValue1, recoveredValue2, recoveredValue3; + // Try to get them, once more + QVERIFY(newReadTxn->get(key1, &recoveredValue1)); + QCOMPARE(value1, recoveredValue1); + QVERIFY(newReadTxn->get(key2, &recoveredValue2)); + QCOMPARE(value2, recoveredValue2); + QVERIFY(newReadTxn->get(key3, &recoveredValue3)); + QCOMPARE(value3, recoveredValue3); +#endif + QVERIFY(storage.close()); + removeTemporaryFiles(); +} + +/* + * It seems that QMap and QByteArray do not share its love for non-ascii + * characters. This test case checks that. + */ +void QKeyValueStoreTest::testNonAsciiChars() +{ + removeTemporaryFiles(); + + char rawKey0[] = { 0, 0, 0, 2, 0 }; + char rawKey1[] = { 0, 0, 0, 2, 1 }; + char rawKey2[] = { 0, 0, 0, 2, 2 }; + char rawKey3[] = { 0, 0, 0, 2, 3 }; + int rawKeySize = 5; + QByteArray key0(rawKey0, rawKeySize); + QByteArray key1(rawKey1, rawKeySize); + QByteArray key2(rawKey2, rawKeySize); + QByteArray key3(rawKey3, rawKeySize); + char rawValue0[] = { 1, 0, 0, 2, 0 }; + char rawValue1[] = { 1, 0, 0, 2, 1 }; + char rawValue2[] = { 1, 0, 0, 2, 2 }; + char rawValue3[] = { 1, 0, 0, 2, 3 }; + int rawValueSize = 5; + QByteArray value0(rawValue0, rawValueSize); + QByteArray value1(rawValue1, rawValueSize); + QByteArray value2(rawValue2, rawValueSize); + QByteArray value3(rawValue3, rawValueSize); + + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + // Set the sync threshold very low + storage.setSyncThreshold(5); + + // Create a write transaction + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "Write txn is NULL"); + QVERIFY(writeTxn->put(key0, value0)); + QVERIFY(writeTxn->put(key1, value1)); + QVERIFY(writeTxn->put(key2, value2)); + QVERIFY(writeTxn->put(key3, value3)); + QVERIFY(writeTxn->commit(100)); + + QKeyValueStoreTxn *readTxn = storage.beginRead(); + QVERIFY2(readTxn, "Read txn is NULL"); + QByteArray recoveredValue; + QVERIFY(readTxn->get(key0, &recoveredValue)); + QCOMPARE(recoveredValue, value0); + QVERIFY(readTxn->get(key1, &recoveredValue)); + QCOMPARE(recoveredValue, value1); + QVERIFY(readTxn->get(key2, &recoveredValue)); + QCOMPARE(recoveredValue, value2); + QVERIFY(readTxn->get(key3, &recoveredValue)); + QCOMPARE(recoveredValue, value3); + QVERIFY(readTxn->abort()); + + removeTemporaryFiles(); +} + +/* + * We create a database and start writing elements to it. + * We count the number of marks on the DB and check the status + * of the btree. + */ +void QKeyValueStoreTest::autoSync() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + /* + * We write 100 entries, and check if the automatic sync kicked in. + * This can be verified by checking the number of marks on the db file. + * Entries 0..9 weight 20 bytes, and the rest 21. That's about 2090 + * bytes, therefore we should have 2 marks (2nd mark comes at 2048 bytes). + */ + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i + 1024)); + } + QVERIFY(storage.close()); + // Now we open the file and inspect it. + QFile db(m_journal); + db.open(QIODevice::ReadOnly); + QVERIFY(db.size() != 0); + QDataStream stream(&db); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream >> count; + while (!stream.atEnd()) { + QByteArray key; + stream >> key; + quint8 operation = 0; + stream >> operation; + QByteArray value; + stream >> value; + quint32 hash = 0xFFFFFFFF; + stream >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream >> tag; + quint32 marker = 0; + stream >> marker; + if (marker == m_marker) { + // Yes we do! + stream >> dataTimestamp; + stream >> hash; + numberOfMarkers++; + stream >> count; + } else { + count = marker; + } + found = 0; + } + } + // 2 automatic markers and one after calling close + QCOMPARE(numberOfMarkers, 3); + // And now to inspect the btree + QFile tree(m_tree); + tree.open(QIODevice::ReadOnly); + QDataStream treeStream(&tree); + treeStream.setByteOrder(QDataStream::LittleEndian); + quint64 treeTimestamp = 0; + found = 0; + treeStream >> count; + QCOMPARE(count, (quint32)100); + while (!treeStream.atEnd()) { + QByteArray key; + qint64 value; + treeStream >> key; + treeStream >> value; + found++; + if (count == found) + break; + } + treeStream >> treeTimestamp; + quint32 hash = 0xFFFFFFFF; + treeStream >> hash; + quint32 computedHash = qHash(treeTimestamp); + QCOMPARE(hash, computedHash); + QCOMPARE(treeTimestamp, dataTimestamp); + + removeTemporaryFiles(); +} + +/* + * We create a database and start writing elements to it. + * We count the number of marks on the DB and check the status + * of the btree. + */ +void QKeyValueStoreTest::manualSync() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + // Set it on manual sync mode + storage.setSyncThreshold(0); + // Now run the test + QVERIFY(storage.open()); + /* + * We write 100 entries, and check if the automatic sync kicked in. + * This can be verified by checking the number of marks on the db file. + * Entries 0..9 weight 20 bytes, and the rest 21. That's about 2090 + * bytes. Since this is in manual sync mode, we should have no markers until + * we call close or sync. + */ + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(100)); + } + QVERIFY(storage.close()); + // Now we open the file and inspect it. + QFile db(m_journal); + db.open(QIODevice::ReadOnly); + QDataStream stream(&db); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream >> count; + while (!stream.atEnd()) { + QByteArray key; + stream >> key; + quint8 operation = 0; + stream >> operation; + QByteArray value; + stream >> value; + quint32 hash = 0xFFFFFFFF; + stream >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream >> tag; + quint32 marker = 0; + stream >> marker; + if (marker == m_marker) { + // Yes we do! + stream >> dataTimestamp; + stream >> hash; + numberOfMarkers++; + stream >> count; + } else + count = marker; + found = 0; + } + } + // One marker after calling close + QCOMPARE(numberOfMarkers, 1); + // And now to inspect the btree + QFile tree(m_tree); + tree.open(QIODevice::ReadOnly); + QDataStream treeStream(&tree); + treeStream.setByteOrder(QDataStream::LittleEndian); + quint64 treeTimestamp = 0; + found = 0; + treeStream >> count; + QCOMPARE(count, (quint32)100); + while (!treeStream.atEnd()) { + QByteArray key; + qint64 value; + treeStream >> key; + treeStream >> value; + found++; + if (count == found) + break; + } + treeStream >> treeTimestamp; + quint32 hash = 0xFFFFFFFF; + treeStream >> hash; + quint32 computedHash = qHash(treeTimestamp); + QCOMPARE(hash, computedHash); + QCOMPARE(treeTimestamp, dataTimestamp); + + removeTemporaryFiles(); +} + +/* + * To test compaction we need to add elements and then + * remove some of them. The compacted file should not have those elements. + */ +void QKeyValueStoreTest::compactionAfterRemove() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + // Set it on manual sync mode + storage.setSyncThreshold(0); + // Now run the test + QVERIFY(storage.open()); + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i)); + } + // Let's remove all of them but one + for (int i = 0; i < 99; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->remove(key)); + QVERIFY(writeTxn->commit(i)); + } + QVERIFY(storage.sync()); + // Check that all elements are there. + qint32 addOperations = 0, removeOperations = 0; + QFile check1(m_dbName + ".dat"); + QVERIFY(check1.open(QIODevice::ReadOnly)); + QDataStream stream1(&check1); + stream1.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream1 >> count; + while (!stream1.atEnd()) { + QByteArray key; + stream1 >> key; + quint8 operation = 0; + stream1 >> operation; + // Add + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream1 >> value; + quint32 hash = 0xFFFFFFFF; + stream1 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream1 >> tag; + quint32 marker = 0; + stream1 >> marker; + if (marker == m_marker) { + // Yes we do! + stream1 >> dataTimestamp; + stream1 >> hash; + numberOfMarkers++; + stream1 >> count; + } else + count = marker; + found = 0; + } + } + check1.close(); + QCOMPARE(addOperations, 100); + QCOMPARE(removeOperations, 99); + // Let's compact the file and check that only one element is there. + QVERIFY(storage.compact()); + QFile check2(m_dbName + ".dat"); + QVERIFY(check2.open(QIODevice::ReadOnly)); + QDataStream stream2(&check2); + stream2.setByteOrder(QDataStream::LittleEndian); + addOperations = 0; + removeOperations = 0; + while (!stream2.atEnd()) { + QByteArray key; + stream2 >> key; + quint8 operation = 0; + stream2 >> operation; + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream2 >> value; + quint32 hash = 0xFFFFFFFF; + stream2 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream2 >> tag; + quint32 marker = 0; + stream2 >> marker; + if (marker == m_marker) { + // Yes we do! + stream2 >> dataTimestamp; + stream2 >> hash; + numberOfMarkers++; + stream2 >> count; + } else + count = marker; + found = 0; + } + } + check2.close(); + QCOMPARE(addOperations, 1); + QCOMPARE(removeOperations, 0); + // Close the file + QVERIFY(storage.close()); + removeTemporaryFiles(); +} + +/* + * To test compaction we need to add elements and update them. + * The compacted file should have only one version of each element. + */ +void QKeyValueStoreTest::compactionAfterPut() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + // Set it on manual sync mode + storage.setSyncThreshold(0); + // Now run the test + QVERIFY(storage.open()); + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QByteArray key("key-0"); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i)); + } + QVERIFY(storage.sync()); + // Check that all elements are there. + qint32 addOperations = 0, removeOperations = 0; + QFile check1(m_dbName + ".dat"); + QVERIFY(check1.open(QIODevice::ReadOnly)); + QDataStream stream1(&check1); + stream1.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream1 >> count; + while (!stream1.atEnd()) { + QByteArray key; + stream1 >> key; + quint8 operation = 0; + stream1 >> operation; + // Add + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream1 >> value; + quint32 hash = 0xFFFFFFFF; + stream1 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream1 >> tag; + quint32 marker = 0; + stream1 >> marker; + if (marker == m_marker) { + // Yes we do! + stream1 >> dataTimestamp; + stream1 >> hash; + numberOfMarkers++; + stream1 >> count; + } else + count = marker; + found = 0; + } + } + check1.close(); + QCOMPARE(addOperations, 100); + QCOMPARE(removeOperations, 0); + // Let's compact the file and check that only one element is there. + QVERIFY(storage.compact()); + QFile check2(m_dbName + ".dat"); + QVERIFY(check2.open(QIODevice::ReadOnly)); + QDataStream stream2(&check2); + stream2.setByteOrder(QDataStream::LittleEndian); + addOperations = 0; + removeOperations = 0; + while (!stream2.atEnd()) { + QByteArray key; + stream2 >> key; + quint8 operation = 0; + stream2 >> operation; + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream2 >> value; + quint32 hash = 0xFFFFFFFF; + stream2 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream2 >> tag; + quint32 marker = 0; + stream2 >> marker; + if (marker == m_marker) { + // Yes we do! + stream2 >> dataTimestamp; + stream2 >> hash; + numberOfMarkers++; + stream2 >> count; + } else + count = marker; + found = 0; + } + } + check2.close(); + QCOMPARE(addOperations, 1); + QCOMPARE(removeOperations, 0); + // Close the file + QVERIFY(storage.close()); + removeTemporaryFiles(); +} + +/* + * To test this type of compaction we need to add elements until we trigger + * a compaction round. There are two cases, after remove or after too many + * updates. + */ +void QKeyValueStoreTest::compactionAutoTrigger() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + // Now run the test + QVERIFY(storage.open()); + // Manual sync + storage.setSyncThreshold(0); + // Compact after 100 operations + storage.setCompactThreshold(100); + QByteArray value("012345678901234"); + QByteArray key("key-0"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i)); + } + // After sync there should be only one element + QVERIFY(storage.sync()); + qint32 addOperations = 0, removeOperations = 0; + QFile check1(m_dbName + ".dat"); + QVERIFY(check1.open(QIODevice::ReadOnly)); + QDataStream stream1(&check1); + stream1.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream1 >> count; + while (!stream1.atEnd()) { + QByteArray key; + stream1 >> key; + quint8 operation = 0; + stream1 >> operation; + // Add + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream1 >> value; + quint32 hash = 0xFFFFFFFF; + stream1 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream1 >> tag; + quint32 marker = 0; + stream1 >> marker; + if (marker == m_marker) { + // Yes we do! + stream1 >> dataTimestamp; + stream1 >> hash; + numberOfMarkers++; + stream1 >> count; + } else + count = marker; + found = 0; + } + } + check1.close(); + // There should be only one element + QCOMPARE(addOperations, 1); + QCOMPARE(removeOperations, 0); + + removeTemporaryFiles(); +} + +/* + * What we test here is if the file is usable after compaction. + * First test is to write some more elements and then to close it + * and open it. + */ +void QKeyValueStoreTest::compactionContinuation() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + // Manual sync + storage.setSyncThreshold(0); + // Compact after 100 operations + storage.setCompactThreshold(100); + // Now run the test + QVERIFY(storage.open()); + { + QByteArray value("012345678901234"); + QByteArray key("key-0"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i)); + } + } + // After sync there should be only one element + QVERIFY(storage.sync()); + qint32 addOperations = 0, removeOperations = 0; + QFile check1(m_dbName + ".dat"); + QVERIFY(check1.open(QIODevice::ReadOnly)); + QDataStream stream1(&check1); + stream1.setByteOrder(QDataStream::LittleEndian); + quint32 count = 0, found = 0; + int numberOfMarkers = 0; + quint32 m_marker = 0x55AAAA55; + quint32 tag = 0; + quint64 dataTimestamp = 0; + stream1 >> count; + while (!stream1.atEnd()) { + QByteArray key; + stream1 >> key; + quint8 operation = 0; + stream1 >> operation; + // Add + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream1 >> value; + quint32 hash = 0xFFFFFFFF; + stream1 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream1 >> tag; + quint32 marker = 0; + stream1 >> marker; + if (marker == m_marker) { + // Yes we do! + stream1 >> dataTimestamp; + stream1 >> hash; + numberOfMarkers++; + stream1 >> count; + } else + count = marker; + found = 0; + } + } + check1.close(); + // There should be only one element + QCOMPARE(addOperations, 1); + QCOMPARE(removeOperations, 0); + QCOMPARE(storage.tag(), (quint32)99); + // Now we add some more elements and see what happens + { + QByteArray value("012345678901234"); + QByteArray key("key-1"); + for (int i = 100; i < 200; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(i)); + } + } + QVERIFY(storage.sync()); + addOperations = 0; + removeOperations = 0; + QFile check2(m_dbName + ".dat"); + QVERIFY(check2.open(QIODevice::ReadOnly)); + QDataStream stream2(&check2); + stream2.setByteOrder(QDataStream::LittleEndian); + count = 0; + found = 0; + numberOfMarkers = 0; + stream2 >> count; + while (!stream2.atEnd()) { + QByteArray key; + stream2 >> key; + quint8 operation = 0; + stream2 >> operation; + // Add + if (operation == QKeyValueStoreEntry::Add) + addOperations++; + else + removeOperations++; + QByteArray value; + stream2 >> value; + quint32 hash = 0xFFFFFFFF; + stream2 >> hash; + found++; + if (count == found) { + // Do we have a marker? + stream2 >> tag; + quint32 marker = 0; + stream2 >> marker; + if (marker == m_marker) { + // Yes we do! + stream2 >> dataTimestamp; + stream2 >> hash; + numberOfMarkers++; + stream2 >> count; + } else + count = marker; + found = 0; + } + } + check2.close(); + // There should be only two elements + QCOMPARE(addOperations, 2); + QCOMPARE(removeOperations, 0); + + // Now close the file + QVERIFY(storage.close()); + // And open it in a new element + QKeyValueStore storage2(m_dbName); + QVERIFY(storage2.open()); + QCOMPARE(storage2.tag(), (quint32)199); + removeTemporaryFiles(); +} + +/* + * We store values in the file. + * We close it and then try to open it again. + */ +void QKeyValueStoreTest::fastOpen() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + + // Disable the autosync + storage.setSyncThreshold(0); + + QByteArray key1("key1"); + QByteArray value1("value1"); + + // Create a write transaction + QKeyValueStoreTxn *writeTxn1 = storage.beginWrite(); + QVERIFY2(writeTxn1, "Write txn1 is NULL"); + QVERIFY(writeTxn1->put(key1, value1)); + QVERIFY(writeTxn1->commit(100)); + QVERIFY(storage.sync()); + // At this point we have one marker. + QByteArray key2("key2"); + QByteArray value2("value2"); + + // Create a write transaction + QKeyValueStoreTxn *writeTxn2 = storage.beginWrite(); + QVERIFY2(writeTxn2, "Write txn2 is NULL"); + QVERIFY(writeTxn2->put(key2, value2)); + QVERIFY(writeTxn2->commit(200)); + // Now we have two markers, so we close the storage. + QVERIFY(storage.close()); + + /* + * We open the storage and count the markers. There should be two of them, + * however since this will be a fast open, we will only find the last one. + * That means there will be only one marker. + */ + QKeyValueStore storage2(m_dbName); + QVERIFY(storage2.open()); + // Now we count the markers + QCOMPARE(storage2.markers(), 1); + + // Done, let's go. + QVERIFY(storage2.close()); + removeTemporaryFiles(); +} + +/* + * We write entries to the database and then close it. + * We delete the btree file and open the database again. + * The algorithm should rebuild the btree from the journal. + */ +void QKeyValueStoreTest::buildBTreeFromScratch() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + QVERIFY(writeTxn->commit(100)); + } + // Now we close the file + QVERIFY(storage.close()); + // Remove the file + QFile::remove(m_tree); + // Now create a new storage and see if it builds the tree. + QKeyValueStore storage2(m_dbName); + QVERIFY(storage2.open()); + QKeyValueStoreTxn *readTxn = storage2.beginRead(); + QVERIFY2(readTxn, "readTxn is NULL"); + QByteArray getKey("key-89"); + QByteArray getValue; + QVERIFY(readTxn->get(getKey, &getValue)); + QCOMPARE(getValue, value); + storage2.close(); + + removeTemporaryFiles(); +} + +/* + * We write entries to the database and then close it. + * We copy the btree file and then add some more entries to the db. + * We replace the new btree file with the old one and open the database again. + * The algorithm should rebuild the btree from the journal. + */ +void QKeyValueStoreTest::buildBTreeFromKnownState() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + bool test = false; + test = storage.open(); + QCOMPARE(test, true); + QByteArray value("012345678901234"); + for (int i = 0; i < 100; i++) { + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + test = writeTxn->put(key, value); + QCOMPARE(test, true); + test = writeTxn->commit(100); + QCOMPARE(test, true); + } + // At this time we only have two markers. + QFile::copy(m_tree, m_treeCopy); + // Now we close the file + test = storage.close(); + // Remove the file + QFile::remove(m_tree); + QFile::rename(m_treeCopy, m_tree); + + // Now create a new storage and see if it builds the tree. + QKeyValueStore storage2(m_dbName); + test = storage2.open(); + QCOMPARE(test, true); + QKeyValueStoreTxn *readTxn = storage2.beginRead(); + QVERIFY2(readTxn, "readTxn is NULL"); + QByteArray getKey("key-89"); + QByteArray getValue; + test = readTxn->get(getKey, &getValue); + QCOMPARE(test, true); + QCOMPARE(getValue, value); + storage2.close(); + removeTemporaryFiles(); +} + +/* + * This is a very useless test, but everything here should work. + */ +void QKeyValueStoreTest::cursorSanityCheck() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + bool test = false; + test = storage.open(); + QCOMPARE(test, true); + QByteArray value("012345678901234"); + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + for (int i = 0; i < 100; i++) { + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + test = writeTxn->put(key, value); + QCOMPARE(test, true); + QCOMPARE(test, true); + } + // Now we have 100 items, let's use the cursor and iterate over the first and the last. + QKeyValueStoreCursor *cursor = new QKeyValueStoreCursor(writeTxn); + // At this point the cursor should be pointing to the first item + QByteArray firstElementKey("key-0"); + QByteArray lastElementKey("key-99"); + QByteArray recoveredKey; + QByteArray recoveredValue; + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, firstElementKey); + QCOMPARE(recoveredValue, value); + // Move to the last element + QVERIFY(cursor->last()); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, lastElementKey); + QCOMPARE(recoveredValue, value); + removeTemporaryFiles(); +} + +/* + * This is a funny test, ascending order means ascending in a string kind of way. + * Therefore the keys are ordered as follows: + * key-0 + * key-1 + * key-10 + * key-11 + * ... + * key-2 + * key-20 + * ... + * key-98 + * key-99 + */ +void QKeyValueStoreTest::cursorAscendingOrder() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + bool test = false; + test = storage.open(); + QCOMPARE(test, true); + QByteArray value("012345678901234"); + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + for (int i = 0; i < 100; i++) { + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + test = writeTxn->put(key, value); + QCOMPARE(test, true); + } + // Now we have 100 items, let's use the cursor and iterate over them + QKeyValueStoreCursor *cursor = new QKeyValueStoreCursor(writeTxn); + // At this point the cursor should be pointing to the first item + for (int i = 0; i < 10; i++) { + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QByteArray recoveredKey; + QByteArray recoveredValue; + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key); + QCOMPARE(recoveredValue, value); + QVERIFY(cursor->next()); + if (0 == i) + continue; + for (int j = 0; j < 10; j++, cursor->next()) { + QString baseKey2(baseKey); + QString number2; + number2.setNum(j); + baseKey2.append(number2); + key = baseKey2.toAscii(); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key); + QCOMPARE(recoveredValue, value); + } + } + removeTemporaryFiles(); +} + +void QKeyValueStoreTest::cursorSeek() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + QByteArray value("012345678901234"); + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + for (int i = 0; i < 100; i++) { + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + } + // Now we have 100 items, let's use the cursor and iterate over them. + QKeyValueStoreCursor *cursor = new QKeyValueStoreCursor(writeTxn); + // At this point the cursor should be pointing to the first item + // First let's try seeking a known element + QByteArray key55("key-55"); + QByteArray recoveredKey; + QByteArray recoveredValue; + QVERIFY(cursor->seek(key55)); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key55); + QCOMPARE(recoveredValue, value); + // Now let's try an unknown element + QByteArray key101("key-101"); + QVERIFY2(!cursor->seek(key101), "We have an unknown element on the array (101)"); + // And let's finish with a new known element + QByteArray key10("key-10"); + QVERIFY(cursor->seek(key10)); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key10); + QCOMPARE(recoveredValue, value); + // Done + removeTemporaryFiles(); +} + +/* + * seek and seekRange are similar. The difference is on the "not found" + * reply. While seeks returns false, seekRange can return an element. + */ +void QKeyValueStoreTest::cursorSeekRange() +{ + removeTemporaryFiles(); + QKeyValueStore storage(m_dbName); + QVERIFY(storage.open()); + QByteArray value("012345678901234"); + QKeyValueStoreTxn *writeTxn = storage.beginWrite(); + for (int i = 0; i < 100; i++) { + QVERIFY2(writeTxn, "writeTxn is NULL"); + QString number; + number.setNum(i); + QString baseKey("key-"); + baseKey.append(number); + QByteArray key = baseKey.toAscii(); + QVERIFY(writeTxn->put(key, value)); + } + // Now we have 100 items, let's use the cursor and iterate over them. + QKeyValueStoreCursor *cursor = new QKeyValueStoreCursor(writeTxn); + // At this point the cursor should be pointing to the first item + // First let's try seeking a known element + QByteArray key55("key-55"); + QByteArray recoveredKey; + QByteArray recoveredValue; + QVERIFY(cursor->seekRange(key55)); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key55); + QCOMPARE(recoveredValue, value); + // Now let's try an unknown element + QByteArray key999("key-999"); + QVERIFY2(!cursor->seekRange(key999), "We have an unknown element on the array (999)"); + // And let's finish with a new known element + QByteArray key10("key-10"); + QVERIFY(cursor->seekRange(key10)); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key10); + QCOMPARE(recoveredValue, value); + // Now we try elements that are not found but have a greater than + QByteArray key101("key-101"); + QByteArray key101greater("key-11"); + QVERIFY(cursor->seekRange(key101)); + QVERIFY(cursor->current(&recoveredKey, &recoveredValue)); + QCOMPARE(recoveredKey, key101greater); + QCOMPARE(recoveredValue, value); + // Done + removeTemporaryFiles(); +} + +/* + * As all the other sanity checks this is pretty useless, although it + * checks that the API returns the correct values. + */ +void QKeyValueStoreTest::fileSanityCheck() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QVERIFY(file.close()); + removeTemporaryFiles(); +} + +/* + * Create a file and write something to it, then check the file size. + */ +void QKeyValueStoreTest::fileWrite() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)testStringSize); + QVERIFY(file.close()); + removeTemporaryFiles(); +} + +/* + * Create a file and write something to it, then check the file size. + * Once that is done, read the content back and check it is the same. + */ +void QKeyValueStoreTest::fileRead() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)testStringSize); + // Now read the content back + char buffer[15]; + file.setOffset(0); + QCOMPARE(file.read(buffer, testStringSize), (qint32)testStringSize); + QCOMPARE(strncmp(buffer, testString, testStringSize), 0); + QVERIFY(file.close()); + removeTemporaryFiles(); +} + +/* + * Create a file and write some data into it. Store the offset and then + * write some more data into it. Read the new data using the stored offset. + * Check the total file size. + */ +void QKeyValueStoreTest::fileOffset() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)testStringSize); + qint64 offset = file.size(); + // Write some more data into the file + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)(2*testStringSize)); + // Now read the data from the stored offset. + char buffer[15]; + file.setOffset(offset); + QCOMPARE(file.read(buffer, testStringSize), (qint32)testStringSize); + QCOMPARE(strncmp(buffer, testString, testStringSize), 0); + offset = file.size(); + // Write some more data into the file + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)(3*testStringSize)); + // Now read the data from the stored offset. + file.setOffset(offset); + QCOMPARE(file.read(buffer, testStringSize), (qint32)testStringSize); + QCOMPARE(strncmp(buffer, testString, testStringSize), 0); + QVERIFY(file.close()); + removeTemporaryFiles(); +} + +/* + * Create a new file and write some data to it. Without closing the file, + * open a new instance of it and start reading it. It might or not return the + * contents. On the first file call sync. Now read on the second file, the content + * should be visible. + */ +void QKeyValueStoreTest::fileSync() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QKeyValueStoreFile file2("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)testStringSize); + // Open the file a second time and try to read from it. + QVERIFY(file2.open()); + char buffer[15]; + file.setOffset(0); + QCOMPARE(file2.read(buffer, testStringSize), (qint32)testStringSize); + QCOMPARE(file2.size(), (qint64)testStringSize); + QVERIFY(file2.close()); + // Now sync the file + file.sync(); + // Open the other file and check the content + QVERIFY(file2.open()); + QCOMPARE(file2.size(), (qint64)testStringSize); + QVERIFY(file2.close()); + QVERIFY(file.close()); + removeTemporaryFiles(); +} + +/* + * Create a new file and write some data to it checking the file size. + * Open the file again with the truncate flag on and check the file size. + */ +void QKeyValueStoreTest::fileTruncate() +{ + removeTemporaryFiles(); + QKeyValueStoreFile file("db.dat"); + QVERIFY(file.open()); + QCOMPARE(file.size(), (qint64)0); + QCOMPARE(file.write(testString, testStringSize), (qint32)testStringSize); + QCOMPARE(file.size(), (qint64)testStringSize); + QVERIFY(file.close()); + QKeyValueStoreFile file2("db.dat", true); + QVERIFY(file2.open()); + QCOMPARE(file2.size(), (qint64)0); + QVERIFY(file2.close()); + removeTemporaryFiles(); +} + +#if 0 +#endif +QTEST_APPLESS_MAIN(QKeyValueStoreTest) + +#include "tst_qkeyvaluestore.moc" |