Index: src/test_vfs.c ================================================================== --- src/test_vfs.c +++ src/test_vfs.c @@ -57,10 +57,14 @@ int mask; int iIoerrCnt; int ioerr; int nIoerrFail; + int iFullCnt; + int fullerr; + int nFullFail; + int iDevchar; int iSectorsize; }; /* @@ -191,10 +195,34 @@ } return 0; } +static int tvfsInjectIoerr(Testvfs *p){ + int ret = 0; + if( p->ioerr ){ + p->iIoerrCnt--; + if( p->iIoerrCnt==0 || (p->iIoerrCnt<0 && p->ioerr==2) ){ + ret = 1; + p->nIoerrFail++; + } + } + return ret; +} + +static int tvfsInjectFullerr(Testvfs *p){ + int ret = 0; + if( p->fullerr ){ + p->iFullCnt--; + if( p->iFullCnt<=0 ){ + ret = 1; + p->nFullFail++; + } + } + return ret; +} + static void tvfsExecTcl( Testvfs *p, const char *zMethod, Tcl_Obj *arg1, @@ -300,10 +328,12 @@ tvfsExecTcl(p, "xWrite", Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0 ); tvfsResultCode(p, &rc); } + + if( rc==SQLITE_OK && tvfsInjectFullerr(p) ) rc = SQLITE_FULL; if( rc==SQLITE_OK ){ rc = sqlite3OsWrite(pFd->pReal, zBuf, iAmt, iOfst); } return rc; @@ -362,10 +392,12 @@ Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, Tcl_NewStringObj(zFlags, -1) ); tvfsResultCode(p, &rc); } + + if( rc==SQLITE_OK && tvfsInjectFullerr(p) ) rc = SQLITE_FULL; if( rc==SQLITE_OK ){ rc = sqlite3OsSync(pFd->pReal, flags); } @@ -601,22 +633,10 @@ */ static int tvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut); } -static int tvfsInjectIoerr(Testvfs *p){ - int ret = 0; - if( p->ioerr ){ - p->iIoerrCnt--; - if( p->iIoerrCnt==0 || (p->iIoerrCnt<0 && p->ioerr==2) ){ - ret = 1; - p->nIoerrFail++; - } - } - return ret; -} - static int tvfsShmOpen( sqlite3_file *pFileDes ){ Testvfs *p; int rc = SQLITE_OK; /* Return code */ @@ -830,20 +850,21 @@ ){ Testvfs *p = (Testvfs *)cd; enum DB_enum { CMD_SHM, CMD_DELETE, CMD_FILTER, CMD_IOERR, CMD_SCRIPT, - CMD_DEVCHAR, CMD_SECTORSIZE + CMD_DEVCHAR, CMD_SECTORSIZE, CMD_FULLERR }; struct TestvfsSubcmd { char *zName; enum DB_enum eCmd; } aSubcmd[] = { { "shm", CMD_SHM }, { "delete", CMD_DELETE }, { "filter", CMD_FILTER }, { "ioerr", CMD_IOERR }, + { "fullerr", CMD_FULLERR }, { "script", CMD_SCRIPT }, { "devchar", CMD_DEVCHAR }, { "sectorsize", CMD_SECTORSIZE }, { 0, 0 } }; @@ -975,10 +996,38 @@ Tcl_ResetResult(interp); if( p->pScript ) Tcl_SetObjResult(interp, p->pScript); break; } + + /* + ** TESTVFS fullerr ?IFAIL? + ** + ** Where IFAIL is an integer. + */ + case CMD_FULLERR: { + int iRet = p->nFullFail; + + p->nFullFail = 0; + p->fullerr = 0; + p->iFullCnt = 0; + + if( objc==3 ){ + int iCnt; + if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iCnt) ){ + return TCL_ERROR; + } + p->fullerr = (iCnt>0); + p->iFullCnt = iCnt; + }else if( objc!=2 ){ + Tcl_AppendResult(interp, "Bad args", 0); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(iRet)); + break; + } /* ** TESTVFS ioerr ?IFAIL PERSIST? ** ** Where IFAIL is an integer and PERSIST is boolean. Index: test/malloc_common.tcl ================================================================== --- test/malloc_common.tcl +++ test/malloc_common.tcl @@ -46,10 +46,20 @@ set FAULTSIM(ioerr-persistent) [list \ -injectstart {ioerr_injectstart 1} \ -injectstop ioerr_injectstop \ -injecterrlist {{1 {disk I/O error}}} \ ] + +# SQLITE_FULL errors (always persistent): +# +set FAULTSIM(full) [list \ + -injectinstall fullerr_injectinstall \ + -injectstart fullerr_injectstart \ + -injectstop fullerr_injectstop \ + -injecterrlist {{1 {database or disk is full}}} \ + -injectuninstall fullerr_injectuninstall \ +] # Transient and persistent SHM errors: # set FAULTSIM(shmerr-transient) [list \ -injectinstall shmerr_injectinstall \ @@ -174,10 +184,11 @@ set ::sqlite_io_error_hardhit 0 set ::sqlite_io_error_hit 0 set ::sqlite_io_error_pending 0 return $sv } + # The following procs are used as [do_one_faultsim_test] callbacks when # injecting shared-memory related error faults into test cases. # proc shmerr_injectinstall {} { @@ -192,10 +203,26 @@ shmfault ioerr $iFail $persist } proc shmerr_injectstop {} { shmfault ioerr 0 0 } + +proc fullerr_injectinstall {} { + testvfs shmfault -default true +} +proc fullerr_injectuninstall {} { + catch {db close} + catch {db2 close} + shmfault delete +} +proc fullerr_injectstart {iFail} { + shmfault full $iFail +} +proc fullerr_injectstop {} { + shmfault full 0 +} + # This command is not called directly. It is used by the # [faultsim_test_result] command created by [do_faultsim_test] and used # by -test scripts. # Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -24,10 +24,12 @@ # # pager1-4.*: Hot-journal related tests. # # pager1-5.*: Cases related to multi-file commits. # +# pager1-6.*: Cases related to "PRAGMA max_page_count" +# proc do_execsql_test {testname sql result} { uplevel do_test $testname [list "execsql {$sql}"] [list $result] } proc do_catchsql_test {testname sql result} { @@ -219,10 +221,16 @@ foreach code [list { set s 512 } { set s 1024 set sql { PRAGMA journal_mode = memory } +} { + set s 1024 + set sql { + PRAGMA journal_mode = memory; + PRAGMA locking_mode = exclusive; + } } { set s 2048 tv devchar safe_append } { set s 4096 @@ -447,9 +455,170 @@ } $result } db close #------------------------------------------------------------------------- +# The following tests deal with multi-file commits. +# +# pager1-5.1.*: The case where a multi-file cannot be committed because +# another connection is holding a SHARED lock on one of the +# files. After the SHARED lock is removed, the COMMIT succeeds. +# +# pager1-5.2.*: Multi-file commits with journal_mode=memory. +# +# pager1-5.3.*: Multi-file commits with journal_mode=memory. +# +# pager1-5.4.*: Check that with synchronous=normal, the master-journal file +# name is added to a journal file immediately after the last +# journal record. But with synchronous=full, extra unused space +# is allocated between the last journal record and the +# master-journal file name so that the master-journal file +# name does not lie on the same sector as the last journal file +# record. +# +# pager1-5.5.*: +# +do_test pager1-5.1.1 { + faultsim_delete_and_reopen + execsql { + ATTACH 'test.db2' AS aux; + CREATE TABLE t1(a, b); + CREATE TABLE aux.t2(a, b); + INSERT INTO t1 VALUES(17, 'Lenin'); + INSERT INTO t1 VALUES(22, 'Stalin'); + INSERT INTO t1 VALUES(53, 'Khrushchev'); + } +} {} +do_test pager1-5.1.2 { + execsql { + BEGIN; + INSERT INTO t1 VALUES(64, 'Brezhnev'); + INSERT INTO t2 SELECT * FROM t1; + } + sqlite3 db2 test.db2 + execsql { + BEGIN; + SELECT * FROM t2; + } db2 +} {} +do_test pager1-5.1.3 { + catchsql COMMIT +} {1 {database is locked}} +do_test pager1-5.1.4 { + execsql COMMIT db2 + execsql COMMIT + execsql { SELECT * FROM t2 } db2 +} {17 Lenin 22 Stalin 53 Khrushchev 64 Brezhnev} +do_test pager1-5.1.5 { + db2 close +} {} + +do_test pager1-5.2.1 { + execsql { + PRAGMA journal_mode = memory; + BEGIN; + INSERT INTO t1 VALUES(84, 'Andropov'); + INSERT INTO t2 VALUES(84, 'Andropov'); + COMMIT; + } +} {memory} +do_test pager1-5.3.1 { + execsql { + PRAGMA journal_mode = off; + BEGIN; + INSERT INTO t1 VALUES(85, 'Gorbachev'); + INSERT INTO t2 VALUES(85, 'Gorbachev'); + COMMIT; + } +} {off} + +do_test pager1-5.4.1 { + db close + testvfs tv + sqlite3 db test.db -vfs tv + execsql { ATTACH 'test.db2' AS aux } + + tv filter xDelete + tv script max_journal_size + tv sectorsize 512 + set ::max_journal 0 + proc max_journal_size {method args} { + set sz 0 + catch { set sz [file size test.db-journal] } + if {$sz > $::max_journal} { + set ::max_journal $sz + } + return SQLITE_OK + } + execsql { + PRAGMA journal_mode = DELETE; + PRAGMA synchronous = NORMAL; + BEGIN; + INSERT INTO t1 VALUES(85, 'Gorbachev'); + INSERT INTO t2 VALUES(85, 'Gorbachev'); + COMMIT; + } + set ::max_journal +} [expr 2615+[string length [pwd]]] +do_test pager1-5.4.2 { + set ::max_journal 0 + execsql { + PRAGMA synchronous = full; + BEGIN; + DELETE FROM t1 WHERE b = 'Lenin'; + DELETE FROM t2 WHERE b = 'Lenin'; + COMMIT; + } + set ::max_journal +} [expr 3111+[string length [pwd]]] +db close +tv delete + +do_test pager1-5.5.1 { + sqlite3 db test.db + execsql { + ATTACH 'test.db2' AS aux; + PRAGMA journal_mode = PERSIST; + CREATE TABLE t3(a, b); + INSERT INTO t3 SELECT randomblob(1500), randomblob(1500) FROM t1; + UPDATE t3 SET b = randomblob(1500); + } + expr [file size test.db-journal] > 15000 +} {1} +do_test pager1-5.5.2 { + execsql { + PRAGMA synchronous = full; + BEGIN; + DELETE FROM t1 WHERE b = 'Stalin'; + DELETE FROM t2 WHERE b = 'Stalin'; + COMMIT; + } + file size test.db-journal +} {0} + + +#------------------------------------------------------------------------- +# The following tests work with "PRAGMA max_page_count" # +do_test pager1-6.1 { + faultsim_delete_and_reopen + execsql { + PRAGMA max_page_count = 10; + CREATE TABLE t2(a, b); + CREATE TABLE t3(a, b); + CREATE TABLE t4(a, b); + CREATE TABLE t5(a, b); + CREATE TABLE t6(a, b); + CREATE TABLE t7(a, b); + CREATE TABLE t8(a, b); + CREATE TABLE t9(a, b); + CREATE TABLE t10(a, b); + } +} {10} +do_test pager1-6.2 { + catchsql { + CREATE TABLE t11(a, b); + } +} {1 {database or disk is full}} finish_test Index: test/pagerfault.test ================================================================== --- test/pagerfault.test +++ test/pagerfault.test @@ -177,10 +177,16 @@ faultsim_integrity_check } #------------------------------------------------------------------------- # Test fault-injection as part of a commit when using journal_mode=PERSIST. +# Three different cases: +# +# pagerfault-5.1: With no journal_size_limit configured. +# pagerfault-5.2: With a journal_size_limit configured. +# pagerfault-5.4: Multi-file transaction. One connection has a +# journal_size_limit of 0, the other has no limit. # do_test pagerfault-5-pre1 { faultsim_delete_and_reopen db func a_string a_string execsql { @@ -212,11 +218,10 @@ execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 } } -test { faultsim_test_result {0 {}} faultsim_integrity_check } - do_faultsim_test pagerfault-5.3 -prep { faultsim_restore_and_reopen db func a_string a_string file delete -force test2.db test2.db-journal test2.db-wal execsql { @@ -233,7 +238,49 @@ COMMIT; } } -test { faultsim_test_result {0 {}} } + +# The following was an attempt to get a bitvec malloc to fail. Didn't work. +# +# do_test pagerfault-6-pre1 { +# faultsim_delete_and_reopen +# execsql { +# CREATE TABLE t1(x, y, UNIQUE(x, y)); +# INSERT INTO t1 VALUES(1, randomblob(1501)); +# INSERT INTO t1 VALUES(2, randomblob(1502)); +# INSERT INTO t1 VALUES(3, randomblob(1503)); +# INSERT INTO t1 VALUES(4, randomblob(1504)); +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# } +# faultsim_save_and_close +# } {} +# do_faultsim_test pagerfault-6 -prep { +# faultsim_restore_and_reopen +# } -body { +# execsql { +# BEGIN; +# UPDATE t1 SET x=x+4 WHERE x=1; +# SAVEPOINT one; +# UPDATE t1 SET x=x+4 WHERE x=2; +# SAVEPOINT three; +# UPDATE t1 SET x=x+4 WHERE x=3; +# SAVEPOINT four; +# UPDATE t1 SET x=x+4 WHERE x=4; +# RELEASE three; +# COMMIT; +# SELECT DISTINCT x FROM t1; +# } +# } -test { +# faultsim_test_result {0 {5 6 7 8}} +# faultsim_integrity_check +# } finish_test Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -168,10 +168,11 @@ test_suite "coverage-pager" -description { Coverage tests for file pager.c. } -files { pager1.test pagerfault.test + journal2.test } lappend ::testsuitelist xxx #-------------------------------------------------------------------------