From: dan Date: Tue, 9 Dec 2014 20:13:40 +0000 (+0000) Subject: Add extra tests to e_walckpt.test. X-Git-Tag: version-3.8.8~100 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9778bd7292dbf9b4386fab47f16e178c97f20c02;p=thirdparty%2Fsqlite.git Add extra tests to e_walckpt.test. FossilOrigin-Name: 84f9581019961efa31297f8be48427b17bcca857 --- diff --git a/manifest b/manifest index b0f6c74e0a..0baa94d673 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Lower\sthe\sdefault\sSQLITE_SORTER_PMASZ\svalue\sback\sto\s10,\swhere\sit\shas\sbeen\sfor\nthe\spast\scouple\sof\sreleases.\s\sApplications\sthat\sneed\sa\slarger\svalue\scan\sset\none. -D 2014-12-09T19:16:41.997 +C Add\sextra\stests\sto\se_walckpt.test. +D 2014-12-09T20:13:40.856 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 6c4f961fa91d0b4fa121946a19f9e5eac2f2f809 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -238,7 +238,7 @@ F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 81712116e826b0089bb221b018929536b2b5406f F src/table.c f142bba7903e93ca8d113a5b8877a108ad1a27dc F src/tclsqlite.c 0a874655dd39a9875e39c5d3c464db662171d228 -F src/test1.c fa655a378b11bf811f0c15c56eb1d232118224cc +F src/test1.c f60b9e973cf813fdabb2b67ccbb4a08e9f1d81be F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712 F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df @@ -478,7 +478,7 @@ F test/e_update.test 312cb8f5ccfe41515a6bb092f8ea562a9bd54d52 F test/e_uri.test 5ae33760fb2039c61aa2d90886f1664664173585 F test/e_vacuum.test 5bfbdc21b65c0abf24398d0ba31dc88d93ca77a9 F test/e_wal.test 0967f0b8f1dfda871dc7b9b5574198f1f4f7d69a -F test/e_walckpt.test 18de8fca6b74f29bf7d24a2e267eec749b8fec50 +F test/e_walckpt.test 3116a98fa0dd9b2c9e493de7c59730adfe436746 F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea F test/enc2.test 83437a79ba1545a55fb549309175c683fb334473 F test/enc3.test 90683ad0e6ea587b9d5542ca93568af9a9858c40 @@ -1226,7 +1226,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 4f7549ff924b8ed8f90fc447cd4be11421453984 -R 07540d2951e499e9ba9f784727d9cc12 -U drh -Z 7254b0c65f796e3de7bb7b37671c0413 +P 1ba8911c18f2fe34c20dc42f25a8f3c1c798fa7a +R 3c53b0c88f36488767cc876fe1e6e0b0 +U dan +Z bbcee16517facfe70298aa47dfb20c31 diff --git a/manifest.uuid b/manifest.uuid index f4de208a5d..587f7e93dc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1ba8911c18f2fe34c20dc42f25a8f3c1c798fa7a \ No newline at end of file +84f9581019961efa31297f8be48427b17bcca857 \ No newline at end of file diff --git a/src/test1.c b/src/test1.c index 9f97c7371f..009b88a543 100644 --- a/src/test1.c +++ b/src/test1.c @@ -5712,6 +5712,7 @@ static int test_wal_checkpoint_v2( rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt); if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ const char *zErrCode = sqlite3ErrName(rc); + Tcl_ResetResult(interp); Tcl_AppendResult(interp, zErrCode, " - ", (char *)sqlite3_errmsg(db), 0); return TCL_ERROR; } diff --git a/test/e_walckpt.test b/test/e_walckpt.test index f0edc46bee..e6b6566bed 100644 --- a/test/e_walckpt.test +++ b/test/e_walckpt.test @@ -42,6 +42,38 @@ proc compare_db_hashes {} { set ret } +#------------------------------------------------------------------------- +# All calls to the [sqlite3_wal_checkpoint_v2] command made within this +# file use this wrapper. It's sole purpose is to throw an error if the +# following requirement is violated: +# +# EVIDENCE-OF: R-60567-47780 Unless it returns SQLITE_MISUSE, the +# sqlite3_wal_checkpoint_v2() interface sets the error information that +# is queried by sqlite3_errcode() and sqlite3_errmsg(). +# +proc wal_checkpoint_v2 {db args} { + set rc [catch { + uplevel sqlite3_wal_checkpoint_v2 $db $args + } msg] + + set errcode "SQLITE_OK" + if {$rc} { + set errcode [lindex [split $msg " "] 0] + } elseif { [lindex $msg 0] } { + set errcode "SQLITE_BUSY" + } + + if {$errcode != "SQLITE_MISUSE" && [sqlite3_errcode $db] != $errcode} { + error "sqlite3_errcode mismatch! (1) $errcode!=[sqlite3_errcode $db]" + } + + if {$rc==0} { + return $msg + } else { + error $msg + } +} + # The following tests are run 3 times, each using a different method of # invoking a checkpoint: @@ -63,7 +95,7 @@ proc compare_db_hashes {} { foreach {tn script} { 1 { proc checkpoint {db mode args} { - eval sqlite3_wal_checkpoint_v2 [list $db] [list $mode] $args + eval wal_checkpoint_v2 [list $db] [list $mode] $args } } @@ -90,7 +122,7 @@ foreach {tn script} { error "$rc - [sqlite3_errmsg $db]" } } else { - eval sqlite3_wal_checkpoint_v2 [list $db] [list $mode] $args + eval wal_checkpoint_v2 [list $db] [list $mode] $args } } } @@ -270,36 +302,46 @@ foreach {tn script} { } 2 { - # Close first the reader, then later the writer. + # Close first the reader, then later the writer. Give up before + # closing the [db6] reader. if {$n==5} { catch {db2 eval commit} } if {$n==10} { catch {db3 eval commit} } + if {$n==15} { return 1 } return 0 } 3 { - # Close first the writer, then later the reader. + # Close first the writer, then later the reader. And finally the + # [db6] reader. if {$n==5} { catch {db2 eval commit} } if {$n==10} { catch {db3 eval commit} } + if {$n==15} { catch {db6 eval commit} } return 0 } } } foreach {mode busy_handler_mode} { - passive 1 - full 1 - full 2 - full 3 + passive 1 + full 1 full 2 full 3 + restart 1 restart 2 restart 3 + truncate 1 truncate 2 truncate 3 } { + set tp "$tn.$mode.$busy_handler_mode" set ::sync_counter 0 + # Set up a callback function for xSync and xWrite calls made during + # the checkpoint. + # + set ::checkpoint_ongoing 0 proc tvfs_callback {method args} { + if {$::checkpoint_ongoing==0} return + set tail [file tail [lindex $args 0]] if {$method == "xSync" && $tail == "test.db"} { incr ::sync_counter } - if {$method == "xWrite" && $tail=="test.db"} { if {$::write_ok < 0} { set ::write_ok [expr ![catch {db5 eval { BEGIN IMMEDIATE }}]] @@ -308,6 +350,14 @@ foreach {tn script} { if {$::read_ok < 0} { set ::read_ok [expr ![catch {db5 eval { SELECT * FROM t1 }}]] } + + # If one has not already been opened, open a read-transaction using + # connection [db6] + catch { db6 eval { BEGIN ; SELECT * FROM sqlite_master } } msg + } + if {$method == "xShmLock" } { + set details [lindex $args 2] + if {$details == "0 1 lock exclusive"} { set ::seen_writer_lock 1 } } } @@ -318,7 +368,7 @@ foreach {tn script} { #tvfs filter xSync tvfs script tvfs_callback - do_execsql_test $tn.4.$mode.0 { + do_execsql_test $tp.0 { CREATE TABLE t1(a, b); CREATE TABLE t2(a, b); PRAGMA journal_mode = wal; @@ -328,7 +378,7 @@ foreach {tn script} { } {wal} # Open a reader on the current database snapshot. - do_test $tn.4.$mode.1 { + do_test $tp.1 { sqlite3 db2 test.db -vfs tvfs execsql { BEGIN; @@ -338,7 +388,7 @@ foreach {tn script} { # Open a writer. Write a transaction. Then begin, but do not commit, # a second transaction. - do_test $tn.4.$mode.2 { + do_test $tp.2 { sqlite3 db3 test.db -vfs tvfs execsql { INSERT INTO t2 VALUES(7, 8); @@ -349,6 +399,7 @@ foreach {tn script} { } {1 2 3 4 5 6 7 8 9 10} sqlite3 db5 test.db -vfs tvfs + sqlite3 db6 test.db -vfs tvfs # Register a busy-handler with connection [db]. # @@ -357,11 +408,15 @@ foreach {tn script} { set ::busy_handler_counter 0 set ::read_ok -1 set ::write_ok -1 + set ::seen_writer_lock 0 - do_test $tn.4.$mode.3 { + set ::checkpoint_ongoing 1 + do_test $tp.3 { checkpoint db $mode main set {} {} } {} + set ::checkpoint_ongoing 0 + set ::did_restart_blocking [expr {[catch {db6 eval commit}]}] if { $mode=="passive" } { # EVIDENCE-OF: R-16333-64433 Checkpoint as many frames as possible @@ -381,15 +436,15 @@ foreach {tn script} { # EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked # in the SQLITE_CHECKPOINT_PASSIVE mode. # - # It's not. Test case "$tn.4.$mode.6". + # It's not. Test case "$tp.6". # - do_test $tn.4.$mode.4 { + do_test $tp.4 { forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6} - do_test $tn.4.$mode.5 { set ::sync_counter } 0 - do_test $tn.4.$mode.6 { set ::busy_handler_counter } 0 + do_test $tp.5 { set ::sync_counter } 0 + do_test $tp.6 { set ::busy_handler_counter } 0 db4 close db2 eval COMMIT @@ -405,26 +460,53 @@ foreach {tn script} { # Also, because the checkpoint finishes this time, the db is synced. # Which is part of R-16333-64433 above. # - do_test $tn.4.$mode.7 { + set ::checkpoint_ongoing 1 + do_test $tp.7 { checkpoint db $mode main forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6 7 8 9 10} - do_test $tn.4.$mode.6 { set ::sync_counter } 1 - do_test $tn.4.$mode.7 { set ::busy_handler_counter } 0 + set ::checkpoint_ongoing 0 + do_test $tp.7 { set ::sync_counter } 1 + do_test $tp.8 { set ::busy_handler_counter } 0 db4 close } - if { $mode=="full" } { + if { $mode=="full" || $mode=="restart" || $mode=="truncate" } { + + # EVIDENCE-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and + # TRUNCATE modes also obtain the exclusive "writer" lock on the + # database file. + # + # Or at least attempts to obtain. + # + do_test $tp.9 { + set ::seen_writer_lock + } {1} + if {$busy_handler_mode==2 || $busy_handler_mode==3} { # EVIDENCE-OF: R-59171-47567 This mode blocks (it invokes the # busy-handler callback) until there is no database writer and all # readers are reading from the most recent database snapshot. # - # Show that both the reader and writer have finished: + # The test below shows that both the reader and writer have + # finished: + # + # Also restated by the following two. That both busy_handler_mode + # values 2 and 3 work show that both of the following are true - as + # they release the reader and writer transactions in different + # orders. + # + # EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + # immediately, and a busy-handler is configured, it is invoked and the + # writer lock retried until either the busy-handler returns 0 or the + # lock is successfully obtained. # - do_test $tn.4.$mode.7 { + # EVIDENCE-OF: R-48107-00250 The busy-handler is also invoked while + # waiting for database readers as described above. + # + do_test $tp.7 { list [catchsql COMMIT db2] [catchsql COMMIT db3] } [list \ {1 {cannot commit - no transaction is active}} \ @@ -434,27 +516,79 @@ foreach {tn script} { # EVIDENCE-OF: R-29177-48281 It then checkpoints all frames in the log # file and syncs the database file. # - do_test $tn.4.$mode.8 { + do_test $tp.8 { forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6 7 8 9 10} - do_test $tn.4.$mode.9 { set ::sync_counter } 1 + do_test $tp.9 { set ::sync_counter } 1 db4 close # EVIDENCE-OF: R-51867-44713 This mode blocks new database writers # while it is pending, but new database readers are allowed to continue # unimpeded. - do_test $tn.4.$mode.10 { + # + # EVIDENCE-OF: R-47276-58266 Like SQLITE_CHECKPOINT_FULL, this mode + # blocks new database writer attempts while it is pending, but does not + # impede readers. + # + # The first of the above two refers to "full" mode. The second + # to "restart". + # + do_test $tp.10.1 { list $::write_ok $::read_ok } {0 1} + # EVIDENCE-OF: R-12410-31217 This mode works the same way as + # SQLITE_CHECKPOINT_FULL with the addition that after checkpointing the + # log file it blocks (calls the busy-handler callback) until all + # readers are reading from the database file only. + # + # The stuff above passed, so the first part of this requirement + # is met. The second part is tested below. If the checkpoint mode + # was "restart" or "truncate", then the busy-handler will have + # been called to block on wal-file readers. + # + do_test $tp.11 { + set ::did_restart_blocking + } [expr {($mode=="restart"||$mode=="truncate")&&$busy_handler_mode==3}] + + # EVIDENCE-OF: R-44699-57140 This mode works the same way as + # SQLITE_CHECKPOINT_RESTART with the addition that it also truncates + # the log file to zero bytes just prior to a successful return. + if {$mode=="truncate" && $busy_handler_mode==3} { + do_test $tp.12 { + file size test.db-wal + } 0 + } + } elseif {$busy_handler_mode==1} { + + # EVIDENCE-OF: R-34519-06271 SQLITE_BUSY is returned in this case. + if {$tn!=2} { + # ($tn==2) is the loop that uses "PRAGMA wal_checkpoint" + do_test $tp.13 { sqlite3_errcode db } {SQLITE_BUSY} + } + + # EVIDENCE-OF: R-49155-63541 If the busy-handler returns 0 before the + # writer lock is obtained or while waiting for database readers, the + # checkpoint operation proceeds from that point in the same way as + # SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible + # without blocking any further. + do_test $tp.14 { + forcecopy test.db abc.db + sqlite3 db4 abc.db + db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } + } {1 2 3 4 5 6} + do_test $tp.15 { set ::sync_counter } 0 + do_test $tp.16 { set ::busy_handler_counter } 1 + db4 close } } db2 close db3 close db5 close + db6 close } db close @@ -480,9 +614,139 @@ foreach {tn mode res} { 8 1000000 {1 {SQLITE_MISUSE - not an error}} } { do_test 4.$tn { - list [catch "sqlite3_wal_checkpoint_v2 db $mode" msg] $msg + list [catch "wal_checkpoint_v2 db $mode" msg] $msg } $res } +db close + +foreach tn {1 2 3} { + forcedelete test.db test.db2 test.db3 + testvfs tvfs + + sqlite3 db test.db -vfs tvfs + execsql { + ATTACH 'test.db2' AS aux2; + ATTACH 'test.db3' AS aux3; + PRAGMA main.journal_mode = WAL; + PRAGMA aux2.journal_mode = WAL; + PRAGMA aux3.journal_mode = WAL; + + CREATE TABLE main.t1(x,y); + CREATE TABLE aux2.t2(x,y); + CREATE TABLE aux3.t3(x,y); + + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t2 VALUES('a', 'b'); + INSERT INTO t3 VALUES('a', 'b'); + } + sqlite3 db2 test.db2 -vfs tvfs + + switch -- $tn { + 1 { + # EVIDENCE-OF: R-41299-52117 If no error (SQLITE_BUSY or otherwise) is + # encountered while processing the attached databases, SQLITE_OK is + # returned. + do_test 5.$tn.1 { + lindex [wal_checkpoint_v2 db truncate] 0 + } {0} ;# 0 -> SQLITE_OK + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 1 1} + } + + 2 { + # EVIDENCE-OF: R-38578-34175 If an SQLITE_BUSY error is encountered when + # processing one or more of the attached WAL databases, the operation is + # still attempted on any remaining attached databases and SQLITE_BUSY is + # returned at the end. + db2 eval { BEGIN; INSERT INTO t2 VALUES('d', 'e'); } + do_test 5.$tn.1 { + lindex [wal_checkpoint_v2 db truncate] 0 + } {1} ;# 1 -> SQLITE_BUSY + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 0 1} + db2 eval ROLLBACK + } + + 3 { + # EVIDENCE-OF: R-38049-07913 If any other error occurs while processing + # an attached database, processing is abandoned and the error code is + # returned to the caller immediately. + tvfs filter xWrite + tvfs script inject_ioerr + proc inject_ioerr {method file args} { + if {[file tail $file]=="test.db2"} { + return "SQLITE_IOERR" + } + return 0 + } + do_test 5.$tn.1 { + list [catch { wal_checkpoint_v2 db truncate } msg] $msg + } {1 {SQLITE_IOERR - disk I/O error}} + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 0 0} + tvfs script "" + } + } + + db close + db2 close +} + +reset_db +sqlite3 db2 test.db + +do_test 6.1 { + execsql { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } + file size test.db-wal +} [wal_file_size 3 1024] + +do_test 6.2 { + db2 eval { BEGIN; SELECT * FROM t1; } + db eval { INSERT INTO t1 VALUES(3, 4) } + file size test.db-wal +} [wal_file_size 4 1024] + +# At this point the log file contains 4 frames. 3 of which it should +# be possible to checkpoint. +# +# EVIDENCE-OF: R-16642-42503 If pnLog is not NULL, then *pnLog is set to +# the total number of frames in the log file or to -1 if the checkpoint +# could not run because of an error or because the database is not in +# WAL mode. +# +# EVIDENCE-OF: R-10514-25250 If pnCkpt is not NULL,then *pnCkpt is set +# to the total number of checkpointed frames in the log file (including +# any that were already checkpointed before the function was called) or +# to -1 if the checkpoint could not run due to an error or because the +# database is not in WAL mode. +# +do_test 6.4 { + lrange [wal_checkpoint_v2 db passive] 1 2 +} {4 3} + +# EVIDENCE-OF: R-37257-17813 Note that upon successful completion of an +# SQLITE_CHECKPOINT_TRUNCATE, the log file will have been truncated to +# zero bytes and so both *pnLog and *pnCkpt will be set to zero. +# +do_test 6.5 { + db2 eval COMMIT + wal_checkpoint_v2 db truncate +} {0 0 0} + finish_test +