/ Check-in [41868d79]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Test cases to verify recovery after a crash. (CVS 1675)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:41868d79ac5b3c496c4d87ca6b4ee7c17ef38965
User & Date: danielk1977 2004-06-23 10:43:10
Context
2004-06-23
12:15
Add some tests for user functions that prefer various text encodings. (CVS 1676) check-in: db6bab57 user: danielk1977 tags: trunk
10:43
Test cases to verify recovery after a crash. (CVS 1675) check-in: 41868d79 user: danielk1977 tags: trunk
01:05
Handle corrupt journal file headers correctly. (CVS 1674) check-in: 46107da7 user: danielk1977 tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/os_test.c.

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

89
90
91

92
93











94
95
96

97
98
99
100
101
102
103
...
173
174
175
176
177
178
179


180
181
182
183
184
185
186
...
193
194
195
196
197
198
199
200

201
202
203
204
205
206

207
208
209
210
211
212
213
214
215
216
...
231
232
233
234
235
236
237
238

239
240
241
242



243
244
245
246
247
248
249


250
251
252
253
254
255
256
#undef sqlite3OsCheckReservedLock 

#define BLOCKSIZE 512
#define BLOCK_OFFSET(x) ((x) * BLOCKSIZE)


/*
** The crash-seed. Accessed via functions crashseed() and
** sqlite3SetCrashseed().
*/
static int crashseed_var = 0;

/*
** This function is used to set the value of the 'crash-seed' integer.
**
** If the crash-seed is 0, the default value, then whenever sqlite3OsSync()
** or sqlite3OsClose() is called, the write cache is written to disk before
** the os_unix.c Sync() or Close() function is called.
**
** If the crash-seed is non-zero, then it is used to determine a subset of
** the write-cache to actually write to disk before calling Sync() or
** Close() in os_unix.c. The actual subset of writes selected is not
** significant, except that it is constant for a given value of the
** crash-seed and cache contents. Before returning, exit(-1) is invoked.
*/
void sqlite3SetCrashseed(int seed){
  sqlite3OsEnterMutex();
  crashseed_var = seed;
  sqlite3OsLeaveMutex();
}

/*
** Retrieve the current value of the crash-seed.

*/
static int crashseed(){
  int i;

  sqlite3OsEnterMutex();
  i = crashseed_var;











  sqlite3OsLeaveMutex();
  return i;
}


static OsTestFile *pAllFiles = 0;

/*
** Initialise the os_test.c specific fields of pFile.
*/
static void initFile(OsFile *id, char const *zName){
................................................................................
      if( rc!=SQLITE_OK ) return rc;
    }
  }

  return SQLITE_OK;
}



