]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Test cases to verify recovery after a crash. (CVS 1675)
authordanielk1977 <danielk1977@noemail.net>
Wed, 23 Jun 2004 10:43:10 +0000 (10:43 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Wed, 23 Jun 2004 10:43:10 +0000 (10:43 +0000)
FossilOrigin-Name: 41868d79ac5b3c496c4d87ca6b4ee7c17ef38965

manifest
manifest.uuid
src/os_test.c
src/os_test.h
src/pager.c
src/test1.c
test/crash.test

index 2f812b242964d81703faaa041c3cac0fa2466f8e..2f56094a563399a5372cb1d7bf66de670fb9bb59 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Handle\scorrupt\sjournal\sfile\sheaders\scorrectly.\s(CVS\s1674)
-D 2004-06-23T01:05:27
+C Test\scases\sto\sverify\srecovery\safter\sa\scrash.\s(CVS\s1675)
+D 2004-06-23T10:43:10
 F Makefile.in 0a3d7aaefa50717bd550b0cf568a51072c4c103c
 F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457
 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@@ -44,13 +44,13 @@ F src/os.h 2f5ea879b784bc82aac8022a3e8fe00b73c83d67
 F src/os_common.h ba1b7306e16e2091718f2c48db0fe6c1d7a31bb8
 F src/os_mac.c 3d31e26be1411acfb7961033098631b4f3486fdf
 F src/os_mac.h 51d2445f47e182ed32d3bd6937f81070c6fd9bd4
-F src/os_test.c db4df491bad874c095b1a9d4db346990cfd56ae0
-F src/os_test.h acacfe7e7bb78dd99865f16cfa822426b177d2ab
+F src/os_test.c ab55524911b66395c39ff8f465dfee0ba1ba06e4
+F src/os_test.h 6a26a4978492e4bbdbf385554958418ff02db162
 F src/os_unix.c 39e73ed02fc992a6bfc52200ea26704633412cc0
 F src/os_unix.h 00c1f82b526ab2fb7ee5ddd555ea4ed68363c93a
 F src/os_win.c 84549f6cc815237533c5d0eb3697352b03478d96
 F src/os_win.h babd4e912967c6b09088cfe38a45e8005a07ba44
-F src/pager.c 42297421e9e7646f99b332c69f3f8085c1d765bf
+F src/pager.c ec34fbae1a23228cb3743cf7cd8eba1af8e4cd5c
 F src/pager.h bc58d32a9dee464f7268fb68652c130a4216e438
 F src/parse.y 097438674976355a10cf177bd97326c548820b86
 F src/pragma.c 0750e1c360647dbe0a991f16133b0fe5e42e5039
@@ -62,7 +62,7 @@ F src/sqlite.h.in 1f400a561fca3b1df73677d2d97046425d47cae4
 F src/sqliteInt.h dd796b6abc6d50505fe33c54f0143d7000681a41
 F src/table.c af14284fa36c8d41f6829e3f2819dce07d3e2de2
 F src/tclsqlite.c 8d093146332b2f0cbf2a8ebe8597d481619308a3
-F src/test1.c 7ecde02fdecff651250f11d96da73d484a4ff764
+F src/test1.c a7e559240e677671224d2d13b4d1dab284e23c20
 F src/test2.c dafd8bd314a554bf376c6d3a8c83fd69219f5a40
 F src/test3.c 7247090d15a5a43823079b6fd8dad1ed3cccdedf
 F src/test4.c a921a69821fd30209589228e64f94e9f715b6fe2
@@ -104,7 +104,7 @@ F test/collate4.test 0e9fc08ffcf6eddf72e354a15de06688fa86db31
 F test/collate5.test 1dd5f0f508c46667f9d4606c7950c414b0bdc0d5
 F test/collate6.test 2a45768914f04c1447a69d1358bbede376552675
 F test/conflict.test c5b849b01cfbe0a4f63a90cba6f68e2fe3a75f87
-F test/crash.test fa7c6ef4d1ac1aa2d14d8afd1583cef8f8e2a0e4
+F test/crash.test 01b4a1cf195678138810f973ec9e2e6cef731d3e
 F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
 F test/date.test aed5030482ebc02bd8d386c6c86a29f694ab068d
 F test/delete.test 4f0c86e2bebdc822d179c80697b1ceabe6bbcd07
@@ -229,7 +229,7 @@ F www/tclsqlite.tcl 19191cf2a1010eaeff74c51d83fd5f5a4d899075
 F www/vdbe.tcl 59288db1ac5c0616296b26dce071c36cb611dfe9
 F www/version3.tcl 563ba3ac02f64da27ab17f3edbe8e56bfd0293fb
 F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4
-P e2f7f182987fbfe8611ead8bd1f12b2e8b47f6dc
-R 8314e42b760c1a8a452b6f1accd15858
+P 46107da7eddbdda8b582e2ece2dc41222a70330a
+R 2d06dadbd77bf8c7d6cc2f4d941e75d8
 U danielk1977
-Z 0fc9a3cf7f51cb64d701f7be6ce96ff8
+Z eb0225a30546e9d6f49bf0fdd5127daf
index 6307d2cddfe8b3b8a6238830a65d1e8e535697b2..4f2cbbfea89179be0e979aa528414d6b835515f4 100644 (file)
@@ -1 +1 @@
-46107da7eddbdda8b582e2ece2dc41222a70330a
\ No newline at end of file
+41868d79ac5b3c496c4d87ca6b4ee7c17ef38965
\ No newline at end of file
index 663453d67bdcaeddb18940713ae187b6d4ac4b35..ce40552770630b50523d9394f14d2ef889d5ad70 100644 (file)
 
 
 /*
-** The crash-seed. Accessed via functions crashseed() and
-** sqlite3SetCrashseed().
+** 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 crashseed_var = 0;
+static int iCrashDelay = 0;
+char zCrashFile[256];
 
 /*
-** 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.
+** Set the value of the two crash parameters.
 */
-void sqlite3SetCrashseed(int seed){
+void sqlite3SetCrashParams(int iDelay, char const *zFile){
   sqlite3OsEnterMutex();
-  crashseed_var = seed;
+  assert( strlen(zFile)<256 );
+  strcpy(zCrashFile, zFile);
+  iCrashDelay = iDelay;
   sqlite3OsLeaveMutex();
 }
 
 /*
-** Retrieve the current value of the crash-seed.
+** File zPath is being sync()ed. Return non-zero if this should
+** cause a crash.
 */
-static int crashseed(){
-  int i;
+static int crashRequired(char const *zPath){
+  int r;
+  int n;
   sqlite3OsEnterMutex();
-  i = crashseed_var;
+  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 i;
+  return r;
 }
 
+
 static OsTestFile *pAllFiles = 0;
 
 /*
@@ -177,6 +190,8 @@ static int cacheBlock(OsTestFile *pFile, int blk){
   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.
@@ -197,16 +212,13 @@ static int writeCache2(OsTestFile *pFile, int crash){
         sqlite3Randomness(1, &random);
         if( random & 0x01 ){
           skip = 1;
-/*
-          printf("Not writing block %d of %s\n", i, pFile->zName);
-*/
+#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);
-*/
+printf("Writing block %d of %s\n", i, pFile->zName); 
+#endif
         }
       }
-
       if( rc==SQLITE_OK ){
         rc = sqlite3RealSeek(&pFile->fd, BLOCK_OFFSET(i));
       }
@@ -235,18 +247,22 @@ static int writeCache2(OsTestFile *pFile, int crash){
 ** 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);
+  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);
     }
