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.1.1.1 2012/02/21 17:04:16 misho 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: forcedelete 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 { forcedelete test.db }
159: catch { forcedelete 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
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>