0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2024-12-16 11:28:45 +00:00
2023-10-16 13:58:16 +02:00

662 lines
16 KiB
C

#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);
}