summaryrefslogtreecommitdiff
path: root/test/backup_ioerr.test
diff options
context:
space:
mode:
Diffstat (limited to 'test/backup_ioerr.test')
-rw-r--r--test/backup_ioerr.test286
1 files changed, 286 insertions, 0 deletions
diff --git a/test/backup_ioerr.test b/test/backup_ioerr.test
new file mode 100644
index 0000000..313cff3
--- /dev/null
+++ b/test/backup_ioerr.test
@@ -0,0 +1,286 @@
+# 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.3 2009/04/10 18:41:01 danielk1977 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