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 40 41proc journal_op_catcher {method filename args} { 42 43 # If global variable ::tvfs_error_on_write is defined, then return an 44 # IO error to every attempt to modify the file-system. Otherwise, return 45 # SQLITE_OK. 46 # 47 if {[info exists ::tvfs_error_on_write]} { 48 if {$method == "xDelete" || $method == "xWrite" || $method == "xTruncate"} { 49 return SQLITE_IOERR 50 } 51 return SQLITE_OK 52 } 53 54 if {[string match *journal* $filename]==0} return 55 56 set f [file tail $filename] 57 lappend ::oplog $method $f 58 59 if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 } 60 switch -- $method { 61 xOpen { 62 incr ::open_journals($f) +1 63 } 64 xClose { 65 incr ::open_journals($f) -1 66 } 67 xDelete { 68 if {$::open_journals($f)>0} { return SQLITE_IOERR } 69 } 70 } 71 72 return 73} 74 75 76do_test journal2-1.1 { 77 set ::oplog [list] 78 sqlite3 db test.db 79 execsql { CREATE TABLE t1(a, b) } 80 set ::oplog 81} {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal} 82do_test journal2-1.2 { 83 set ::oplog [list] 84 execsql { 85 PRAGMA journal_mode = truncate; 86 INSERT INTO t1 VALUES(1, 2); 87 } 88 set ::oplog 89} {xOpen test.db-journal} 90do_test journal2-1.3 { 91 set ::oplog [list] 92 execsql { INSERT INTO t1 VALUES(3, 4) } 93 set ::oplog 94} {} 95do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4} 96 97# Add a second connection. This connection attempts to commit data in 98# journal_mode=DELETE mode. When it tries to delete the journal file, 99# the VFS layer returns an IO error. 100# 101do_test journal2-1.5 { 102 set ::oplog [list] 103 sqlite3 db2 test.db 104 execsql { PRAGMA journal_mode = delete } db2 105 catchsql { INSERT INTO t1 VALUES(5, 6) } db2 106} {1 {disk I/O error}} 107do_test journal2-1.6 { file exists test.db-journal } 1 108do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4} 109do_test journal2-1.8 { 110 execsql { PRAGMA journal_mode = truncate } db2 111 execsql { INSERT INTO t1 VALUES(5, 6) } db2 112} {} 113do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6} 114 115# Grow the database until it is reasonably large. Then, from a 116# journal_mode=DELETE connection, attempt to commit a large transaction (one 117# that involves upgrading to an exclusive lock and writing the database 118# before the transaction is committed). 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 140do_test journal2-1.12 { 141 sqlite3 db2 test.db 142 execsql { 143 PRAGMA cache_size = 10; 144 BEGIN; 145 INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2; -- 128 146 } db2 147} {} 148do_test journal2-1.13 { 149 tvfs filter {xOpen xClose xDelete xWrite xTruncate} 150 set ::tvfs_error_on_write 1 151 catchsql { COMMIT } db2 152} {1 {disk I/O error}} 153db2 close 154unset ::tvfs_error_on_write 155file copy -force test.db testX.db 156 157do_test journal2-1.14 { file exists test.db-journal } 1 158do_test journal2-1.15 { 159 execsql { 160 SELECT count(*) FROM t2; 161 PRAGMA integrity_check; 162 } 163} {64 ok} 164 165# This block checks that in the test case above, connection [db2] really 166# did begin writing to the database file before it hit IO errors. If 167# this is true, then the copy of the database file made before [db] 168# rolled back the hot journal should fail the integrity-check. 169# 170do_test journal2-1.16 { 171 set sz [expr [file size testX.db] / 1024] 172 expr {$sz>240 && $sz<400} 173} 1 174do_test journal2-1.17 { 175 expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"} 176} {1} 177do_test journal2-1.20 { 178 sqlite3 db2 testX.db 179 expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"} 180} {0} 181do_test journal2-1.21 { 182 db2 close 183} {} 184 185db close 186tvfs delete 187finish_test 188 189