1# 2010 June 16 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 14set testdir [file dirname $argv0] 15source $testdir/tester.tcl 16source $testdir/lock_common.tcl 17source $testdir/malloc_common.tcl 18db close 19 20set a_string_counter 1 21proc a_string {n} { 22 global a_string_counter 23 incr a_string_counter 24 string range [string repeat "${a_string_counter}." $n] 1 $n 25} 26 27# Create a [testvfs] and install it as the default VFS. Set the device 28# characteristics flags to "SAFE_DELETE". 29# 30testvfs tvfs -default 1 31tvfs devchar safe_delete 32 33# Set up a hook so that each time a journal file is opened, closed or 34# deleted, the method name ("xOpen", "xClose" or "xDelete") and the final 35# segment of the journal file-name (i.e. "test.db-journal") are appended to 36# global list variable $::oplog. 37# 38tvfs filter {xOpen xClose xDelete} 39tvfs script journal_op_catcher 40proc journal_op_catcher {method filename args} { 41 42 # If global variable ::tvfs_error_on_write is defined, then return an 43 # IO error to every attempt to modify the file-system. Otherwise, return 44 # SQLITE_OK. 45 # 46 if {[info exists ::tvfs_error_on_write]} { 47 if {[lsearch {xDelete xWrite xTruncate} $method]>=0} { 48 return SQLITE_IOERR 49 } 50 } 51 52 # The rest of this command only deals with xOpen(), xClose() and xDelete() 53 # operations on journal files. If this invocation does not represent such 54 # an operation, return with no further ado. 55 # 56 set f [file tail $filename] 57 if {[string match *journal $f]==0} return 58 if {[lsearch {xOpen xDelete xClose} $method]<0} return 59 60 # Append a record of this operation to global list variable $::oplog. 61 # 62 lappend ::oplog $method $f 63 64 # If this is an attempt to delete a journal file for which there exists 65 # one ore more open handles, return an error. The code in test_vfs.c 66 # will not invoke the xDelete method of the "real" VFS in this case. 67 # 68 if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 } 69 switch -- $method { 70 xOpen { incr ::open_journals($f) +1 } 71 xClose { incr ::open_journals($f) -1 } 72 xDelete { if {$::open_journals($f)>0} { return SQLITE_IOERR } } 73 } 74 75 return "" 76} 77 78 79do_test journal2-1.1 { 80 set ::oplog [list] 81 sqlite3 db test.db 82 execsql { CREATE TABLE t1(a, b) } 83 set ::oplog 84} {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal} 85do_test journal2-1.2 { 86 set ::oplog [list] 87 execsql { 88 PRAGMA journal_mode = truncate; 89 INSERT INTO t1 VALUES(1, 2); 90 } 91 set ::oplog 92} {xOpen test.db-journal} 93do_test journal2-1.3 { 94 set ::oplog [list] 95 execsql { INSERT INTO t1 VALUES(3, 4) } 96 set ::oplog 97} {} 98do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4} 99 100# Add a second connection. This connection attempts to commit data in 101# journal_mode=DELETE mode. When it tries to delete the journal file, 102# the VFS layer returns an IO error. 103# 104do_test journal2-1.5 { 105 set ::oplog [list] 106 sqlite3 db2 test.db 107 execsql { PRAGMA journal_mode = delete } db2 108 catchsql { INSERT INTO t1 VALUES(5, 6) } db2 109} {1 {disk I/O error}} 110do_test journal2-1.6 { file exists test.db-journal } 1 111do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4} 112do_test journal2-1.8 { 113 execsql { PRAGMA journal_mode = truncate } db2 114 execsql { INSERT INTO t1 VALUES(5, 6) } db2 115} {} 116do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6} 117 118# Grow the database until it is reasonably large. 119# 120do_test journal2-1.10 { 121 db2 close 122 db func a_string a_string 123 execsql { 124 CREATE TABLE t2(a UNIQUE, b UNIQUE); 125 INSERT INTO t2 VALUES(a_string(200), a_string(300)); 126 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 2 127 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 4 128 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 8 129 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 16 130 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 32 131 INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 64 132 } 133 file size test.db-journal 134} {0} 135do_test journal2-1.11 { 136 set sz [expr [file size test.db] / 1024] 137 expr {$sz>120 && $sz<200} 138} 1 139 140# Using new connection [db2] (with journal_mode=DELETE), write a lot of 141# data to the database. So that many pages within the database file are 142# modified before the transaction is committed. 143# 144# Then, enable simulated IO errors in all calls to xDelete, xWrite 145# and xTruncate before committing the transaction and closing the 146# database file. From the point of view of other file-system users, it 147# appears as if the process hosting [db2] unexpectedly exited. 148# 149do_test journal2-1.12 { 150 sqlite3 db2 test.db 151 execsql { 152 PRAGMA cache_size = 10; 153 BEGIN; 154 INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2; -- 128 155 } db2 156} {} 157do_test journal2-1.13 { 158 tvfs filter {xOpen xClose xDelete xWrite xTruncate} 159 set ::tvfs_error_on_write 1 160 catchsql { COMMIT } db2 161} {1 {disk I/O error}} 162db2 close 163unset ::tvfs_error_on_write 164file copy -force test.db testX.db 165 166do_test journal2-1.14 { file exists test.db-journal } 1 167do_test journal2-1.15 { 168 execsql { 169 SELECT count(*) FROM t2; 170 PRAGMA integrity_check; 171 } 172} {64 ok} 173 174# This block checks that in the test case above, connection [db2] really 175# did begin writing to the database file before it hit IO errors. If 176# this is true, then the copy of the database file made before [db] 177# rolled back the hot journal should fail the integrity-check. 178# 179do_test journal2-1.16 { 180 set sz [expr [file size testX.db] / 1024] 181 expr {$sz>240 && $sz<400} 182} 1 183do_test journal2-1.17 { 184 expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"} 185} {1} 186do_test journal2-1.20 { 187 sqlite3 db2 testX.db 188 expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"} 189} {0} 190do_test journal2-1.21 { 191 db2 close 192} {} 193db close 194 195#------------------------------------------------------------------------- 196# Test that it is possible to switch from journal_mode=truncate to 197# journal_mode=WAL on a SAFE_DELETE file-system. SQLite should close and 198# delete the journal file when committing the transaction that switches 199# the system to WAL mode. 200# 201ifcapable wal { 202 do_test journal2-2.1 { 203 faultsim_delete_and_reopen 204 set ::oplog [list] 205 execsql { PRAGMA journal_mode = persist } 206 set ::oplog 207 } {} 208 do_test journal2-2.2 { 209 execsql { 210 CREATE TABLE t1(x); 211 INSERT INTO t1 VALUES(3.14159); 212 } 213 set ::oplog 214 } {xOpen test.db-journal} 215 do_test journal2-2.3 { 216 expr {[file size test.db-journal] > 512} 217 } {1} 218 do_test journal2-2.4 { 219 set ::oplog [list] 220 execsql { PRAGMA journal_mode = WAL } 221 set ::oplog 222 } {xClose test.db-journal xDelete test.db-journal} 223 db close 224} 225 226tvfs delete 227finish_test 228 229