/*
** Write the cache of pFile to disk. If crash is non-zero, randomly
** skip blocks when writing. The cache is deleted before returning.
*/
static int writeCache2(OsTestFile *pFile, int crash){
  int i;
  int nMax = pFile->nMaxWrite;
................................................................................
    if( p ){
      int skip = 0;
      if( crash ){
        char random;
        sqlite3Randomness(1, &random);
        if( random & 0x01 ){
          skip = 1;
/*

          printf("Not writing block %d of %s\n", i, pFile->zName);
*/
        }else{
/*
          printf("Writing block %d of %s\n", i, pFile->zName);
*/

        }
      }

      if( rc==SQLITE_OK ){
        rc = sqlite3RealSeek(&pFile->fd, BLOCK_OFFSET(i));
      }
      if( rc==SQLITE_OK && !skip ){
        int len = BLOCKSIZE;
        if( BLOCK_OFFSET(i+1)>nMax ){
          len = nMax-BLOCK_OFFSET(i);
................................................................................
  return rc;
}

/*
** Write the cache to disk.
*/
static int writeCache(OsTestFile *pFile){
  int cs = crashseed();

  if( cs==1 ){
    /* FIX ME: writeCache2() should be called on all open files here. */
    OsTestFile *pFile;
    for(pFile=pAllFiles; pFile; pFile=pFile->pNext){



      writeCache2(pFile, 1);
    }
    exit(-1);
  }else{
    if( cs>0 ) sqlite3SetCrashseed(cs-1);
    return writeCache2(pFile, 0);
  }


}

/*
** Close the file.
*/
int sqlite3OsClose(OsFile *id){
  if( !(*id) ) return SQLITE_OK;







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|




|
>

|
|
>

<
>
>
>
>
>
>
>
>
>
>
>

|

>







 







>
>







 







<
>
|
<

<
|
<
>


<







 







|
>
|
<
|
|
>
>
>
|
|
|
|
<
|
|
>
>







56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
...
208
209
210
211
212
213
214

215
216

217

218

219
220
221

222
223
224
225
226
227
228
...
243
244
245
246
247
248
249
250
251
252

253
254
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269
270
271
272
#undef sqlite3OsCheckReservedLock 

#define BLOCKSIZE 512
#define BLOCK_OFFSET(x) ((x) * BLOCKSIZE)


/*
** The following variables control when a simulated crash occurs.
**
** If iCrashDelay is non-zero, then zCrashFile contains (full path) name of
** a file that SQLite will call sqlite3OsSync() on. Each time this happens
** iCrashDelay is decremented. If iCrashDelay is zero after being
** decremented, a "crash" occurs during the sync() operation.
**
** In other words, a crash occurs the iCrashDelay'th time zCrashFile is
** synced.
*/
static int iCrashDelay = 0;
char zCrashFile[256];

/*
** Set the value of the two crash parameters.
*/
void sqlite3SetCrashParams(int iDelay, char const *zFile){
  sqlite3OsEnterMutex();
  assert( strlen(zFile)<256 );
  strcpy(zCrashFile, zFile);
  iCrashDelay = iDelay;
  sqlite3OsLeaveMutex();
}

/*
** File zPath is being sync()ed. Return non-zero if this should
** cause a crash.
*/
static int crashRequired(char const *zPath){
  int r;
  int n;
  sqlite3OsEnterMutex();

  n = strlen(zCrashFile);
  if( zCrashFile[n-1]=='*' ){
    n--;
  }else if( strlen(zPath)>n ){
    n = strlen(zPath);
  }
  r = ( 
    iCrashDelay>0 &&
    !strncmp(zPath, zCrashFile, n) &&
    --iCrashDelay==0
  )?1:0;
  sqlite3OsLeaveMutex();
  return r;
}


static OsTestFile *pAllFiles = 0;

/*
** Initialise the os_test.c specific fields of pFile.
*/
static void initFile(OsFile *id, char const *zName){
................................................................................
      if( rc!=SQLITE_OK ) return rc;
    }
  }

  return SQLITE_OK;
}

/* #define TRACE_WRITECACHE  */

/*
** Write the cache of pFile to disk. If crash is non-zero, randomly
** skip blocks when writing. The cache is deleted before returning.
*/
static int writeCache2(OsTestFile *pFile, int crash){
  int i;
  int nMax = pFile->nMaxWrite;
................................................................................
    if( p ){
      int skip = 0;
      if( crash ){
        char random;
        sqlite3Randomness(1, &random);
        if( random & 0x01 ){
          skip = 1;

#ifdef TRACE_WRITECACHE
printf("Not writing block %d of %s\n", i, pFile->zName); 

        }else{

printf("Writing block %d of %s\n", i, pFile->zName); 

#endif
        }
      }

      if( rc==SQLITE_OK ){
        rc = sqlite3RealSeek(&pFile->fd, BLOCK_OFFSET(i));
      }
      if( rc==SQLITE_OK && !skip ){
        int len = BLOCKSIZE;
        if( BLOCK_OFFSET(i+1)>nMax ){
          len = nMax-BLOCK_OFFSET(i);
................................................................................
  return rc;
}

/*
** Write the cache to disk.
*/
static int writeCache(OsTestFile *pFile){
  if( pFile->apBlk ){
    int c = crashRequired(pFile->zName);
    if( c ){

      OsTestFile *p;
#ifdef TRACE_WRITECACHE
      printf("Crash during sync of %s\n", pFile->zName);
#endif
      for(p=pAllFiles; p; p=p->pNext){
        writeCache2(p, 1);
      }
      exit(-1);
    }else{

      return writeCache2(pFile, 0);
    }
  }
  return SQLITE_OK;
}

/*
** Close the file.
*/
int sqlite3OsClose(OsFile *id){
  if( !(*id) ) return SQLITE_OK;

Changes to src/os_test.h.

30
31
32
33
34
35
36
37
38
39
  int nBlk;         /* Size of apBlock. */
  int nMaxWrite;    /* Largest offset written to. */
  char *zName;      /* File name */
  OsRealFile fd;
  OsTestFile *pNext;
};

void sqlite3SetCrashseed(int seed);

#endif /* _SQLITE_OS_UNIX_H_ */







|


30
31
32
33
34
35
36
37
38
39
  int nBlk;         /* Size of apBlock. */
  int nMaxWrite;    /* Largest offset written to. */
  char *zName;      /* File name */
  OsRealFile fd;
  OsTestFile *pNext;
};

void sqlite3SetCrashParams(int iDelay, char const *zFile);

#endif /* _SQLITE_OS_UNIX_H_ */

Changes to src/pager.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
475
476
477
478
479
480
481



482
483
484
485
486

487
488
489
490
491
492
493
...
504
505
506
507
508
509
510
511





512
513
514
515
516
517
518
...
836
837
838
839
840
841
842
843


844
845
846
847
848
849
850
....
1003
1004
1005
1006
1007
1008
1009


1010
1011
1012
1013
1014
1015
1016
....
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.136 2004/06/23 01:05:27 danielk1977 Exp $
*/
#include "os.h"         /* Must be first to enable large file support */
#include "sqliteInt.h"
#include "pager.h"
#include <assert.h>
#include <string.h>

................................................................................
    sqliteFree( pPager->aInJournal );
    pPager->aInJournal = 0;
    for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
      pPg->inJournal = 0;
      pPg->dirty = 0;
      pPg->needSync = 0;
    }



  }else{
    assert( pPager->dirtyCache==0 || pPager->useJournal==0 );
  }
  sqlite3OsUnlock(&pPager->fd, SHARED_LOCK);
  pPager->state = PAGER_SHARED;

  return SQLITE_OK;
}

/*
** Compute and return a checksum for the page of data.
**
** This is not a real checksum.  It is really just the sum of the 
................................................................................
**
** FIX ME:  Consider adding every 200th (or so) byte of the data to the
** checksum.  That way if a single page spans 3 or more disk sectors and
** only the middle sector is corrupt, we will still have a reasonable
** chance of failing the checksum and thus detecting the problem.
*/
static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){
  u32 cksum = pPager->cksumInit + pgno;





  return cksum;
}

