Test cases to verify recovery after a crash. (CVS 1675)

FossilOrigin-Name: 41868d79ac5b3c496c4d87ca6b4ee7c17ef38965
This commit is contained in:
danielk1977 2004-06-23 10:43:10 +00:00
parent ece80f1e48
commit ef317ab577
7 changed files with 343 additions and 104 deletions

View File

@ -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

View File

@ -1 +1 @@
46107da7eddbdda8b582e2ece2dc41222a70330a
41868d79ac5b3c496c4d87ca6b4ee7c17ef38965

View File

@ -60,41 +60,54 @@
/*
** 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;
}
/*

View 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_ */

View 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;

View 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;

View File

@ -10,74 +10,297 @@
#***********************************************************************
# 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 {
catchsql {
SELECT * FROM abc;
}
} {0 {1 2 3 4 5 6}}
signature
} $::sig
do_test crash-1.4 {
catch {
crashsql 1 {
DELETE FROM abc WHERE a = 1;
}
} msg
set msg
} {child process exited abnormally}
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}}
} {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