summaryrefslogtreecommitdiffstats
path: root/ext/lsm1/lsm-test/lsmtest2.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/lsm1/lsm-test/lsmtest2.c')
-rw-r--r--ext/lsm1/lsm-test/lsmtest2.c488
1 files changed, 488 insertions, 0 deletions
diff --git a/ext/lsm1/lsm-test/lsmtest2.c b/ext/lsm1/lsm-test/lsmtest2.c
new file mode 100644
index 0000000..d2ef0eb
--- /dev/null
+++ b/ext/lsm1/lsm-test/lsmtest2.c
@@ -0,0 +1,488 @@
+
+/*
+** This file contains tests related to recovery following application
+** and system crashes (power failures) while writing to the database.
+*/
+
+#include "lsmtest.h"
+
+/*
+** Structure used by testCksumDatabase() to accumulate checksum values in.
+*/
+typedef struct Cksum Cksum;
+struct Cksum {
+ int nRow;
+ int cksum1;
+ int cksum2;
+};
+
+/*
+** tdb_scan() callback used by testCksumDatabase()
+*/
+static void scanCksumDb(
+ void *pCtx,
+ void *pKey, int nKey,
+ void *pVal, int nVal
+){
+ Cksum *p = (Cksum *)pCtx;
+ int i;
+
+ p->nRow++;
+ for(i=0; i<nKey; i++){
+ p->cksum1 += ((u8 *)pKey)[i];
+ p->cksum2 += p->cksum1;
+ }
+ for(i=0; i<nVal; i++){
+ p->cksum1 += ((u8 *)pVal)[i];
+ p->cksum2 += p->cksum1;
+ }
+}
+
+/*
+** tdb_scan() callback used by testCountDatabase()
+*/
+static void scanCountDb(
+ void *pCtx,
+ void *pKey, int nKey,
+ void *pVal, int nVal
+){
+ Cksum *p = (Cksum *)pCtx;
+ p->nRow++;
+
+ unused_parameter(pKey);
+ unused_parameter(nKey);
+ unused_parameter(pVal);
+ unused_parameter(nVal);
+}
+
+
+/*
+** Iterate through the entire contents of database pDb. Write a checksum
+** string based on the db contents into buffer zOut before returning. A
+** checksum string is at most 29 (TEST_CKSUM_BYTES) bytes in size:
+**
+** * 32-bit integer (10 bytes)
+** * 1 space (1 byte)
+** * 32-bit hex (8 bytes)
+** * 1 space (1 byte)
+** * 32-bit hex (8 bytes)
+** * nul-terminator (1 byte)
+**
+** The number of entries in the database is returned.
+*/
+int testCksumDatabase(
+ TestDb *pDb, /* Database handle */
+ char *zOut /* Buffer to write checksum to */
+){
+ Cksum cksum;
+ memset(&cksum, 0, sizeof(Cksum));
+ tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCksumDb);
+ sprintf(zOut, "%d %x %x",
+ cksum.nRow, (u32)cksum.cksum1, (u32)cksum.cksum2
+ );
+ assert( strlen(zOut)<TEST_CKSUM_BYTES );
+ return cksum.nRow;
+}
+
+int testCountDatabase(TestDb *pDb){
+ Cksum cksum;
+ memset(&cksum, 0, sizeof(Cksum));
+ tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCountDb);
+ return cksum.nRow;
+}
+
+/*
+** This function is a no-op if *pRc is not 0 when it is called.
+**
+** Otherwise, the two nul-terminated strings z1 and z1 are compared. If
+** they are the same, the function returns without doing anything. Otherwise,
+** an error message is printed, *pRc is set to 1 and the test_failed()
+** function called.
+*/
+void testCompareStr(const char *z1, const char *z2, int *pRc){
+ if( *pRc==0 ){
+ if( strcmp(z1, z2) ){
+ testPrintError("testCompareStr: \"%s\" != \"%s\"\n", z1, z2);
+ *pRc = 1;
+ test_failed();
+ }
+ }
+}
+
+/*
+** This function is a no-op if *pRc is not 0 when it is called.
+**
+** Otherwise, the two integers i1 and i2 are compared. If they are equal,
+** the function returns without doing anything. Otherwise, an error message
+** is printed, *pRc is set to 1 and the test_failed() function called.
+*/
+void testCompareInt(int i1, int i2, int *pRc){
+ if( *pRc==0 && i1!=i2 ){
+ testPrintError("testCompareInt: %d != %d\n", i1, i2);
+ *pRc = 1;
+ test_failed();
+ }
+}
+
+void testCaseStart(int *pRc, char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ vprintf(zFmt, ap);
+ printf(" ...");
+ va_end(ap);
+ *pRc = 0;
+ fflush(stdout);
+}
+
+/*
+** This function is a no-op if *pRc is non-zero when it is called. Zero
+** is returned in this case.
+**
+** Otherwise, the zFmt (a printf style format string) and following arguments
+** are used to create a test case name. If zPattern is NULL or a glob pattern
+** that matches the test case name, 1 is returned and the test case started.
+** Otherwise, zero is returned and the test case does not start.
+*/
+int testCaseBegin(int *pRc, const char *zPattern, const char *zFmt, ...){
+ int res = 0;
+ if( *pRc==0 ){
+ char *zTest;
+ va_list ap;
+
+ va_start(ap, zFmt);
+ zTest = testMallocVPrintf(zFmt, ap);
+ va_end(ap);
+ if( zPattern==0 || testGlobMatch(zPattern, zTest) ){
+ printf("%-50s ...", zTest);
+ res = 1;
+ }
+ testFree(zTest);
+ fflush(stdout);
+ }
+
+ return res;
+}
+
+void testCaseFinish(int rc){
+ if( rc==0 ){
+ printf("Ok\n");
+ }else{
+ printf("FAILED\n");
+ }
+ fflush(stdout);
+}
+
+void testCaseSkip(){
+ printf("Skipped\n");
+}
+
+void testSetupSavedLsmdb(
+ const char *zCfg,
+ const char *zFile,
+ Datasource *pData,
+ int nRow,
+ int *pRc
+){
+ if( *pRc==0 ){
+ int rc;
+ TestDb *pDb;
+ rc = tdb_lsm_open(zCfg, zFile, 1, &pDb);
+ if( rc==0 ){
+ testWriteDatasourceRange(pDb, pData, 0, nRow, &rc);
+ testClose(&pDb);
+ if( rc==0 ) testSaveDb(zFile, "log");
+ }
+ *pRc = rc;
+ }
+}
+
+/*
+** This function is a no-op if *pRc is non-zero when it is called.
+**
+** Open the LSM database identified by zFile and compute its checksum
+** (a string, as returned by testCksumDatabase()). If the checksum is
+** identical to zExpect1 or, if it is not NULL, zExpect2, the test passes.
+** Otherwise, print an error message and set *pRc to 1.
+*/
+static void testCompareCksumLsmdb(
+ const char *zFile, /* Path to LSM database */
+ int bCompress, /* True if db is compressed */
+ const char *zExpect1, /* Expected checksum 1 */
+ const char *zExpect2, /* Expected checksum 2 (or NULL) */
+ int *pRc /* IN/OUT: Test case error code */
+){
+ if( *pRc==0 ){
+ char zCksum[TEST_CKSUM_BYTES];
+ TestDb *pDb;
+
+ *pRc = tdb_lsm_open((bCompress?"compression=1 mmap=0":""), zFile, 0, &pDb);
+ testCksumDatabase(pDb, zCksum);
+ testClose(&pDb);
+
+ if( *pRc==0 ){
+ int r1 = 0;
+ int r2 = -1;
+
+ r1 = strcmp(zCksum, zExpect1);
+ if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
+ if( r1 && r2 ){
+ if( zExpect2 ){
+ testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
+ zCksum, zExpect1, zExpect2
+ );
+ }else{
+ testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
+ zCksum, zExpect1
+ );
+ }
+ *pRc = 1;
+ test_failed();
+ }
+ }
+ }
+}
+
+#if 0 /* not used */
+static void testCompareCksumBtdb(
+ const char *zFile, /* Path to LSM database */
+ const char *zExpect1, /* Expected checksum 1 */
+ const char *zExpect2, /* Expected checksum 2 (or NULL) */
+ int *pRc /* IN/OUT: Test case error code */
+){
+ if( *pRc==0 ){
+ char zCksum[TEST_CKSUM_BYTES];
+ TestDb *pDb;
+
+ *pRc = tdb_open("bt", zFile, 0, &pDb);
+ testCksumDatabase(pDb, zCksum);
+ testClose(&pDb);
+
+ if( *pRc==0 ){
+ int r1 = 0;
+ int r2 = -1;
+
+ r1 = strcmp(zCksum, zExpect1);
+ if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
+ if( r1 && r2 ){
+ if( zExpect2 ){
+ testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
+ zCksum, zExpect1, zExpect2
+ );
+ }else{
+ testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
+ zCksum, zExpect1
+ );
+ }
+ *pRc = 1;
+ test_failed();
+ }
+ }
+ }
+}
+#endif /* not used */
+
+/* Above this point are reusable test routines. Not clear that they
+** should really be in this file.
+*************************************************************************/
+
+/*
+** This test verifies that if a system crash occurs while doing merge work
+** on the db, no data is lost.
+*/
+static void crash_test1(int bCompress, int *pRc){
+ const char *DBNAME = "testdb.lsm";
+ const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 200, 200};
+
+ const int nRow = 5000; /* Database size */
+ const int nIter = 200; /* Number of test iterations */
+ const int nWork = 20; /* Maximum lsm_work() calls per iteration */
+ const int nPage = 15; /* Pages per lsm_work call */
+
+ int i;
+ int iDot = 0;
+ Datasource *pData;
+ CksumDb *pCksumDb;
+ TestDb *pDb;
+ char *zCfg;
+
+ const char *azConfig[2] = {
+ "page_size=1024 block_size=65536 autoflush=16384 safety=2 mmap=0",
+ "page_size=1024 block_size=65536 autoflush=16384 safety=2 "
+ " compression=1 mmap=0"
+ };
+ assert( bCompress==0 || bCompress==1 );
+
+ /* Allocate datasource. And calculate the expected checksums. */
+ pData = testDatasourceNew(&defn);
+ pCksumDb = testCksumArrayNew(pData, nRow, nRow, 1);
+
+ /* Setup and save the initial database. */
+
+ zCfg = testMallocPrintf("%s automerge=7", azConfig[bCompress]);
+ testSetupSavedLsmdb(zCfg, DBNAME, pData, 5000, pRc);
+ testFree(zCfg);
+
+ for(i=0; i<nIter && *pRc==0; i++){
+ int iWork;
+ int testrc = 0;
+
+ testCaseProgress(i, nIter, testCaseNDot(), &iDot);
+
+ /* Restore and open the database. */
+ testRestoreDb(DBNAME, "log");
+ testrc = tdb_lsm_open(azConfig[bCompress], DBNAME, 0, &pDb);
+ assert( testrc==0 );
+
+ /* Call lsm_work() on the db */
+ tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nWork*2)));
+ for(iWork=0; testrc==0 && iWork<nWork; iWork++){
+ int nWrite = 0;
+ lsm_db *db = tdb_lsm(pDb);
+ testrc = lsm_work(db, 0, nPage, &nWrite);
+ /* assert( testrc!=0 || nWrite>0 ); */
+ if( testrc==0 ) testrc = lsm_checkpoint(db, 0);
+ }
+ tdb_close(pDb);
+
+ /* Check that the database content is still correct */
+ testCompareCksumLsmdb(DBNAME,
+ bCompress, testCksumArrayGet(pCksumDb, nRow), 0, pRc);
+ }
+
+ testCksumArrayFree(pCksumDb);
+ testDatasourceFree(pData);
+}
+
+/*
+** This test verifies that if a system crash occurs while committing a
+** transaction to the log file, no earlier transactions are lost or damaged.
+*/
+static void crash_test2(int bCompress, int *pRc){
+ const char *DBNAME = "testdb.lsm";
+ const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
+
+ const int nIter = 200;
+ const int nInsert = 20;
+
+ int i;
+ int iDot = 0;
+ Datasource *pData;
+ CksumDb *pCksumDb;
+ TestDb *pDb;
+
+ /* Allocate datasource. And calculate the expected checksums. */
+ pData = testDatasourceNew(&defn);
+ pCksumDb = testCksumArrayNew(pData, 100, 100+nInsert, 1);
+
+ /* Setup and save the initial database. */
+ testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
+
+ for(i=0; i<nIter && *pRc==0; i++){
+ int iIns;
+ int testrc = 0;
+
+ testCaseProgress(i, nIter, testCaseNDot(), &iDot);
+
+ /* Restore and open the database. */
+ testRestoreDb(DBNAME, "log");
+ testrc = tdb_lsm_open("safety=2", DBNAME, 0, &pDb);
+ assert( testrc==0 );
+
+ /* Insert nInsert records into the database. Crash midway through. */
+ tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nInsert+2)));
+ for(iIns=0; iIns<nInsert; iIns++){
+ void *pKey; int nKey;
+ void *pVal; int nVal;
+
+ testDatasourceEntry(pData, 100+iIns, &pKey, &nKey, &pVal, &nVal);
+ testrc = tdb_write(pDb, pKey, nKey, pVal, nVal);
+ if( testrc ) break;
+ }
+ tdb_close(pDb);
+
+ /* Check that no data was lost when the system crashed. */
+ testCompareCksumLsmdb(DBNAME, bCompress,
+ testCksumArrayGet(pCksumDb, 100 + iIns),
+ testCksumArrayGet(pCksumDb, 100 + iIns + 1),
+ pRc
+ );
+ }
+
+ testDatasourceFree(pData);
+ testCksumArrayFree(pCksumDb);
+}
+
+
+/*
+** This test verifies that if a system crash occurs when checkpointing
+** the database, data is not lost (assuming that any writes not synced
+** to the db have been synced into the log file).
+*/
+static void crash_test3(int bCompress, int *pRc){
+ const char *DBNAME = "testdb.lsm";
+ const int nIter = 100;
+ const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
+
+ int i;
+ int iDot = 0;
+ Datasource *pData;
+ CksumDb *pCksumDb;
+ TestDb *pDb;
+
+ /* Allocate datasource. And calculate the expected checksums. */
+ pData = testDatasourceNew(&defn);
+ pCksumDb = testCksumArrayNew(pData, 110, 150, 10);
+
+ /* Setup and save the initial database. */
+ testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
+
+ for(i=0; i<nIter && *pRc==0; i++){
+ int iOpen;
+ testCaseProgress(i, nIter, testCaseNDot(), &iDot);
+ testRestoreDb(DBNAME, "log");
+
+ for(iOpen=0; iOpen<5; iOpen++){
+ /* Open the database. Insert 10 more records. */
+ pDb = testOpen("lsm", 0, pRc);
+ testWriteDatasourceRange(pDb, pData, 100+iOpen*10, 10, pRc);
+
+ /* Schedule a crash simulation then close the db. */
+ tdb_lsm_prepare_sync_crash(pDb, 1 + (i%2));
+ tdb_close(pDb);
+
+ /* Open the database and check that the crash did not cause any
+ ** data loss. */
+ testCompareCksumLsmdb(DBNAME, bCompress,
+ testCksumArrayGet(pCksumDb, 110 + iOpen*10), 0,
+ pRc
+ );
+ }
+ }
+
+ testDatasourceFree(pData);
+ testCksumArrayFree(pCksumDb);
+}
+
+void do_crash_test(const char *zPattern, int *pRc){
+ struct Test {
+ const char *zTest;
+ void (*x)(int, int *);
+ int bCompress;
+ } aTest [] = {
+ { "crash.lsm.1", crash_test1, 0 },
+#ifdef HAVE_ZLIB
+ { "crash.lsm_zip.1", crash_test1, 1 },
+#endif
+ { "crash.lsm.2", crash_test2, 0 },
+ { "crash.lsm.3", crash_test3, 0 },
+ };
+ int i;
+
+ for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
+ struct Test *p = &aTest[i];
+ if( testCaseBegin(pRc, zPattern, "%s", p->zTest) ){
+ p->x(p->bCompress, pRc);
+ testCaseFinish(*pRc);
+ }
+ }
+}