OLD | NEW |
| (Empty) |
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 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 occurred 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 occurred 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 | |
OLD | NEW |