/*
** Read a single page from the journal file opened on file descriptor
** jfd.  Playback this one page.
**
................................................................................
  if( rc!=SQLITE_OK || memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){
    goto end_playback;
  }

  /* (2) Read the number of pages stored in the journal.  */
  rc = read32bits(&pPager->jfd, (u32*)&nRec);
  if( rc ) goto end_playback;
  if( nRec==0xffffffff || useJournalSize ){


    nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager);
  }

  /* (3) Read the initial value for the sanity checksum */
  rc = read32bits(&pPager->jfd, &pPager->cksumInit);
  if( rc ) goto end_playback;

................................................................................
    if( pPager->noSync ) pPager->needSync = 0; 
  }else{
    pPager->noSync = 1;
    mxPage = -mxPage;
  }
  if( mxPage>10 ){
    pPager->mxPage = mxPage;


  }
}

/*
** Adjust the robustness of the database to damage due to OS crashes
** or power failures by changing the number of syncs()s when writing
** the rollback journal.  There are three levels:
................................................................................
    ** if there have been no changes to the database file. */
    assert( pPager->needSync==0 );
    rc = pager_unwritelock(pPager);
    pPager->dbSize = -1;
    return rc;
  }
  assert( pPager->journalOpen );
#if 0
  rc = syncJournal(pPager, 0);
  if( rc!=SQLITE_OK ){
    goto commit_abort;
  }
  pPg = pager_get_all_dirty_pages(pPager);
  if( pPg ){
    rc = pager_write_pagelist(pPg);
    if( rc || (!pPager->noSync && sqlite3OsSync(&pPager->fd)!=SQLITE_OK) ){
      goto commit_abort;
    }
  }
