summaryrefslogtreecommitdiffstats
path: root/ext/lsm1/lsm-test/lsmtest6.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/lsm1/lsm-test/lsmtest6.c')
-rw-r--r--ext/lsm1/lsm-test/lsmtest6.c661
1 files changed, 661 insertions, 0 deletions
diff --git a/ext/lsm1/lsm-test/lsmtest6.c b/ext/lsm1/lsm-test/lsmtest6.c
new file mode 100644
index 0000000..a61b738
--- /dev/null
+++ b/ext/lsm1/lsm-test/lsmtest6.c
@@ -0,0 +1,661 @@
+
+#include "lsmtest.h"
+
+typedef struct OomTest OomTest;
+struct OomTest {
+ lsm_env *pEnv;
+ int iNext; /* Next value to pass to testMallocOom() */
+ int nFail; /* Number of OOM events injected */
+ int bEnable;
+ int rc; /* Test case error code */
+};
+
+static void testOomStart(OomTest *p){
+ memset(p, 0, sizeof(OomTest));
+ p->iNext = 1;
+ p->bEnable = 1;
+ p->nFail = 1;
+ p->pEnv = tdb_lsm_env();
+}
+
+static void xOomHook(OomTest *p){
+ p->nFail++;
+}
+
+static int testOomContinue(OomTest *p){
+ if( p->rc!=0 || (p->iNext>1 && p->nFail==0) ){
+ return 0;
+ }
+ p->nFail = 0;
+ testMallocOom(p->pEnv, p->iNext, 0, (void (*)(void*))xOomHook, (void *)p);
+ return 1;
+}
+
+static void testOomEnable(OomTest *p, int bEnable){
+ p->bEnable = bEnable;
+ testMallocOomEnable(p->pEnv, bEnable);
+}
+
+static void testOomNext(OomTest *p){
+ p->iNext++;
+}
+
+static int testOomHit(OomTest *p){
+ return (p->nFail>0);
+}
+
+static int testOomFinish(OomTest *p){
+ return p->rc;
+}
+
+static void testOomAssert(OomTest *p, int bVal){
+ if( bVal==0 ){
+ test_failed();
+ p->rc = 1;
+ }
+}
+
+/*
+** Test that the error code matches the state of the OomTest object passed
+** as the first argument. Specifically, check that rc is LSM_NOMEM if an
+** OOM error has already been injected, or LSM_OK if not.
+*/
+static void testOomAssertRc(OomTest *p, int rc){
+ testOomAssert(p, rc==LSM_OK || rc==LSM_NOMEM);
+ testOomAssert(p, testOomHit(p)==(rc==LSM_NOMEM) || p->bEnable==0 );
+}
+
+static void testOomOpen(
+ OomTest *pOom,
+ const char *zName,
+ lsm_db **ppDb,
+ int *pRc
+){
+ if( *pRc==LSM_OK ){
+ int rc;
+ rc = lsm_new(tdb_lsm_env(), ppDb);
+ if( rc==LSM_OK ) rc = lsm_open(*ppDb, zName);
+ testOomAssertRc(pOom, rc);
+ *pRc = rc;
+ }
+}
+
+static void testOomFetch(
+ OomTest *pOom,
+ lsm_db *pDb,
+ void *pKey, int nKey,
+ void *pVal, int nVal,
+ int *pRc
+){
+ testOomAssertRc(pOom, *pRc);
+ if( *pRc==LSM_OK ){
+ lsm_cursor *pCsr;
+ int rc;
+
+ rc = lsm_csr_open(pDb, &pCsr);
+ if( rc==LSM_OK ) rc = lsm_csr_seek(pCsr, pKey, nKey, 0);
+ testOomAssertRc(pOom, rc);
+
+ if( rc==LSM_OK ){
+ const void *p; int n;
+ testOomAssert(pOom, lsm_csr_valid(pCsr));
+
+ rc = lsm_csr_key(pCsr, &p, &n);
+ testOomAssertRc(pOom, rc);
+ testOomAssert(pOom, rc!=LSM_OK || (n==nKey && memcmp(pKey, p, nKey)==0) );
+ }
+
+ if( rc==LSM_OK ){
+ const void *p; int n;
+ testOomAssert(pOom, lsm_csr_valid(pCsr));
+
+ rc = lsm_csr_value(pCsr, &p, &n);
+ testOomAssertRc(pOom, rc);
+ testOomAssert(pOom, rc!=LSM_OK || (n==nVal && memcmp(pVal, p, nVal)==0) );
+ }
+
+ lsm_csr_close(pCsr);
+ *pRc = rc;
+ }
+}
+
+static void testOomWrite(
+ OomTest *pOom,
+ lsm_db *pDb,
+ void *pKey, int nKey,
+ void *pVal, int nVal,
+ int *pRc
+){
+ testOomAssertRc(pOom, *pRc);
+ if( *pRc==LSM_OK ){
+ int rc;
+
+ rc = lsm_insert(pDb, pKey, nKey, pVal, nVal);
+ testOomAssertRc(pOom, rc);
+
+ *pRc = rc;
+ }
+}
+
+
+static void testOomFetchStr(
+ OomTest *pOom,
+ lsm_db *pDb,
+ const char *zKey,
+ const char *zVal,
+ int *pRc
+){
+ int nKey = strlen(zKey);
+ int nVal = strlen(zVal);
+ testOomFetch(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc);
+}
+
+static void testOomFetchData(
+ OomTest *pOom,
+ lsm_db *pDb,
+ Datasource *pData,
+ int iKey,
+ int *pRc
+){
+ void *pKey; int nKey;
+ void *pVal; int nVal;
+ testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
+ testOomFetch(pOom, pDb, pKey, nKey, pVal, nVal, pRc);
+}
+
+static void testOomWriteStr(
+ OomTest *pOom,
+ lsm_db *pDb,
+ const char *zKey,
+ const char *zVal,
+ int *pRc
+){
+ int nKey = strlen(zKey);
+ int nVal = strlen(zVal);
+ testOomWrite(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc);
+}
+
+static void testOomWriteData(
+ OomTest *pOom,
+ lsm_db *pDb,
+ Datasource *pData,
+ int iKey,
+ int *pRc
+){
+ void *pKey; int nKey;
+ void *pVal; int nVal;
+ testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
+ testOomWrite(pOom, pDb, pKey, nKey, pVal, nVal, pRc);
+}
+
+static void testOomScan(
+ OomTest *pOom,
+ lsm_db *pDb,
+ int bReverse,
+ const void *pKey, int nKey,
+ int nScan,
+ int *pRc
+){
+ if( *pRc==0 ){
+ int rc;
+ int iScan = 0;
+ lsm_cursor *pCsr;
+ int (*xAdvance)(lsm_cursor *) = 0;
+
+
+ rc = lsm_csr_open(pDb, &pCsr);
+ testOomAssertRc(pOom, rc);
+
+ if( rc==LSM_OK ){
+ if( bReverse ){
+ rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_LE);
+ xAdvance = lsm_csr_prev;
+ }else{
+ rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_GE);
+ xAdvance = lsm_csr_next;
+ }
+ }
+ testOomAssertRc(pOom, rc);
+
+ while( rc==LSM_OK && lsm_csr_valid(pCsr) && iScan<nScan ){
+ const void *p; int n;
+
+ rc = lsm_csr_key(pCsr, &p, &n);
+ testOomAssertRc(pOom, rc);
+ if( rc==LSM_OK ){
+ rc = lsm_csr_value(pCsr, &p, &n);
+ testOomAssertRc(pOom, rc);
+ }
+ if( rc==LSM_OK ){
+ rc = xAdvance(pCsr);
+ testOomAssertRc(pOom, rc);
+ }
+ iScan++;
+ }
+
+ lsm_csr_close(pCsr);
+ *pRc = rc;
+ }
+}
+
+#define LSMTEST6_TESTDB "testdb.lsm"
+
+void testDeleteLsmdb(const char *zFile){
+ char *zLog = testMallocPrintf("%s-log", zFile);
+ char *zShm = testMallocPrintf("%s-shm", zFile);
+ unlink(zFile);
+ unlink(zLog);
+ unlink(zShm);
+ testFree(zLog);
+ testFree(zShm);
+}
+
+static void copy_file(const char *zFrom, const char *zTo, int isDatabase){
+
+ if( access(zFrom, F_OK) ){
+ unlink(zTo);
+ }else{
+ int fd1;
+ int fd2;
+ off_t sz;
+ off_t i;
+ struct stat buf;
+ u8 *aBuf;
+
+ fd1 = open(zFrom, O_RDONLY | _O_BINARY, 0644);
+ fd2 = open(zTo, O_RDWR | O_CREAT | _O_BINARY, 0644);
+
+ fstat(fd1, &buf);
+ sz = buf.st_size;
+ ftruncate(fd2, sz);
+
+ aBuf = testMalloc(4096);
+ for(i=0; i<sz; i+=4096){
+ int bLockPage = isDatabase && i == 0;
+ int nByte = MIN((bLockPage ? 4066 : 4096), sz - i);
+ memset(aBuf, 0, 4096);
+ read(fd1, aBuf, nByte);
+ write(fd2, aBuf, nByte);
+ if( bLockPage ){
+ lseek(fd1, 4096, SEEK_SET);
+ lseek(fd2, 4096, SEEK_SET);
+ }
+ }
+ testFree(aBuf);
+
+ close(fd1);
+ close(fd2);
+ }
+}
+
+void testCopyLsmdb(const char *zFrom, const char *zTo){
+ char *zLog1 = testMallocPrintf("%s-log", zFrom);
+ char *zLog2 = testMallocPrintf("%s-log", zTo);
+ char *zShm1 = testMallocPrintf("%s-shm", zFrom);
+ char *zShm2 = testMallocPrintf("%s-shm", zTo);
+
+ unlink(zShm2);
+ unlink(zLog2);
+ unlink(zTo);
+ copy_file(zFrom, zTo, 1);
+ copy_file(zLog1, zLog2, 0);
+ copy_file(zShm1, zShm2, 0);
+
+ testFree(zLog1); testFree(zLog2); testFree(zShm1); testFree(zShm2);
+}
+
+/*
+** File zFile is the path to a database. This function makes backups
+** of the database file and its log as follows:
+**
+** cp $(zFile) $(zFile)-save
+** cp $(zFile)-$(zAux) $(zFile)-save-$(zAux)
+**
+** Function testRestoreDb() can be used to copy the files back in the
+** other direction.
+*/
+void testSaveDb(const char *zFile, const char *zAux){
+ char *zLog = testMallocPrintf("%s-%s", zFile, zAux);
+ char *zFileSave = testMallocPrintf("%s-save", zFile);
+ char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux);
+
+ unlink(zFileSave);
+ unlink(zLogSave);
+ copy_file(zFile, zFileSave, 1);
+ copy_file(zLog, zLogSave, 0);
+
+ testFree(zLog); testFree(zFileSave); testFree(zLogSave);
+}
+
+/*
+** File zFile is the path to a database. This function restores
+** a backup of the database made by a previous call to testSaveDb().
+** Specifically, it does the equivalent of:
+**
+** cp $(zFile)-save $(zFile)
+** cp $(zFile)-save-$(zAux) $(zFile)-$(zAux)
+*/
+void testRestoreDb(const char *zFile, const char *zAux){
+ char *zLog = testMallocPrintf("%s-%s", zFile, zAux);
+ char *zFileSave = testMallocPrintf("%s-save", zFile);
+ char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux);
+
+ copy_file(zFileSave, zFile, 1);
+ copy_file(zLogSave, zLog, 0);
+
+ testFree(zLog); testFree(zFileSave); testFree(zLogSave);
+}
+
+
+static int lsmWriteStr(lsm_db *pDb, const char *zKey, const char *zVal){
+ int nKey = strlen(zKey);
+ int nVal = strlen(zVal);
+ return lsm_insert(pDb, (void *)zKey, nKey, (void *)zVal, nVal);
+}
+
+static void setup_delete_db(void){
+ testDeleteLsmdb(LSMTEST6_TESTDB);
+}
+
+/*
+** Create a small database. With the following content:
+**
+** "one" -> "one"
+** "two" -> "four"
+** "three" -> "nine"
+** "four" -> "sixteen"
+** "five" -> "twentyfive"
+** "six" -> "thirtysix"
+** "seven" -> "fourtynine"
+** "eight" -> "sixtyfour"
+*/
+static void setup_populate_db(void){
+ const char *azStr[] = {
+ "one", "one",
+ "two", "four",
+ "three", "nine",
+ "four", "sixteen",
+ "five", "twentyfive",
+ "six", "thirtysix",
+ "seven", "fourtynine",
+ "eight", "sixtyfour",
+ };
+ int rc;
+ int ii;
+ lsm_db *pDb;
+
+ testDeleteLsmdb(LSMTEST6_TESTDB);
+
+ rc = lsm_new(tdb_lsm_env(), &pDb);
+ if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB);
+
+ for(ii=0; rc==LSM_OK && ii<ArraySize(azStr); ii+=2){
+ rc = lsmWriteStr(pDb, azStr[ii], azStr[ii+1]);
+ }
+ lsm_close(pDb);
+
+ testSaveDb(LSMTEST6_TESTDB, "log");
+ assert( rc==LSM_OK );
+}
+
+static Datasource *getDatasource(void){
+ const DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 10, 15, 200, 250 };
+ return testDatasourceNew(&defn);
+}
+
+/*
+** Set up a database file with the following properties:
+**
+** * Page size is 1024 bytes.
+** * Block size is 64 KB.
+** * Contains 5000 key-value pairs starting at 0 from the
+** datasource returned getDatasource().
+*/
+static void setup_populate_db2(void){
+ Datasource *pData;
+ int ii;
+ int rc;
+ int nBlocksize = 64*1024;
+ int nPagesize = 1024;
+ int nWritebuffer = 4*1024;
+ lsm_db *pDb;
+
+ testDeleteLsmdb(LSMTEST6_TESTDB);
+ rc = lsm_new(tdb_lsm_env(), &pDb);
+ if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB);
+
+ lsm_config(pDb, LSM_CONFIG_BLOCK_SIZE, &nBlocksize);
+ lsm_config(pDb, LSM_CONFIG_PAGE_SIZE, &nPagesize);
+ lsm_config(pDb, LSM_CONFIG_AUTOFLUSH, &nWritebuffer);
+
+ pData = getDatasource();
+ for(ii=0; rc==LSM_OK && ii<5000; ii++){
+ void *pKey; int nKey;
+ void *pVal; int nVal;
+ testDatasourceEntry(pData, ii, &pKey, &nKey, &pVal, &nVal);
+ lsm_insert(pDb, pKey, nKey, pVal, nVal);
+ }
+ testDatasourceFree(pData);
+ lsm_close(pDb);
+
+ testSaveDb(LSMTEST6_TESTDB, "log");
+ assert( rc==LSM_OK );
+}
+
+/*
+** Test the results of OOM conditions in lsm_new().
+*/
+static void simple_oom_1(OomTest *pOom){
+ int rc;
+ lsm_db *pDb;
+
+ rc = lsm_new(tdb_lsm_env(), &pDb);
+ testOomAssertRc(pOom, rc);
+
+ lsm_close(pDb);
+}
+
+/*
+** Test the results of OOM conditions in lsm_open().
+*/
+static void simple_oom_2(OomTest *pOom){
+ int rc;
+ lsm_db *pDb;
+
+ rc = lsm_new(tdb_lsm_env(), &pDb);
+ if( rc==LSM_OK ){
+ rc = lsm_open(pDb, "testdb.lsm");
+ }
+ testOomAssertRc(pOom, rc);
+
+ lsm_close(pDb);
+}
+
+/*
+** Test the results of OOM conditions in simple fetch operations.
+*/
+static void simple_oom_3(OomTest *pOom){
+ int rc = LSM_OK;
+ lsm_db *pDb;
+
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+
+ testOomFetchStr(pOom, pDb, "four", "sixteen", &rc);
+ testOomFetchStr(pOom, pDb, "seven", "fourtynine", &rc);
+ testOomFetchStr(pOom, pDb, "one", "one", &rc);
+ testOomFetchStr(pOom, pDb, "eight", "sixtyfour", &rc);
+
+ lsm_close(pDb);
+}
+
+/*
+** Test the results of OOM conditions in simple write operations.
+*/
+static void simple_oom_4(OomTest *pOom){
+ int rc = LSM_OK;
+ lsm_db *pDb;
+
+ testDeleteLsmdb(LSMTEST6_TESTDB);
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+
+ testOomWriteStr(pOom, pDb, "123", "onetwothree", &rc);
+ testOomWriteStr(pOom, pDb, "456", "fourfivesix", &rc);
+ testOomWriteStr(pOom, pDb, "789", "seveneightnine", &rc);
+ testOomWriteStr(pOom, pDb, "123", "teneleventwelve", &rc);
+ testOomWriteStr(pOom, pDb, "456", "fourteenfifteensixteen", &rc);
+
+ lsm_close(pDb);
+}
+
+static void simple_oom_5(OomTest *pOom){
+ Datasource *pData = getDatasource();
+ int rc = LSM_OK;
+ lsm_db *pDb;
+
+ testRestoreDb(LSMTEST6_TESTDB, "log");
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+
+ testOomFetchData(pOom, pDb, pData, 3333, &rc);
+ testOomFetchData(pOom, pDb, pData, 0, &rc);
+ testOomFetchData(pOom, pDb, pData, 4999, &rc);
+
+ lsm_close(pDb);
+ testDatasourceFree(pData);
+}
+
+static void simple_oom_6(OomTest *pOom){
+ Datasource *pData = getDatasource();
+ int rc = LSM_OK;
+ lsm_db *pDb;
+
+ testRestoreDb(LSMTEST6_TESTDB, "log");
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+
+ testOomWriteData(pOom, pDb, pData, 5000, &rc);
+ testOomWriteData(pOom, pDb, pData, 5001, &rc);
+ testOomWriteData(pOom, pDb, pData, 5002, &rc);
+ testOomFetchData(pOom, pDb, pData, 5001, &rc);
+ testOomFetchData(pOom, pDb, pData, 1234, &rc);
+
+ lsm_close(pDb);
+ testDatasourceFree(pData);
+}
+
+static void simple_oom_7(OomTest *pOom){
+ Datasource *pData = getDatasource();
+ int rc = LSM_OK;
+ lsm_db *pDb;
+
+ testRestoreDb(LSMTEST6_TESTDB, "log");
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+ testOomScan(pOom, pDb, 0, "abc", 3, 20, &rc);
+ lsm_close(pDb);
+ testDatasourceFree(pData);
+}
+
+static void simple_oom_8(OomTest *pOom){
+ Datasource *pData = getDatasource();
+ int rc = LSM_OK;
+ lsm_db *pDb;
+ testRestoreDb(LSMTEST6_TESTDB, "log");
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
+ testOomScan(pOom, pDb, 1, "xyz", 3, 20, &rc);
+ lsm_close(pDb);
+ testDatasourceFree(pData);
+}
+
+/*
+** This test case has two clients connected to a database. The first client
+** hits an OOM while writing to the database. Check that the second
+** connection is still able to query the db following the OOM.
+*/
+static void simple_oom2_1(OomTest *pOom){
+ const int nRecord = 100; /* Number of records initially in db */
+ const int nIns = 10; /* Number of records inserted with OOM */
+
+ Datasource *pData = getDatasource();
+ int rc = LSM_OK;
+ lsm_db *pDb1;
+ lsm_db *pDb2;
+ int i;
+
+ testDeleteLsmdb(LSMTEST6_TESTDB);
+
+ /* Open the two connections. Initialize the in-memory tree so that it
+ ** contains 100 records. Do all this with OOM injection disabled. */
+ testOomEnable(pOom, 0);
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb1, &rc);
+ testOomOpen(pOom, LSMTEST6_TESTDB, &pDb2, &rc);
+ for(i=0; i<nRecord; i++){
+ testOomWriteData(pOom, pDb1, pData, i, &rc);
+ }
+ testOomEnable(pOom, 1);
+ assert( rc==0 );
+
+ /* Insert 10 more records using pDb1. Stop when an OOM is encountered. */
+ for(i=nRecord; i<nRecord+nIns; i++){
+ testOomWriteData(pOom, pDb1, pData, i, &rc);
+ if( rc ) break;
+ }
+ testOomAssertRc(pOom, rc);
+
+ /* Switch off OOM injection. Write a few rows using pDb2. Then check
+ ** that the database may be successfully queried. */
+ testOomEnable(pOom, 0);
+ rc = 0;
+ for(; i<nRecord+nIns && rc==0; i++){
+ testOomWriteData(pOom, pDb2, pData, i, &rc);
+ }
+ for(i=0; i<nRecord+nIns; i++) testOomFetchData(pOom, pDb2, pData, i, &rc);
+ testOomEnable(pOom, 1);
+
+ lsm_close(pDb1);
+ lsm_close(pDb2);
+ testDatasourceFree(pData);
+}
+
+
+static void do_test_oom1(const char *zPattern, int *pRc){
+ struct SimpleOom {
+ const char *zName;
+ void (*xSetup)(void);
+ void (*xFunc)(OomTest *);
+ } aSimple[] = {
+ { "oom1.lsm.1", setup_delete_db, simple_oom_1 },
+ { "oom1.lsm.2", setup_delete_db, simple_oom_2 },
+ { "oom1.lsm.3", setup_populate_db, simple_oom_3 },
+ { "oom1.lsm.4", setup_delete_db, simple_oom_4 },
+ { "oom1.lsm.5", setup_populate_db2, simple_oom_5 },
+ { "oom1.lsm.6", setup_populate_db2, simple_oom_6 },
+ { "oom1.lsm.7", setup_populate_db2, simple_oom_7 },
+ { "oom1.lsm.8", setup_populate_db2, simple_oom_8 },
+
+ { "oom2.lsm.1", setup_delete_db, simple_oom2_1 },
+ };
+ int i;
+
+ for(i=0; i<ArraySize(aSimple); i++){
+ if( *pRc==0 && testCaseBegin(pRc, zPattern, "%s", aSimple[i].zName) ){
+ OomTest t;
+
+ if( aSimple[i].xSetup ){
+ aSimple[i].xSetup();
+ }
+
+ for(testOomStart(&t); testOomContinue(&t); testOomNext(&t)){
+ aSimple[i].xFunc(&t);
+ }
+
+ printf("(%d injections).", t.iNext-2);
+ testCaseFinish( (*pRc = testOomFinish(&t)) );
+ testMallocOom(tdb_lsm_env(), 0, 0, 0, 0);
+ }
+ }
+}
+
+void test_oom(
+ const char *zPattern, /* Run test cases that match this pattern */
+ int *pRc /* IN/OUT: Error code */
+){
+ do_test_oom1(zPattern, pRc);
+}