1 # 2009 January 30 2 # 3 # The author disclaims copyright to this source code. In place of 4 # a legal notice, here is a blessing: 5 # 6 # May you do good and not evil. 7 # May you find forgiveness for yourself and forgive others. 8 # May you share freely, never taking more than you give. 9 # 10 #*********************************************************************** 11 # This file implements regression tests for SQLite library. The 12 # focus of this file is testing the handling of IO errors by the 13 # sqlite3_backup_XXX APIs. 14 # 15 # $Id: backup_ioerr.test,v 1.3 2009/04/10 18:41:01 danielk1977 Exp $ 16 17 set testdir [file dirname $argv0] 18 source $testdir/tester.tcl 19 20 proc data_checksum {db file} { 21 $db one "SELECT md5sum(a, b) FROM ${file}.t1" 22 } 23 proc test_contents {name db1 file1 db2 file2} { 24 $db2 eval {select * from sqlite_master} 25 $db1 eval {select * from sqlite_master} 26 set checksum [data_checksum $db2 $file2] 27 uplevel [list do_test $name [list data_checksum $db1 $file1] $checksum] 28 } 29 30 #-------------------------------------------------------------------- 31 # This proc creates a database of approximately 290 pages. Depending 32 # on whether or not auto-vacuum is configured. Test cases backup_ioerr-1.* 33 # verify nothing more than this assumption. 34 # 35 proc populate_database {db {xtra_large 0}} { 36 execsql { 37 BEGIN; 38 CREATE TABLE t1(a, b); 39 INSERT INTO t1 VALUES(1, randstr(1000,1000)); 40 INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1; 41 INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1; 42 INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1; 43 INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1; 44 INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1; 45 INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1; 46 CREATE INDEX i1 ON t1(b); 47 COMMIT; 48 } $db 49 if {$xtra_large} { 50 execsql { INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1 } $db 51 } 52 } 53 do_test backup_ioerr-1.1 { 54 populate_database db 55 set nPage [expr {[file size test.db] / 1024}] 56 expr {$nPage>130 && $nPage<160} 57 } {1} 58 do_test backup_ioerr-1.2 { 59 expr {[file size test.db] > $sqlite_pending_byte} 60 } {1} 61 do_test backup_ioerr-1.3 { 62 db close 63 file delete -force test.db 64 } {} 65 66 # Turn off IO error simulation. 67 # 68 proc clear_ioerr_simulation {} { 69 set ::sqlite_io_error_hit 0 70 set ::sqlite_io_error_hardhit 0 71 set ::sqlite_io_error_pending 0 72 set ::sqlite_io_error_persist 0 73 } 74 75 #-------------------------------------------------------------------- 76 # The following procedure runs with SQLite's IO error simulation 77 # enabled. 78 # 79 # 1) Start with a reasonably sized database. One that includes the 80 # pending-byte (locking) page. 81 # 82 # 2) Open a backup process. Set the cache-size for the destination 83 # database to 10 pages only. 84 # 85 # 3) Step the backup process N times to partially backup the database 86 # file. If an IO error is reported, then the backup process is 87 # concluded with a call to backup_finish(). 88 # 89 # If an IO error occurs, verify that: 90 # 91 # * the call to backup_step() returns an SQLITE_IOERR_XXX error code. 92 # 93 # * after the failed call to backup_step() but before the call to 94 # backup_finish() the destination database handle error code and 95 # error message remain unchanged. 96 # 97 # * the call to backup_finish() returns an SQLITE_IOERR_XXX error code. 98 # 99 # * following the call to backup_finish(), the destination database 100 # handle has been populated with an error code and error message. 101 # 102 # 4) Write to the database via the source database connection. Check 103 # that: 104 # 105 # * If an IO error occurs while writing the source database, the 106 # write operation should report an IO error. The backup should 107 # proceed as normal. 108 # 109 # * If an IO error occurs while updating the backup, the write 110 # operation should proceed normally. The error should be reported 111 # from the next call to backup_step() (in step 5 of this test 112 # procedure). 113 # 114 # 5) Step the backup process to finish the backup. If an IO error is 115 # reported, then the backup process is concluded with a call to 116 # backup_finish(). 117 # 118 # Test that if an IO error occurs, or if one occured while updating 119 # the backup database during step 4, then the conditions listed 120 # under step 3 are all true. 121 # 122 # 6) Finish the backup process. 123 # 124 # * If the backup succeeds (backup_finish() returns SQLITE_OK), then 125 # the contents of the backup database should match that of the 126 # source database. 127 # 128 # * If the backup fails (backup_finish() returns other than SQLITE_OK), 129 # then the contents of the backup database should be as they were 130 # before the operation was started. 131 # 132 # The following factors are varied: 133 # 134 # * Destination database is initially larger than the source database, OR 135 # * Destination database is initially smaller than the source database. 136 # 137 # * IO errors are transient, OR 138 # * IO errors are persistent. 139 # 140 # * Destination page-size is smaller than the source. 141 # * Destination page-size is the same as the source. 142 # * Destination page-size is larger than the source. 143 # 144 145 set iTest 1 146 foreach bPersist {0 1} { 147 foreach iDestPagesize {512 1024 4096} { 148 foreach zSetupBak [list "" {populate_database ddb 1}] { 149 150 incr iTest 151 set bStop 0 152 for {set iError 1} {$bStop == 0} {incr iError} { 153 # Disable IO error simulation. 154 clear_ioerr_simulation 155 156 catch { ddb close } 157 catch { sdb close } 158 catch { file delete -force test.db } 159 catch { file delete -force bak.db } 160 161 # Open the source and destination databases. 162 sqlite3 sdb test.db 163 sqlite3 ddb bak.db 164 165 # Step 1: Populate the source and destination databases. 166 populate_database sdb 167 ddb eval "PRAGMA page_size = $iDestPagesize" 168 ddb eval "PRAGMA cache_size = 10" 169 eval $zSetupBak 170 171 # Step 2: Open the backup process. 172 sqlite3_backup B ddb main sdb main 173 174 # Enable IO error simulation. 175 set ::sqlite_io_error_pending $iError 176 set ::sqlite_io_error_persist $bPersist 177 178 # Step 3: Partially backup the database. If an IO error occurs, check 179 # a few things then skip to the next iteration of the loop. 180 # 181 set rc [B step 100] 182 if {$::sqlite_io_error_hardhit} { 183 184 do_test backup_ioerr-$iTest.$iError.1 { 185 string match SQLITE_IOERR* $rc 186 } {1} 187 do_test backup_ioerr-$iTest.$iError.2 { 188 list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] 189 } {SQLITE_OK {not an error}} 190 191 set rc [B finish] 192 do_test backup_ioerr-$iTest.$iError.3 { 193 string match SQLITE_IOERR* $rc 194 } {1} 195 196 do_test backup_ioerr-$iTest.$iError.4 { 197 sqlite3_errmsg ddb 198 } {disk I/O error} 199 200 clear_ioerr_simulation 201 sqlite3 ddb bak.db 202 integrity_check backup_ioerr-$iTest.$iError.5 ddb 203 204 continue 205 } 206 207 # No IO error was encountered during step 3. Check that backup_step() 208 # returned SQLITE_OK before proceding. 209 do_test backup_ioerr-$iTest.$iError.6 { 210 expr {$rc eq "SQLITE_OK"} 211 } {1} 212 213 # Step 4: Write to the source database. 214 set rc [catchsql { UPDATE t1 SET b = randstr(1000,1000) WHERE a < 50 } sdb] 215 216 if {[lindex $rc 0] && $::sqlite_io_error_persist==0} { 217 # The IO error occured while updating the source database. In this 218 # case the backup should be able to continue. 219 set rc [B step 5000] 220 if { $rc != "SQLITE_IOERR_UNLOCK" } { 221 do_test backup_ioerr-$iTest.$iError.7 { 222 list [B step 5000] [B finish] 223 } {SQLITE_DONE SQLITE_OK} 224 225 clear_ioerr_simulation 226 test_contents backup_ioerr-$iTest.$iError.8 ddb main sdb main 227 integrity_check backup_ioerr-$iTest.$iError.9 ddb 228 } else { 229 do_test backup_ioerr-$iTest.$iError.10 { 230 B finish 231 } {SQLITE_IOERR_UNLOCK} 232 } 233 234 clear_ioerr_simulation 235 sqlite3 ddb bak.db 236 integrity_check backup_ioerr-$iTest.$iError.11 ddb 237 238 continue 239 } 240 241 # Step 5: Finish the backup operation. If an IO error occurs, check that 242 # it is reported correctly and skip to the next iteration of the loop. 243 # 244 set rc [B step 5000] 245 if {$rc != "SQLITE_DONE"} { 246 do_test backup_ioerr-$iTest.$iError.12 { 247 string match SQLITE_IOERR* $rc 248 } {1} 249 do_test backup_ioerr-$iTest.$iError.13 { 250 list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] 251 } {SQLITE_OK {not an error}} 252 253 set rc [B finish] 254 do_test backup_ioerr-$iTest.$iError.14 { 255 string match SQLITE_IOERR* $rc 256 } {1} 257 do_test backup_ioerr-$iTest.$iError.15 { 258 sqlite3_errmsg ddb 259 } {disk I/O error} 260 261 clear_ioerr_simulation 262 sqlite3 ddb bak.db 263 integrity_check backup_ioerr-$iTest.$iError.16 ddb 264 265 continue 266 } 267 268 # The backup was successfully completed. 269 # 270 do_test backup_ioerr-$iTest.$iError.17 { 271 list [set rc] [B finish] 272 } {SQLITE_DONE SQLITE_OK} 273 274 clear_ioerr_simulation 275 sqlite3 sdb test.db 276 sqlite3 ddb bak.db 277 278 test_contents backup_ioerr-$iTest.$iError.18 ddb main sdb main 279 integrity_check backup_ioerr-$iTest.$iError.19 ddb 280 281 set bStop [expr $::sqlite_io_error_pending<=0] 282 }}}} 283 284 catch { sdb close } 285 catch { ddb close } 286 finish_test 287