#endif
  rc = sqlite3pager_sync(pPager, 0);
  if( rc!=SQLITE_OK ){
    goto commit_abort;
  }
  rc = pager_unwritelock(pPager);
  pPager->dbSize = -1;
  return rc;







|







 







>
>
>





>







 







|
>
>
>
>
>







 







|
>
>







 







>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
...
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
...
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
....
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
....
2418
2419
2420
2421
2422
2423
2424













2425
2426
2427
2428
2429
2430
2431
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.137 2004/06/23 10:43:10 danielk1977 Exp $
*/
#include "os.h"         /* Must be first to enable large file support */
#include "sqliteInt.h"
#include "pager.h"
#include <assert.h>
#include <string.h>

................................................................................
    sqliteFree( pPager->aInJournal );
    pPager->aInJournal = 0;
    for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
      pPg->inJournal = 0;
      pPg->dirty = 0;
      pPg->needSync = 0;
    }
    pPager->dirtyCache = 0;
    pPager->nMaster = 0;
    pPager->nRec = 0;
  }else{
    assert( pPager->dirtyCache==0 || pPager->useJournal==0 );
  }
  sqlite3OsUnlock(&pPager->fd, SHARED_LOCK);
  pPager->state = PAGER_SHARED;
  pPager->origDbSize = 0;
  return SQLITE_OK;
}

/*
** Compute and return a checksum for the page of data.
**
** This is not a real checksum.  It is really just the sum of the 
................................................................................
**
** FIX ME:  Consider adding every 200th (or so) byte of the data to the
** checksum.  That way if a single page spans 3 or more disk sectors and
** only the middle sector is corrupt, we will still have a reasonable
** chance of failing the checksum and thus detecting the problem.
*/
static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){
  u32 cksum = pPager->cksumInit;
  int i = pPager->pageSize-200;
  while( i>0 ){
    cksum += aData[i];
    i -= 200;
  }
  return cksum;
}

/*
** Read a single page from the journal file opened on file descriptor
** jfd.  Playback this one page.
**
................................................................................
  if( rc!=SQLITE_OK || memcmp(aMagic, aJournalMagic, sizeof(aMagic))!=0 ){
    goto end_playback;
  }

  /* (2) Read the number of pages stored in the journal.  */
  rc = read32bits(&pPager->jfd, (u32*)&nRec);
  if( rc ) goto end_playback;
  if( nRec==0xffffffff || useJournalSize || 
      nRec>(szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager)
  ){
    nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager);
  }

  /* (3) Read the initial value for the sanity checksum */
  rc = read32bits(&pPager->jfd, &pPager->cksumInit);
  if( rc ) goto end_playback;

................................................................................
    if( pPager->noSync ) pPager->needSync = 0; 
  }else{
    pPager->noSync = 1;
    mxPage = -mxPage;
  }
  if( mxPage>10 ){
    pPager->mxPage = mxPage;
  }else{
    pPager->mxPage = 10;
  }
}

