• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17set testdir [file dirname $argv0]
18source $testdir/tester.tcl
19
20proc data_checksum {db file} {
21  $db one "SELECT md5sum(a, b) FROM ${file}.t1"
22}
23proc 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#
35proc 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}
53do_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}
58do_test backup_ioerr-1.2 {
59  expr {[file size test.db] > $sqlite_pending_byte}
60} {1}
61do_test backup_ioerr-1.3 {
62  db close
63  file delete -force test.db
64} {}
65
66# Turn off IO error simulation.
67#
68proc 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
145set iTest 1
146foreach bPersist {0 1} {
147foreach iDestPagesize {512 1024 4096} {
148foreach zSetupBak [list "" {populate_database ddb 1}] {
149
150  incr iTest
151  set bStop 0
152for {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
284catch { sdb close }
285catch { ddb close }
286finish_test
287