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