/*
** Adjust the robustness of the database to damage due to OS crashes
** or power failures by changing the number of syncs()s when writing
** the rollback journal.  There are three levels:
................................................................................
    ** if there have been no changes to the database file. */
    assert( pPager->needSync==0 );
    rc = pager_unwritelock(pPager);
    pPager->dbSize = -1;
    return rc;
  }
  assert( pPager->journalOpen );













  rc = sqlite3pager_sync(pPager, 0);
  if( rc!=SQLITE_OK ){
    goto commit_abort;
  }
  rc = pager_unwritelock(pPager);
  pPager->dbSize = -1;
  return rc;

Changes to src/test1.c.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
....
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Code for testing the printf() interface to SQLite.  This code
** is not included in the SQLite library.  It is used for automated
** testing of the SQLite library.
**
** $Id: test1.c,v 1.83 2004/06/22 13:12:52 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
#include "os.h"
#include <stdlib.h>
#include <string.h>

................................................................................

bad_args:
  Tcl_AppendResult(interp, "wrong # args: should be \"",
      Tcl_GetStringFromObj(objv[0], 0), " <DB> <utf8> <utf16le> <utf16be>", 0);
  return TCL_ERROR;
}

static int sqlite3_crashseed(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
#ifdef OS_TEST
  int seed;
  if( objc!=2 ) goto bad_args;
  if( Tcl_GetIntFromObj(interp, objv[1], &seed) ) return TCL_ERROR;
  sqlite3SetCrashseed(seed);
#endif
  return TCL_OK;

#ifdef OS_TEST
bad_args:
  Tcl_AppendResult(interp, "wrong # args: should be \"",
      Tcl_GetStringFromObj(objv[0], 0), "<seed>", 0);
  return TCL_ERROR;
#endif
}


/*
** Usage:    breakpoint
................................................................................

     /* Functions from os.h */
     { "sqlite3OsOpenReadWrite",test_sqlite3OsOpenReadWrite, 0 },
     { "sqlite3OsClose",        test_sqlite3OsClose, 0 },
     { "sqlite3OsLock",         test_sqlite3OsLock, 0 },
     { "sqlite3OsUnlock",       test_sqlite3OsUnlock, 0 },
     { "add_test_collate",      test_collate, 0         },
     { "sqlite3_crashseed",     sqlite3_crashseed, 0         },

  };
  int i;
  extern int sqlite3_os_trace;

  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);







|







 







|






|
|
|
|






|







 







|







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
....
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Code for testing the printf() interface to SQLite.  This code
** is not included in the SQLite library.  It is used for automated
** testing of the SQLite library.
**
** $Id: test1.c,v 1.84 2004/06/23 10:43:11 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
#include "os.h"
#include <stdlib.h>
#include <string.h>

................................................................................

bad_args:
  Tcl_AppendResult(interp, "wrong # args: should be \"",
      Tcl_GetStringFromObj(objv[0], 0), " <DB> <utf8> <utf16le> <utf16be>", 0);
  return TCL_ERROR;
}

static int sqlite3_crashparams(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
#ifdef OS_TEST
  int delay;
  if( objc!=3 ) goto bad_args;
  if( Tcl_GetIntFromObj(interp, objv[1], &delay) ) return TCL_ERROR;
  sqlite3SetCrashParams(delay, Tcl_GetString(objv[2]));
#endif
  return TCL_OK;

#ifdef OS_TEST
bad_args:
  Tcl_AppendResult(interp, "wrong # args: should be \"",
      Tcl_GetStringFromObj(objv[0], 0), "<delay> <filename>", 0);
  return TCL_ERROR;
#endif
}


/*
** Usage:    breakpoint
................................................................................

     /* Functions from os.h */
     { "sqlite3OsOpenReadWrite",test_sqlite3OsOpenReadWrite, 0 },
     { "sqlite3OsClose",        test_sqlite3OsClose, 0 },
     { "sqlite3OsLock",         test_sqlite3OsLock, 0 },
     { "sqlite3OsUnlock",       test_sqlite3OsUnlock, 0 },
     { "add_test_collate",      test_collate, 0         },
     { "sqlite3_crashparams",     sqlite3_crashparams, 0         },

  };
  int i;
  extern int sqlite3_os_trace;

  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);

