danielk1977 | 0410302 | 2009-02-03 16:51:24 +0000 | [diff] [blame^] | 1 | # 2008 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.1 2009/02/03 16:51:25 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>140 && $nPage<150} |
| 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 | |