xref: /sqlite-3.40.0/test/crash.test (revision 290c1948)
1# 2001 September 15
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.
12#
13# The focus of this file is testing the ability of the database to
14# uses its rollback journal to recover intact (no database corruption)
15# from a power failure during the middle of a COMMIT.  The special test
16# module "crashtest" compiled with the special "os_test.c" backend is used.
17# The os_test.c simulates the kind of file corruption that can occur
18# when writes are happening at the moment of power loss.
19#
20# The special crash-test module with its os_test.c backend only works
21# on Unix.
22#
23# $Id: crash.test,v 1.8 2004/08/21 17:54:46 drh Exp $
24
25set testdir [file dirname $argv0]
26source $testdir/tester.tcl
27
28# set repeats 100
29set repeats 10
30
31# This proc execs a seperate process that crashes midway through executing
32# the SQL script $sql on database test.db.
33#
34# The crash occurs during a sync() of file $crashfile. When the crash
35# occurs a random subset of all unsynced writes made by the process are
36# written into the files on disk. Argument $crashdelay indicates the
37# number of file syncs to wait before crashing.
38#
39# The return value is a list of two elements. The first element is a
40# boolean, indicating whether or not the process actually crashed or
41# reported some other error. The second element in the returned list is the
42# error message. This is "child process exited abnormally" if the crash
43# occured.
44proc crashsql {crashdelay crashfile sql} {
45  set cfile [file join [pwd] $crashfile]
46
47  set f [open crash.tcl w]
48  puts $f "sqlite3_crashparams $crashdelay $cfile"
49  puts $f "sqlite3 db test.db"
50  puts $f "db eval {pragma cache_size = 10}"
51  puts $f "db eval {"
52  puts $f   "$sql"
53  puts $f "}"
54  close $f
55
56  set r [catch {
57    exec [file join . crashtest] crash.tcl >@stdout
58  } msg]
59  lappend r $msg
60}
61
62# The following procedure computes a "signature" for table "abc".  If
63# abc changes in any way, the signature should change.
64proc signature {} {
65  return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc}]
66}
67proc signature2 {} {
68  return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc2}]
69}
70
71#--------------------------------------------------------------------------
72# Simple crash test:
73#
74# crash-1.1: Create a database with a table with two rows.
75# crash-1.2: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
76#            the first journal-sync.
77# crash-1.3: Ensure the database is in the same state as after crash-1.1.
78# crash-1.4: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
79#            the first database-sync.
80# crash-1.5: Ensure the database is in the same state as after crash-1.1.
81# crash-1.6: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
82#            the second journal-sync.
83# crash-1.7: Ensure the database is in the same state as after crash-1.1.
84#
85# Tests 1.8 through 1.11 test for crashes on the third journal sync and
86# second database sync.  Neither of these is required in such a small test
87# case, so these tests are just to verify that the test infrastructure
88# operates as expected.
89#
90do_test crash-1.1 {
91  execsql {
92    CREATE TABLE abc(a, b, c);
93    INSERT INTO abc VALUES(1, 2, 3);
94    INSERT INTO abc VALUES(4, 5, 6);
95  }
96  set ::sig [signature]
97  expr 0
98} {0}
99do_test crash-1.2 {
100  crashsql 1 test.db-journal {
101    DELETE FROM abc WHERE a = 1;
102  }
103} {1 {child process exited abnormally}}
104do_test crash-1.3 {
105  signature
106} $::sig
107do_test crash-1.4 {
108  crashsql 1 test.db {
109    DELETE FROM abc WHERE a = 1;
110  }
111} {1 {child process exited abnormally}}
112do_test crash-1.5 {
113  signature
114} $::sig
115do_test crash-1.6 {
116  crashsql 2 test.db-journal {
117    DELETE FROM abc WHERE a = 1;
118  }
119} {1 {child process exited abnormally}}
120do_test crash-1.7 {
121  catchsql {
122    SELECT * FROM abc;
123  }
124} {0 {1 2 3 4 5 6}}
125
126do_test crash-1.8 {
127  crashsql 3 test.db-journal {
128    DELETE FROM abc WHERE a = 1;
129  }
130} {0 {}}
131do_test crash-1.9 {
132  catchsql {
133    SELECT * FROM abc;
134  }
135} {0 {4 5 6}}
136do_test crash-1.10 {
137  crashsql 2 test.db {
138    DELETE FROM abc WHERE a = 4;
139  }
140} {0 {}}
141do_test crash-1.11 {
142  catchsql {
143    SELECT * FROM abc;
144  }
145} {0 {}}
146
147#--------------------------------------------------------------------------
148# The following tests test recovery when both the database file and the the
149# journal file contain corrupt data. This can happen after pages are
150# written to the database file before a transaction is committed due to
151# cache-pressure.
152#
153# crash-2.1: Insert 18 pages of data into the database.
154# crash-2.2: Check the database file size looks ok.
155# crash-2.3: Delete 15 or so pages (with a 10 page page-cache), then crash.
156# crash-2.4: Ensure the database is in the same state as after crash-2.1.
157#
158# Test cases crash-2.5 and crash-2.6 check that the database is OK if the
159# crash occurs during the main database file sync. But this isn't really
160# different from the crash-1.* cases.
161#
162do_test crash-2.1 {
163  execsql { BEGIN }
164  for {set n 0} {$n < 1000} {incr n} {
165    execsql "INSERT INTO abc VALUES($n, [expr 2*$n], [expr 3*$n])"
166  }
167  execsql { COMMIT }
168  set ::sig [signature]
169  execsql { SELECT sum(a), sum(b), sum(c) from abc }
170} {499500.0 999000.0 1498500.0}
171do_test crash-2.2 {
172  expr [file size test.db] / 1024
173} {19}
174do_test crash-2.3 {
175  crashsql 2 test.db-journal {
176    DELETE FROM abc WHERE a < 800;
177  }
178} {1 {child process exited abnormally}}
179do_test crash-2.4 {
180  signature
181} $sig
182do_test crash-2.5 {
183  crashsql 1 test.db {
184    DELETE FROM abc WHERE a<800;
185  }
186} {1 {child process exited abnormally}}
187do_test crash-2.6 {
188  signature
189} $sig
190
191#--------------------------------------------------------------------------
192# The crash-3.* test cases are essentially the same test as test case
193# crash-2.*, but with a more complicated data set.
194#
195# The test is repeated a few times with different seeds for the random
196# number generator in the crashing executable. Because there is no way to
197# seed the random number generator directly, some SQL is added to the test
198# case to 'use up' a different quantity random numbers before the test SQL
199# is executed.
200#
201
202# Make sure the file is much bigger than the pager-cache (10 pages). This
203# ensures that cache-spills happen regularly.
204do_test crash-3.0 {
205  execsql {
206    INSERT INTO abc SELECT * FROM abc;
207    INSERT INTO abc SELECT * FROM abc;
208    INSERT INTO abc SELECT * FROM abc;
209    INSERT INTO abc SELECT * FROM abc;
210    INSERT INTO abc SELECT * FROM abc;
211  }
212  expr [file size test.db] / 1024
213} {554}
214for {set i 1} {$i < $repeats} {incr i} {
215  set sig [signature]
216  do_test crash-3.$i.1 {
217     crashsql [expr $i%5 + 1] test.db-journal "
218       BEGIN;
219       SELECT random() FROM abc LIMIT $i;
220       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
221       DELETE FROM abc WHERE random()%10!=0;
222       COMMIT;
223     "
224  } {1 {child process exited abnormally}}
225  do_test crash-3.$i.2 {
226    signature
227  } $sig
228}
229
230#--------------------------------------------------------------------------
231# The following test cases - crash-4.* - test the correct recovery of the
232# database when a crash occurs during a multi-file transaction.
233#
234# crash-4.1.*: Test recovery when crash occurs during sync() of the
235#              main database journal file.
236# crash-4.2.*: Test recovery when crash occurs during sync() of an
237#              attached database journal file.
238# crash-4.3.*: Test recovery when crash occurs during sync() of the master
239#              journal file.
240#
241do_test crash-4.0 {
242  file delete -force test2.db
243  file delete -force test2.db-journal
244  execsql {
245    ATTACH 'test2.db' AS aux;
246    PRAGMA aux.default_cache_size = 10;
247    CREATE TABLE aux.abc2 AS SELECT 2*a as a, 2*b as b, 2*c as c FROM abc;
248  }
249  expr [file size test2.db] / 1024
250} {559}
251
252for {set i 1} {$i<$repeats} {incr i} {
253  set sig [signature]
254  set sig2 [signature2]
255  do_test crash-4.1.$i.1 {
256     set c [crashsql $i test.db-journal "
257       ATTACH 'test2.db' AS aux;
258       BEGIN;
259       SELECT random() FROM abc LIMIT $i;
260       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
261       DELETE FROM abc WHERE random()%10!=0;
262       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
263       DELETE FROM abc2 WHERE random()%10!=0;
264       COMMIT;
265     "]
266     set c
267  } {1 {child process exited abnormally}}
268  do_test crash-4.1.$i.2 {
269    signature
270  } $sig
271  do_test crash-4.1.$i.3 {
272    signature2
273  } $sig2
274}
275set i 0
276set i 55
277while {[incr i]} {
278  set sig [signature]
279  set sig2 [signature2]
280  set ::fin 0
281  do_test crash-4.2.$i.1 {
282     set c [crashsql $i test2.db-journal "
283       ATTACH 'test2.db' AS aux;
284       BEGIN;
285       SELECT random() FROM abc LIMIT $i;
286       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
287       DELETE FROM abc WHERE random()%10!=0;
288       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
289       DELETE FROM abc2 WHERE random()%10!=0;
290       COMMIT;
291     "]
292     if { $c == {0 {}} } {
293       set ::fin 1
294       set c {1 {child process exited abnormally}}
295     }
296     set c
297  } {1 {child process exited abnormally}}
298  if { $::fin } break
299  do_test crash-4.2.$i.2 {
300    signature
301  } $sig
302  do_test crash-4.2.$i.3 {
303    signature2
304  } $sig2
305}
306for {set i 1} {$i < 5} {incr i} {
307  set sig [signature]
308  set sig2 [signature2]
309  do_test crash-4.3.$i.1 {
310     crashsql 1 test.db-mj* "
311       ATTACH 'test2.db' AS aux;
312       BEGIN;
313       SELECT random() FROM abc LIMIT $i;
314       INSERT INTO abc VALUES(randstr(10,10), 0, 0);
315       DELETE FROM abc WHERE random()%10!=0;
316       INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
317       DELETE FROM abc2 WHERE random()%10!=0;
318       COMMIT;
319     "
320  } {1 {child process exited abnormally}}
321  do_test crash-4.3.$i.2 {
322    signature
323  } $sig
324  do_test crash-4.3.$i.3 {
325    signature2
326  } $sig2
327}
328