Changes to test/crash.test.

6
7
8
9
10
11
12
13
14
15
16



17
18
19
20
21
22
23







24

25
26
27
28

29
30
31
32
33

34


35
36















37
38
39
40
41
42
43
44
45





46
47
48
49
50
51
52


53
54
55
56
57
58
59
60
61



62
















63
64
65





66
67
68
































69
70
71



72
73




74
75

















76
77




78
79











80
81




82
83





























































































#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
# $Id: crash.test,v 1.2 2004/06/23 01:05:27 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl




# This proc execs a seperate process that crashes midway through executing
# the SQL script $sql on database test.db.
#
# Argument $crashdelay indicates the number of file closes or syncs to wait
# before crashing. When a crash occurs a random subset of unsynced writes
# are written into any open files.







proc crashsql {crashdelay sql} {


  set f [open crash.tcl w]
  puts $f "sqlite3_crashseed $crashdelay"
  puts $f "sqlite3 db test.db"

  puts $f "db eval {"
  puts $f   "$sql"
  puts $f "}"
  close $f


  exec [file join . crashtest] crash.tcl


}
















# Simple crash test:
#
# crash-1.1: Create a database with a table with two rows.
# crash-1.2: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
#            journal-sync
# crash-1.3: Ensure the database is in the same state as after crash-1.1.
# crash-1.4: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
#            database-sync
# crash-1.5: Ensure the database is in the same state as after crash-1.1.





#
do_test crash-1.1 {
  execsql {
    CREATE TABLE abc(a, b, c);
    INSERT INTO abc VALUES(1, 2, 3);
    INSERT INTO abc VALUES(4, 5, 6);
  }


} {}
do_test crash-1.2 {
  catch {
    crashsql 1 {
      DELETE FROM abc WHERE a = 1;
    }
  } msg
  set msg
} {child process exited abnormally}



do_test crash-1.3 {
















  catchsql {
    SELECT * FROM abc;
  }





} {0 {1 2 3 4 5 6}}
do_test crash-1.4 {
  catch {
































   crashsql 1 {
      DELETE FROM abc WHERE a = 1;
    }



  } msg
  set msg




} {child process exited abnormally}
do_test crash-1.5 {

















  catchsql {
    SELECT * FROM abc;




  }
} {0 {1 2 3 4 5 6}}












finish_test










































































































|



>
>
>




|
|
|
>
>
>
>
>
>
>
|
>


|

>





>
|
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|


|

>
>
>
>
>







>
>
|

<
|
|
|
<
<
|
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
>
>
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
>
>
|
<
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>

<
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

92
93
94


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
204
205
206
207

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
# $Id: crash.test,v 1.3 2004/06/23 10:43:15 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

set repeats 100
# set repeats 5

# This proc execs a seperate process that crashes midway through executing
# the SQL script $sql on database test.db.
#
# The crash occurs during a sync() of file $crashfile. When the crash
# occurs a random subset of all unsynced writes made by the process are
# written into the files on disk. Argument $crashdelay indicates the
# number of file syncs to wait before crashing.
#
# The return value is a list of two elements. The first element is a
# boolean, indicating whether or not the process actually crashed or
# reported some other error. The second element in the returned list is the
# error message. This is "child process exited abnormally" if the crash
# occured.
proc crashsql {crashdelay crashfile sql} {
  set cfile [file join [pwd] $crashfile]

  set f [open crash.tcl w]
  puts $f "sqlite3_crashparams $crashdelay $cfile"
  puts $f "sqlite3 db test.db"
  puts $f "db eval {pragma full_synchronous = 1}"
  puts $f "db eval {"
  puts $f   "$sql"
  puts $f "}"
  close $f

  set r [catch {
    exec [file join . crashtest] crash.tcl
  } msg]
  lappend r $msg
}

