Home | History | Annotate | Download | only in test
      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