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