# The following procedure computes a "signature" for table "abc".  If
# abc changes in any way, the signature should change.  
proc signature {} {
  return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc}]
}
proc signature2 {} {
  return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc2}]
}

# Use a small pager-cache for these tests.
do_test crash-0.1 {
  execsql { pragma default_cache_size = 10 }
} {}

#--------------------------------------------------------------------------
# Simple crash test:
#
# crash-1.1: Create a database with a table with two rows.
# crash-1.2: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
#            the first journal-sync.
# crash-1.3: Ensure the database is in the same state as after crash-1.1.
# crash-1.4: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
#            the first database-sync.
# crash-1.5: Ensure the database is in the same state as after crash-1.1.
#
# Tests 1.6 through 1.9 are the same as 1.2 through 1.5, except the crash
# is requested on the second sync of each file. This doesn't happen in
# such a small test case, so these tests are just to verify that the
# test infrastructure operates as expected.
#
do_test crash-1.1 {
  execsql {
    CREATE TABLE abc(a, b, c);
    INSERT INTO abc VALUES(1, 2, 3);
    INSERT INTO abc VALUES(4, 5, 6);
  }
  set ::sig [signature]
  expr 0
} {0}
do_test crash-1.2 {

  crashsql 1 test.db-journal {
    DELETE FROM abc WHERE a = 1;
  }


} {1 {child process exited abnormally}}

# exit

do_test crash-1.3 {
  signature
} $::sig
do_test crash-1.4 {
  crashsql 1 test.db {
    DELETE FROM abc WHERE a = 1;
  }
} {1 {child process exited abnormally}}
do_test crash-1.5 {
  signature
} $::sig
do_test crash-1.6 {
  crashsql 2 test.db-journal {
    DELETE FROM abc WHERE a = 1;
  }
} {0 {}}
do_test crash-1.7 {
  catchsql {
    SELECT * FROM abc;
  }
} {0 {4 5 6}}
do_test crash-1.8 {
  crashsql 2 test.db {
    DELETE FROM abc WHERE a = 4;
  }
} {0 {}}
do_test crash-1.9 {
  catchsql {
    SELECT * FROM abc;
  }
} {0 {}}

#--------------------------------------------------------------------------
# The following tests test recovery when both the database file and the the
# journal file contain corrupt data. This can happen after pages are
# written to the database file before a transaction is committed due to
# cache-pressure.
#
# crash-2.1: Insert 18 pages of data into the database.
# crash-2.2: Check the database file size looks ok.
# crash-2.3: Delete 15 or so pages (with a 10 page page-cache), then crash.
# crash-2.4: Ensure the database is in the same state as after crash-2.1.
#
# Test cases crash-2.5 and crash-2.6 check that the database is OK if the 
# crash occurs during the main database file sync. But this isn't really
# different from the crash-1.* cases.
#
do_test crash-2.1 {
  execsql { BEGIN }
  for {set n 0} {$n < 1000} {incr n} {
    execsql "INSERT INTO abc VALUES($n, [expr 2*$n], [expr 3*$n])"
  }
  execsql { COMMIT }
  set ::sig [signature]
  execsql { SELECT sum(a), sum(b), sum(c) from abc }
} {499500 999000 1498500}
do_test crash-2.2 {
  expr [file size test.db] / 1024
} {19}
do_test crash-2.3 {
  crashsql 2 test.db-journal {
    DELETE FROM abc WHERE a < 800;
  }
} {1 {child process exited abnormally}}
do_test crash-2.4 {
  signature
} $sig

do_test crash-2.5 {
  crashsql 1 test.db {
    DELETE FROM abc WHERE a<800;
  }
} {1 {child process exited abnormally}}
do_test crash-2.6 {
  signature
} $sig

#--------------------------------------------------------------------------
# The crash-3.* test cases are essentially the same test as test case
# crash-2.*, but with a more complicated data set. 
#
# The test is repeated a few times with different seeds for the random
# number generator in the crashing executable. Because there is no way to
# seed the random number generator directly, some SQL is added to the test
# case to 'use up' a different quantity random numbers before the test SQL
# is executed.
#

