1
2 /*
3 ** This file contains tests related to recovery following application
4 ** and system crashes (power failures) while writing to the database.
5 */
6
7 #include "lsmtest.h"
8
9 /*
10 ** Structure used by testCksumDatabase() to accumulate checksum values in.
11 */
12 typedef struct Cksum Cksum;
13 struct Cksum {
14 int nRow;
15 int cksum1;
16 int cksum2;
17 };
18
19 /*
20 ** tdb_scan() callback used by testCksumDatabase()
21 */
scanCksumDb(void * pCtx,void * pKey,int nKey,void * pVal,int nVal)22 static void scanCksumDb(
23 void *pCtx,
24 void *pKey, int nKey,
25 void *pVal, int nVal
26 ){
27 Cksum *p = (Cksum *)pCtx;
28 int i;
29
30 p->nRow++;
31 for(i=0; i<nKey; i++){
32 p->cksum1 += ((u8 *)pKey)[i];
33 p->cksum2 += p->cksum1;
34 }
35 for(i=0; i<nVal; i++){
36 p->cksum1 += ((u8 *)pVal)[i];
37 p->cksum2 += p->cksum1;
38 }
39 }
40
41 /*
42 ** tdb_scan() callback used by testCountDatabase()
43 */
scanCountDb(void * pCtx,void * pKey,int nKey,void * pVal,int nVal)44 static void scanCountDb(
45 void *pCtx,
46 void *pKey, int nKey,
47 void *pVal, int nVal
48 ){
49 Cksum *p = (Cksum *)pCtx;
50 p->nRow++;
51
52 unused_parameter(pKey);
53 unused_parameter(nKey);
54 unused_parameter(pVal);
55 unused_parameter(nVal);
56 }
57
58
59 /*
60 ** Iterate through the entire contents of database pDb. Write a checksum
61 ** string based on the db contents into buffer zOut before returning. A
62 ** checksum string is at most 29 (TEST_CKSUM_BYTES) bytes in size:
63 **
64 ** * 32-bit integer (10 bytes)
65 ** * 1 space (1 byte)
66 ** * 32-bit hex (8 bytes)
67 ** * 1 space (1 byte)
68 ** * 32-bit hex (8 bytes)
69 ** * nul-terminator (1 byte)
70 **
71 ** The number of entries in the database is returned.
72 */
testCksumDatabase(TestDb * pDb,char * zOut)73 int testCksumDatabase(
74 TestDb *pDb, /* Database handle */
75 char *zOut /* Buffer to write checksum to */
76 ){
77 Cksum cksum;
78 memset(&cksum, 0, sizeof(Cksum));
79 tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCksumDb);
80 sprintf(zOut, "%d %x %x",
81 cksum.nRow, (u32)cksum.cksum1, (u32)cksum.cksum2
82 );
83 assert( strlen(zOut)<TEST_CKSUM_BYTES );
84 return cksum.nRow;
85 }
86
testCountDatabase(TestDb * pDb)87 int testCountDatabase(TestDb *pDb){
88 Cksum cksum;
89 memset(&cksum, 0, sizeof(Cksum));
90 tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCountDb);
91 return cksum.nRow;
92 }
93
94 /*
95 ** This function is a no-op if *pRc is not 0 when it is called.
96 **
97 ** Otherwise, the two nul-terminated strings z1 and z1 are compared. If
98 ** they are the same, the function returns without doing anything. Otherwise,
99 ** an error message is printed, *pRc is set to 1 and the test_failed()
100 ** function called.
101 */
testCompareStr(const char * z1,const char * z2,int * pRc)102 void testCompareStr(const char *z1, const char *z2, int *pRc){
103 if( *pRc==0 ){
104 if( strcmp(z1, z2) ){
105 testPrintError("testCompareStr: \"%s\" != \"%s\"\n", z1, z2);
106 *pRc = 1;
107 test_failed();
108 }
109 }
110 }
111
112 /*
113 ** This function is a no-op if *pRc is not 0 when it is called.
114 **
115 ** Otherwise, the two integers i1 and i2 are compared. If they are equal,
116 ** the function returns without doing anything. Otherwise, an error message
117 ** is printed, *pRc is set to 1 and the test_failed() function called.
118 */
testCompareInt(int i1,int i2,int * pRc)119 void testCompareInt(int i1, int i2, int *pRc){
120 if( *pRc==0 && i1!=i2 ){
121 testPrintError("testCompareInt: %d != %d\n", i1, i2);
122 *pRc = 1;
123 test_failed();
124 }
125 }
126
testCaseStart(int * pRc,char * zFmt,...)127 void testCaseStart(int *pRc, char *zFmt, ...){
128 va_list ap;
129 va_start(ap, zFmt);
130 vprintf(zFmt, ap);
131 printf(" ...");
132 va_end(ap);
133 *pRc = 0;
134 fflush(stdout);
135 }
136
137 /*
138 ** This function is a no-op if *pRc is non-zero when it is called. Zero
139 ** is returned in this case.
140 **
141 ** Otherwise, the zFmt (a printf style format string) and following arguments
142 ** are used to create a test case name. If zPattern is NULL or a glob pattern
143 ** that matches the test case name, 1 is returned and the test case started.
144 ** Otherwise, zero is returned and the test case does not start.
145 */
testCaseBegin(int * pRc,const char * zPattern,const char * zFmt,...)146 int testCaseBegin(int *pRc, const char *zPattern, const char *zFmt, ...){
147 int res = 0;
148 if( *pRc==0 ){
149 char *zTest;
150 va_list ap;
151
152 va_start(ap, zFmt);
153 zTest = testMallocVPrintf(zFmt, ap);
154 va_end(ap);
155 if( zPattern==0 || testGlobMatch(zPattern, zTest) ){
156 printf("%-50s ...", zTest);
157 res = 1;
158 }
159 testFree(zTest);
160 fflush(stdout);
161 }
162
163 return res;
164 }
165
testCaseFinish(int rc)166 void testCaseFinish(int rc){
167 if( rc==0 ){
168 printf("Ok\n");
169 }else{
170 printf("FAILED\n");
171 }
172 fflush(stdout);
173 }
174
testCaseSkip()175 void testCaseSkip(){
176 printf("Skipped\n");
177 }
178
testSetupSavedLsmdb(const char * zCfg,const char * zFile,Datasource * pData,int nRow,int * pRc)179 void testSetupSavedLsmdb(
180 const char *zCfg,
181 const char *zFile,
182 Datasource *pData,
183 int nRow,
184 int *pRc
185 ){
186 if( *pRc==0 ){
187 int rc;
188 TestDb *pDb;
189 rc = tdb_lsm_open(zCfg, zFile, 1, &pDb);
190 if( rc==0 ){
191 testWriteDatasourceRange(pDb, pData, 0, nRow, &rc);
192 testClose(&pDb);
193 if( rc==0 ) testSaveDb(zFile, "log");
194 }
195 *pRc = rc;
196 }
197 }
198
199 /*
200 ** This function is a no-op if *pRc is non-zero when it is called.
201 **
202 ** Open the LSM database identified by zFile and compute its checksum
203 ** (a string, as returned by testCksumDatabase()). If the checksum is
204 ** identical to zExpect1 or, if it is not NULL, zExpect2, the test passes.
205 ** Otherwise, print an error message and set *pRc to 1.
206 */
testCompareCksumLsmdb(const char * zFile,int bCompress,const char * zExpect1,const char * zExpect2,int * pRc)207 static void testCompareCksumLsmdb(
208 const char *zFile, /* Path to LSM database */
209 int bCompress, /* True if db is compressed */
210 const char *zExpect1, /* Expected checksum 1 */
211 const char *zExpect2, /* Expected checksum 2 (or NULL) */
212 int *pRc /* IN/OUT: Test case error code */
213 ){
214 if( *pRc==0 ){
215 char zCksum[TEST_CKSUM_BYTES];
216 TestDb *pDb;
217
218 *pRc = tdb_lsm_open((bCompress?"compression=1 mmap=0":""), zFile, 0, &pDb);
219 testCksumDatabase(pDb, zCksum);
220 testClose(&pDb);
221
222 if( *pRc==0 ){
223 int r1 = 0;
224 int r2 = -1;
225
226 r1 = strcmp(zCksum, zExpect1);
227 if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
228 if( r1 && r2 ){
229 if( zExpect2 ){
230 testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
231 zCksum, zExpect1, zExpect2
232 );
233 }else{
234 testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
235 zCksum, zExpect1
236 );
237 }
238 *pRc = 1;
239 test_failed();
240 }
241 }
242 }
243 }
244
245 #if 0 /* not used */
246 static void testCompareCksumBtdb(
247 const char *zFile, /* Path to LSM database */
248 const char *zExpect1, /* Expected checksum 1 */
249 const char *zExpect2, /* Expected checksum 2 (or NULL) */
250 int *pRc /* IN/OUT: Test case error code */
251 ){
252 if( *pRc==0 ){
253 char zCksum[TEST_CKSUM_BYTES];
254 TestDb *pDb;
255
256 *pRc = tdb_open("bt", zFile, 0, &pDb);
257 testCksumDatabase(pDb, zCksum);
258 testClose(&pDb);
259
260 if( *pRc==0 ){
261 int r1 = 0;
262 int r2 = -1;
263
264 r1 = strcmp(zCksum, zExpect1);
265 if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
266 if( r1 && r2 ){
267 if( zExpect2 ){
268 testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
269 zCksum, zExpect1, zExpect2
270 );
271 }else{
272 testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
273 zCksum, zExpect1
274 );
275 }
276 *pRc = 1;
277 test_failed();
278 }
279 }
280 }
281 }
282 #endif /* not used */
283
284 /* Above this point are reusable test routines. Not clear that they
285 ** should really be in this file.
286 *************************************************************************/
287
288 /*
289 ** This test verifies that if a system crash occurs while doing merge work
290 ** on the db, no data is lost.
291 */
crash_test1(int bCompress,int * pRc)292 static void crash_test1(int bCompress, int *pRc){
293 const char *DBNAME = "testdb.lsm";
294 const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 200, 200};
295
296 const int nRow = 5000; /* Database size */
297 const int nIter = 200; /* Number of test iterations */
298 const int nWork = 20; /* Maximum lsm_work() calls per iteration */
299 const int nPage = 15; /* Pages per lsm_work call */
300
301 int i;
302 int iDot = 0;
303 Datasource *pData;
304 CksumDb *pCksumDb;
305 TestDb *pDb;
306 char *zCfg;
307
308 const char *azConfig[2] = {
309 "page_size=1024 block_size=65536 autoflush=16384 safety=2 mmap=0",
310 "page_size=1024 block_size=65536 autoflush=16384 safety=2 "
311 " compression=1 mmap=0"
312 };
313 assert( bCompress==0 || bCompress==1 );
314
315 /* Allocate datasource. And calculate the expected checksums. */
316 pData = testDatasourceNew(&defn);
317 pCksumDb = testCksumArrayNew(pData, nRow, nRow, 1);
318
319 /* Setup and save the initial database. */
320
321 zCfg = testMallocPrintf("%s automerge=7", azConfig[bCompress]);
322 testSetupSavedLsmdb(zCfg, DBNAME, pData, 5000, pRc);
323 testFree(zCfg);
324
325 for(i=0; i<nIter && *pRc==0; i++){
326 int iWork;
327 int testrc = 0;
328
329 testCaseProgress(i, nIter, testCaseNDot(), &iDot);
330
331 /* Restore and open the database. */
332 testRestoreDb(DBNAME, "log");
333 testrc = tdb_lsm_open(azConfig[bCompress], DBNAME, 0, &pDb);
334 assert( testrc==0 );
335
336 /* Call lsm_work() on the db */
337 tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nWork*2)));
338 for(iWork=0; testrc==0 && iWork<nWork; iWork++){
339 int nWrite = 0;
340 lsm_db *db = tdb_lsm(pDb);
341 testrc = lsm_work(db, 0, nPage, &nWrite);
342 /* assert( testrc!=0 || nWrite>0 ); */
343 if( testrc==0 ) testrc = lsm_checkpoint(db, 0);
344 }
345 tdb_close(pDb);
346
347 /* Check that the database content is still correct */
348 testCompareCksumLsmdb(DBNAME,
349 bCompress, testCksumArrayGet(pCksumDb, nRow), 0, pRc);
350 }
351
352 testCksumArrayFree(pCksumDb);
353 testDatasourceFree(pData);
354 }
355
356 /*
357 ** This test verifies that if a system crash occurs while committing a
358 ** transaction to the log file, no earlier transactions are lost or damaged.
359 */
crash_test2(int bCompress,int * pRc)360 static void crash_test2(int bCompress, int *pRc){
361 const char *DBNAME = "testdb.lsm";
362 const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
363
364 const int nIter = 200;
365 const int nInsert = 20;
366
367 int i;
368 int iDot = 0;
369 Datasource *pData;
370 CksumDb *pCksumDb;
371 TestDb *pDb;
372
373 /* Allocate datasource. And calculate the expected checksums. */
374 pData = testDatasourceNew(&defn);
375 pCksumDb = testCksumArrayNew(pData, 100, 100+nInsert, 1);
376
377 /* Setup and save the initial database. */
378 testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
379
380 for(i=0; i<nIter && *pRc==0; i++){
381 int iIns;
382 int testrc = 0;
383
384 testCaseProgress(i, nIter, testCaseNDot(), &iDot);
385
386 /* Restore and open the database. */
387 testRestoreDb(DBNAME, "log");
388 testrc = tdb_lsm_open("safety=2", DBNAME, 0, &pDb);
389 assert( testrc==0 );
390
391 /* Insert nInsert records into the database. Crash midway through. */
392 tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nInsert+2)));
393 for(iIns=0; iIns<nInsert; iIns++){
394 void *pKey; int nKey;
395 void *pVal; int nVal;
396
397 testDatasourceEntry(pData, 100+iIns, &pKey, &nKey, &pVal, &nVal);
398 testrc = tdb_write(pDb, pKey, nKey, pVal, nVal);
399 if( testrc ) break;
400 }
401 tdb_close(pDb);
402
403 /* Check that no data was lost when the system crashed. */
404 testCompareCksumLsmdb(DBNAME, bCompress,
405 testCksumArrayGet(pCksumDb, 100 + iIns),
406 testCksumArrayGet(pCksumDb, 100 + iIns + 1),
407 pRc
408 );
409 }
410
411 testDatasourceFree(pData);
412 testCksumArrayFree(pCksumDb);
413 }
414
415
416 /*
417 ** This test verifies that if a system crash occurs when checkpointing
418 ** the database, data is not lost (assuming that any writes not synced
419 ** to the db have been synced into the log file).
420 */
crash_test3(int bCompress,int * pRc)421 static void crash_test3(int bCompress, int *pRc){
422 const char *DBNAME = "testdb.lsm";
423 const int nIter = 100;
424 const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
425
426 int i;
427 int iDot = 0;
428 Datasource *pData;
429 CksumDb *pCksumDb;
430 TestDb *pDb;
431
432 /* Allocate datasource. And calculate the expected checksums. */
433 pData = testDatasourceNew(&defn);
434 pCksumDb = testCksumArrayNew(pData, 110, 150, 10);
435
436 /* Setup and save the initial database. */
437 testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
438
439 for(i=0; i<nIter && *pRc==0; i++){
440 int iOpen;
441 testCaseProgress(i, nIter, testCaseNDot(), &iDot);
442 testRestoreDb(DBNAME, "log");
443
444 for(iOpen=0; iOpen<5; iOpen++){
445 /* Open the database. Insert 10 more records. */
446 pDb = testOpen("lsm", 0, pRc);
447 testWriteDatasourceRange(pDb, pData, 100+iOpen*10, 10, pRc);
448
449 /* Schedule a crash simulation then close the db. */
450 tdb_lsm_prepare_sync_crash(pDb, 1 + (i%2));
451 tdb_close(pDb);
452
453 /* Open the database and check that the crash did not cause any
454 ** data loss. */
455 testCompareCksumLsmdb(DBNAME, bCompress,
456 testCksumArrayGet(pCksumDb, 110 + iOpen*10), 0,
457 pRc
458 );
459 }
460 }
461
462 testDatasourceFree(pData);
463 testCksumArrayFree(pCksumDb);
464 }
465
do_crash_test(const char * zPattern,int * pRc)466 void do_crash_test(const char *zPattern, int *pRc){
467 struct Test {
468 const char *zTest;
469 void (*x)(int, int *);
470 int bCompress;
471 } aTest [] = {
472 { "crash.lsm.1", crash_test1, 0 },
473 #ifdef HAVE_ZLIB
474 { "crash.lsm_zip.1", crash_test1, 1 },
475 #endif
476 { "crash.lsm.2", crash_test2, 0 },
477 { "crash.lsm.3", crash_test3, 0 },
478 };
479 int i;
480
481 for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
482 struct Test *p = &aTest[i];
483 if( testCaseBegin(pRc, zPattern, "%s", p->zTest) ){
484 p->x(p->bCompress, pRc);
485 testCaseFinish(*pRc);
486 }
487 }
488 }
489