# 2009 January 30 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # 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. The # focus of this file is testing the handling of IO errors by the # sqlite3_backup_XXX APIs. # # $Id: backup_ioerr.test,v 1.1.1.1 2012/02/21 17:04:16 misho Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl proc data_checksum {db file} { $db one "SELECT md5sum(a, b) FROM ${file}.t1" } proc test_contents {name db1 file1 db2 file2} { $db2 eval {select * from sqlite_master} $db1 eval {select * from sqlite_master} set checksum [data_checksum $db2 $file2] uplevel [list do_test $name [list data_checksum $db1 $file1] $checksum] } #-------------------------------------------------------------------- # This proc creates a database of approximately 290 pages. Depending # on whether or not auto-vacuum is configured. Test cases backup_ioerr-1.* # verify nothing more than this assumption. # proc populate_database {db {xtra_large 0}} { execsql { BEGIN; CREATE TABLE t1(a, b); INSERT INTO t1 VALUES(1, randstr(1000,1000)); INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1; INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1; INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1; INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1; INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1; INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1; CREATE INDEX i1 ON t1(b); COMMIT; } $db if {$xtra_large} { execsql { INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1 } $db } } do_test backup_ioerr-1.1 { populate_database db set nPage [expr {[file size test.db] / 1024}] expr {$nPage>130 && $nPage<160} } {1} do_test backup_ioerr-1.2 { expr {[file size test.db] > $sqlite_pending_byte} } {1} do_test backup_ioerr-1.3 { db close forcedelete test.db } {} # Turn off IO error simulation. # proc clear_ioerr_simulation {} { set ::sqlite_io_error_hit 0 set ::sqlite_io_error_hardhit 0 set ::sqlite_io_error_pending 0 set ::sqlite_io_error_persist 0 } #-------------------------------------------------------------------- # The following procedure runs with SQLite's IO error simulation # enabled. # # 1) Start with a reasonably sized database. One that includes the # pending-byte (locking) page. # # 2) Open a backup process. Set the cache-size for the destination # database to 10 pages only. # # 3) Step the backup process N times to partially backup the database # file. If an IO error is reported, then the backup process is # concluded with a call to backup_finish(). # # If an IO error occurs, verify that: # # * the call to backup_step() returns an SQLITE_IOERR_XXX error code. # # * after the failed call to backup_step() but before the call to # backup_finish() the destination database handle error code and # error message remain unchanged. # # * the call to backup_finish() returns an SQLITE_IOERR_XXX error code. # # * following the call to backup_finish(), the destination database # handle has been populated with an error code and error message. # # 4) Write to the database via the source database connection. Check # that: # # * If an IO error occurs while writing the source database, the # write operation should report an IO error. The backup should # proceed as normal. # # * If an IO error occurs while updating the backup, the write # operation should proceed normally. The error should be reported # from the next call to backup_step() (in step 5 of this test # procedure). # # 5) Step the backup process to finish the backup. If an IO error is # reported, then the backup process is concluded with a call to # backup_finish(). # # Test that if an IO error occurs, or if one occured while updating # the backup database during step 4, then the conditions listed # under step 3 are all true. # # 6) Finish the backup process. # # * If the backup succeeds (backup_finish() returns SQLITE_OK), then # the contents of the backup database should match that of the # source database. # # * If the backup fails (backup_finish() returns other than SQLITE_OK), # then the contents of the backup database should be as they were # before the operation was started. # # The following factors are varied: # # * Destination database is initially larger than the source database, OR # * Destination database is initially smaller than the source database. # # * IO errors are transient, OR # * IO errors are persistent. # # * Destination page-size is smaller than the source. # * Destination page-size is the same as the source. # * Destination page-size is larger than the source. # set iTest 1 foreach bPersist {0 1} { foreach iDestPagesize {512 1024 4096} { foreach zSetupBak [list "" {populate_database ddb 1}] { incr iTest set bStop 0 for {set iError 1} {$bStop == 0} {incr iError} { # Disable IO error simulation. clear_ioerr_simulation catch { ddb close } catch { sdb close } catch { forcedelete test.db } catch { forcedelete bak.db } # Open the source and destination databases. sqlite3 sdb test.db sqlite3 ddb bak.db # Step 1: Populate the source and destination databases. populate_database sdb ddb eval "PRAGMA page_size = $iDestPagesize" ddb eval "PRAGMA cache_size = 10" eval $zSetupBak # Step 2: Open the backup process. sqlite3_backup B ddb main sdb main # Enable IO error simulation. set ::sqlite_io_error_pending $iError set ::sqlite_io_error_persist $bPersist # Step 3: Partially backup the database. If an IO error occurs, check # a few things then skip to the next iteration of the loop. # set rc [B step 100] if {$::sqlite_io_error_hardhit} { do_test backup_ioerr-$iTest.$iError.1 { string match SQLITE_IOERR* $rc } {1} do_test backup_ioerr-$iTest.$iError.2 { list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] } {SQLITE_OK {not an error}} set rc [B finish] do_test backup_ioerr-$iTest.$iError.3 { string match SQLITE_IOERR* $rc } {1} do_test backup_ioerr-$iTest.$iError.4 { sqlite3_errmsg ddb } {disk I/O error} clear_ioerr_simulation sqlite3 ddb bak.db integrity_check backup_ioerr-$iTest.$iError.5 ddb continue } # No IO error was encountered during step 3. Check that backup_step() # returned SQLITE_OK before proceding. do_test backup_ioerr-$iTest.$iError.6 { expr {$rc eq "SQLITE_OK"} } {1} # Step 4: Write to the source database. set rc [catchsql { UPDATE t1 SET b = randstr(1000,1000) WHERE a < 50 } sdb] if {[lindex $rc 0] && $::sqlite_io_error_persist==0} { # The IO error occured while updating the source database. In this # case the backup should be able to continue. set rc [B step 5000] if { $rc != "SQLITE_IOERR_UNLOCK" } { do_test backup_ioerr-$iTest.$iError.7 { list [B step 5000] [B finish] } {SQLITE_DONE SQLITE_OK} clear_ioerr_simulation test_contents backup_ioerr-$iTest.$iError.8 ddb main sdb main integrity_check backup_ioerr-$iTest.$iError.9 ddb } else { do_test backup_ioerr-$iTest.$iError.10 { B finish } {SQLITE_IOERR_UNLOCK} } clear_ioerr_simulation sqlite3 ddb bak.db integrity_check backup_ioerr-$iTest.$iError.11 ddb continue } # Step 5: Finish the backup operation. If an IO error occurs, check that # it is reported correctly and skip to the next iteration of the loop. # set rc [B step 5000] if {$rc != "SQLITE_DONE"} { do_test backup_ioerr-$iTest.$iError.12 { string match SQLITE_IOERR* $rc } {1} do_test backup_ioerr-$iTest.$iError.13 { list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] } {SQLITE_OK {not an error}} set rc [B finish] do_test backup_ioerr-$iTest.$iError.14 { string match SQLITE_IOERR* $rc } {1} do_test backup_ioerr-$iTest.$iError.15 { sqlite3_errmsg ddb } {disk I/O error} clear_ioerr_simulation sqlite3 ddb bak.db integrity_check backup_ioerr-$iTest.$iError.16 ddb continue } # The backup was successfully completed. # do_test backup_ioerr-$iTest.$iError.17 { list [set rc] [B finish] } {SQLITE_DONE SQLITE_OK} clear_ioerr_simulation sqlite3 sdb test.db sqlite3 ddb bak.db test_contents backup_ioerr-$iTest.$iError.18 ddb main sdb main integrity_check backup_ioerr-$iTest.$iError.19 ddb set bStop [expr $::sqlite_io_error_pending<=0] }}}} catch { sdb close } catch { ddb close } finish_test