# Make sure the file is much bigger than the pager-cache (10 pages). This
# ensures that cache-spills happen regularly.
do_test crash-3.0 {
  execsql {
    INSERT INTO abc SELECT * FROM abc;
    INSERT INTO abc SELECT * FROM abc;
    INSERT INTO abc SELECT * FROM abc;
    INSERT INTO abc SELECT * FROM abc;
    INSERT INTO abc SELECT * FROM abc;
  }

  expr [file size test.db] / 1024
} {554}
for {set i 1} {$i < $repeats} {incr i} {
  set sig [signature]
  do_test crash-3.$i.1 {
     crashsql [expr $i%5 + 1] test.db-journal "
       BEGIN;
       SELECT random() FROM abc LIMIT $i;
       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc WHERE random()%10!=0;
       COMMIT;
     "

  } {1 {child process exited abnormally}}
  do_test crash-3.$i.2 {
    signature
  } $sig
} 

#--------------------------------------------------------------------------
# The following test cases - crash-4.* - test the correct recovery of the
# database when a crash occurs during a multi-file transaction.
#
# crash-4.1.*: Test recovery when crash occurs during sync() of the 
#              main database journal file.
# crash-4.2.*: Test recovery when crash occurs during sync() of an 
#              attached database journal file.
# crash-4.3.*: Test recovery when crash occurs during sync() of the master
#              journal file. 
#
do_test crash-4.0 {
  file delete -force test2.db
  file delete -force test2.db-journal
  sqlite3 db2 test2.db
  execsql {pragma default_cache_size = 10} db2
  db2 close
  execsql {
    ATTACH 'test2.db' AS aux;
    CREATE TABLE aux.abc2 AS SELECT 2*a as a, 2*b as b, 2*c as c FROM abc;
  }
  expr [file size test2.db] / 1024
} {559}

for {set i 1} {$i < $repeats} {incr i} {
  set sig [signature]
  set sig2 [signature2]
  do_test crash-4.1.$i.1 {
     crashsql [expr $i%5 + 1] test.db-journal "
       ATTACH 'test2.db' AS aux;
       BEGIN;
       SELECT random() FROM abc LIMIT $i;
       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc WHERE random()%10!=0;
       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc2 WHERE random()%10!=0;
       COMMIT;
     "
  } {1 {child process exited abnormally}}
  do_test crash-4.1.$i.2 {
    signature
  } $sig
  do_test crash-4.1.$i.3 {
    signature2
  } $sig2
} 
for {set i 1} {$i < $repeats} {incr i} {
  set sig [signature]
  set sig2 [signature2]
  do_test crash-4.2.$i.1 {
     crashsql [expr $i%5 + 1] test2.db-journal "
       ATTACH 'test2.db' AS aux;
       BEGIN;
       SELECT random() FROM abc LIMIT $i;
       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc WHERE random()%10!=0;
       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc2 WHERE random()%10!=0;
       COMMIT;
     "
  } {1 {child process exited abnormally}}
  do_test crash-4.2.$i.2 {
    signature
  } $sig
  do_test crash-4.2.$i.3 {
    signature2
  } $sig2
} 
for {set i 1} {$i < 5} {incr i} {
  set sig [signature]
  set sig2 [signature2]
  do_test crash-4.3.$i.1 {
     crashsql 1 test.db-mj* "
       ATTACH 'test2.db' AS aux;
       BEGIN;
       SELECT random() FROM abc LIMIT $i;
       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc WHERE random()%10!=0;
       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
       DELETE FROM abc2 WHERE random()%10!=0;
       COMMIT;
     "
  } {1 {child process exited abnormally}}
  do_test crash-4.3.$i.2 {
    signature
  } $sig
  do_test crash-4.3.$i.3 {
    signature2
  } $sig2
}

finish_test