-    exit(-1);
-  }else{
-    if( cs>0 ) sqlite3SetCrashseed(cs-1);
-    return writeCache2(pFile, 0);
   }
+  return SQLITE_OK;
 }
 
 /*
index f569687207c725eba85a9afac1f4af185289213b..256eaaf17ac8cc6da153a17f14338c81c0a47ab6 100644 (file)
@@ -34,6 +34,6 @@ struct OsTestFile {
   OsTestFile *pNext;
 };
 
-void sqlite3SetCrashseed(int seed);
+void sqlite3SetCrashParams(int iDelay, char const *zFile);
 
 #endif /* _SQLITE_OS_UNIX_H_ */
index 9e46ef899cf535d11da32656fd25ca268ac034c6..85e09e3eff01201adb2a4330248dafe5d60054fa 100644 (file)
@@ -18,7 +18,7 @@
 ** 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 $
+** @(#) $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"
@@ -479,11 +479,15 @@ static int pager_unwritelock(Pager *pPager){
       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;
 }
 
@@ -508,7 +512,12 @@ static int pager_unwritelock(Pager *pPager){
 ** 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;
+  u32 cksum = pPager->cksumInit;
+  int i = pPager->pageSize-200;
+  while( i>0 ){
+    cksum += aData[i];
+    i -= 200;
+  }
   return cksum;
 }
 
@@ -840,7 +849,9 @@ static int pager_playback(Pager *pPager, int useJournalSize){
   /* (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 ){
+  if( nRec==0xffffffff || useJournalSize || 
+      nRec>(szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager)
+  ){
     nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager);
   }
 
@@ -1007,6 +1018,8 @@ void sqlite3pager_set_cachesize(Pager *pPager, int mxPage){
   }
   if( mxPage>10 ){
     pPager->mxPage = mxPage;
+  }else{
+    pPager->mxPage = 10;
   }
 }
 
@@ -2409,19 +2422,6 @@ int sqlite3pager_commit(Pager *pPager){
     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;
index 7aa1468e44b73a25663500409ab4739fcbbe3337..aedda2dd73891a9306dc6ccc779a55af5ee1cebc 100644 (file)
@@ -13,7 +13,7 @@
 ** 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 $
+** $Id: test1.c,v 1.84 2004/06/23 10:43:11 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -984,24 +984,24 @@ bad_args:
   return TCL_ERROR;
 }
 
-static int sqlite3_crashseed(
+static int sqlite3_crashparams(
   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);
+  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), "<seed>", 0);
+      Tcl_GetStringFromObj(objv[0], 0), "<delay> <filename>", 0);
   return TCL_ERROR;
 #endif
 }
@@ -2068,7 +2068,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3OsLock",         test_sqlite3OsLock, 0 },
      { "sqlite3OsUnlock",       test_sqlite3OsUnlock, 0 },
      { "add_test_collate",      test_collate, 0         },
-     { "sqlite3_crashseed",     sqlite3_crashseed, 0         },
+     { "sqlite3_crashparams",     sqlite3_crashparams, 0         },
 
   };
   int i;
index e287c33051f46da4b730c38bbc6df9866b1463c5..88ababf3f09a9175383ecb22c8088be144928e36 100644 (file)
 #***********************************************************************
 # This file implements regression tests for SQLite library.
 #
-# $Id: crash.test,v 1.2 2004/06/23 01:05:27 danielk1977 Exp $
+# $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.
 #
-# 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} {
+# 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_crashseed $crashdelay"
+  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
 
-  exec [file join . crashtest] crash.tcl
+  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
-#            journal-sync
+#            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
-#            database-sync
+#            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 {
-  catch {
-    crashsql 1 {
-      DELETE FROM abc WHERE a = 1;
-    }
-  } msg
-  set msg
-} {child process exited abnormally}
+  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 {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 {
+} {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 {1 2 3 4 5 6}}
+} {0 {}}
 
-finish_test
+#--------------------------------------------------------------------------
+# 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