1# 2011 Mar 21 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# 12# The focus of this file is testing the session module. 13# 14 15if {![info exists testdir]} { 16 set testdir [file join [file dirname [info script]] .. .. test] 17} 18source [file join [file dirname [info script]] session_common.tcl] 19source $testdir/tester.tcl 20 21set testprefix sessionfault 22 23forcedelete test.db2 24sqlite3 db2 test.db2 25do_common_sql { 26 CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); 27 INSERT INTO t1 VALUES(1, 2, 3); 28 INSERT INTO t1 VALUES(4, 5, 6); 29} 30faultsim_save_and_close 31db2 close 32 33 34#------------------------------------------------------------------------- 35# Test OOM error handling when collecting and applying a simple changeset. 36# 37# Test 1.1 attaches tables individually by name to the session object. 38# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all 39# tables. 40# 41do_faultsim_test 1.1 -faults oom-* -prep { 42 catch {db2 close} 43 catch {db close} 44 faultsim_restore_and_reopen 45 sqlite3 db2 test.db2 46} -body { 47 do_then_apply_sql { 48 INSERT INTO t1 VALUES('a string value', 8, 9); 49 UPDATE t1 SET c = 10 WHERE a = 1; 50 DELETE FROM t1 WHERE a = 4; 51 } 52} -test { 53 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 54 faultsim_integrity_check 55 if {$testrc==0} { compare_db db db2 } 56} 57 58do_faultsim_test 1.2 -faults oom-* -prep { 59 catch {db2 close} 60 catch {db close} 61 faultsim_restore_and_reopen 62} -body { 63 sqlite3session S db main 64 S attach * 65 execsql { 66 INSERT INTO t1 VALUES('a string value', 8, 9); 67 UPDATE t1 SET c = 10 WHERE a = 1; 68 DELETE FROM t1 WHERE a = 4; 69 } 70 set ::changeset [S changeset] 71 set {} {} 72} -test { 73 catch { S delete } 74 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 75 faultsim_integrity_check 76 if {$testrc==0} { 77 proc xConflict {args} { return "OMIT" } 78 sqlite3 db2 test.db2 79 sqlite3changeset_apply db2 $::changeset xConflict 80 compare_db db db2 81 } 82} 83 84#------------------------------------------------------------------------- 85# The following block of tests - 2.* - are designed to check 86# the handling of faults in the sqlite3changeset_apply() function. 87# 88catch {db close} 89catch {db2 close} 90forcedelete test.db2 test.db 91sqlite3 db2 test.db2 92sqlite3 db test.db 93do_common_sql { 94 CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); 95 INSERT INTO t1 VALUES('apple', 'orange', 'pear'); 96 97 CREATE TABLE t2(x PRIMARY KEY, y); 98} 99db2 close 100faultsim_save_and_close 101 102 103foreach {tn conflict_policy sql sql2} { 104 1 OMIT { INSERT INTO t1 VALUES('one text', 'two text', X'00ff00') } {} 105 2 OMIT { DELETE FROM t1 WHERE a = 'apple' } {} 106 3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' } {} 107 4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } { 108 INSERT INTO t2 VALUES('keyvalue', 'value 2'); 109 } 110} { 111 proc xConflict args [list return $conflict_policy] 112 113 do_faultsim_test 2.$tn -faults oom-transient -prep { 114 catch {db2 close} 115 catch {db close} 116 faultsim_restore_and_reopen 117 set ::changeset [changeset_from_sql $::sql] 118 sqlite3 db2 test.db2 119 sqlite3_db_config_lookaside db2 0 0 0 120 execsql $::sql2 db2 121 } -body { 122 sqlite3changeset_apply db2 $::changeset xConflict 123 } -test { 124 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 125 faultsim_integrity_check 126 if {$testrc==0} { compare_db db db2 } 127 } 128} 129 130#------------------------------------------------------------------------- 131# This test case is designed so that a malloc() failure occurs while 132# resizing the session object hash-table from 256 to 512 buckets. This 133# is not an error, just a sub-optimal condition. 134# 135do_faultsim_test 3 -faults oom-* -prep { 136 catch {db2 close} 137 catch {db close} 138 faultsim_restore_and_reopen 139 sqlite3 db2 test.db2 140 141 sqlite3session S db main 142 S attach t1 143 execsql { BEGIN } 144 for {set i 0} {$i < 125} {incr i} { 145 execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)} 146 } 147} -body { 148 for {set i 125} {$i < 133} {incr i} { 149 execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)} 150 } 151 S changeset 152 set {} {} 153} -test { 154 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 155 if {$testrc==0} { 156 sqlite3changeset_apply db2 [S changeset] xConflict 157 compare_db db db2 158 } 159 catch { S delete } 160 faultsim_integrity_check 161} 162 163catch { db close } 164catch { db2 close } 165forcedelete test.db2 test.db 166sqlite3 db2 test.db2 167sqlite3 db test.db 168 169proc xConflict {op tbl type args} { 170 if { $type=="CONFLICT" || $type=="DATA" } { 171 return "REPLACE" 172 } 173 return "OMIT" 174} 175 176do_test 4.0 { 177 execsql { 178 PRAGMA encoding = 'utf16'; 179 CREATE TABLE t1(a PRIMARY KEY, b); 180 INSERT INTO t1 VALUES(5, 32); 181 } 182 execsql { 183 PRAGMA encoding = 'utf16'; 184 CREATE TABLE t1(a PRIMARY KEY, b NOT NULL); 185 INSERT INTO t1 VALUES(1, 2); 186 INSERT INTO t1 VALUES(2, 4); 187 INSERT INTO t1 VALUES(4, 16); 188 } db2 189} {} 190 191faultsim_save_and_close 192db2 close 193 194do_faultsim_test 4 -faults oom-* -prep { 195 catch {db2 close} 196 catch {db close} 197 faultsim_restore_and_reopen 198 sqlite3 db2 test.db2 199 sqlite3session S db main 200 S attach t1 201 execsql { 202 INSERT INTO t1 VALUES(1, 45); 203 INSERT INTO t1 VALUES(2, 55); 204 INSERT INTO t1 VALUES(3, 55); 205 UPDATE t1 SET a = 4 WHERE a = 5; 206 } 207} -body { 208 sqlite3changeset_apply db2 [S changeset] xConflict 209} -test { 210 catch { S delete } 211 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 212 if {$testrc==0} { compare_db db db2 } 213} 214 215#------------------------------------------------------------------------- 216# This block of tests verifies that OOM faults in the 217# sqlite3changeset_invert() function are handled correctly. 218# 219catch {db close} 220catch {db2 close} 221forcedelete test.db 222sqlite3 db test.db 223execsql { 224 CREATE TABLE t1(a, b, PRIMARY KEY(b)); 225 CREATE TABLE t2(a PRIMARY KEY, b); 226 INSERT INTO t1 VALUES('string', 1); 227 INSERT INTO t1 VALUES(4, 2); 228 INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3); 229} 230set changeset [changeset_from_sql { 231 INSERT INTO t1 VALUES('xxx', 'yyy'); 232 DELETE FROM t1 WHERE a = 'string'; 233 UPDATE t1 SET a = 20 WHERE b = 2; 234}] 235db close 236 237do_faultsim_test 5 -faults oom* -body { 238 set ::inverse [sqlite3changeset_invert $::changeset] 239 set {} {} 240} -test { 241 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 242 if {$testrc==0} { 243 set x [list] 244 sqlite3session_foreach c $::inverse { lappend x $c } 245 foreach c { 246 {DELETE t1 0 .X {t xxx t yyy} {}} 247 {INSERT t1 0 .X {} {t string i 1}} 248 {UPDATE t1 0 .X {i 20 i 2} {i 4 {} {}}} 249 } { lappend y $c } 250 if {$x != $y} { error "changeset no good" } 251 } 252} 253 254#------------------------------------------------------------------------- 255# Test that OOM errors in sqlite3changeset_concat() are handled correctly. 256# 257catch {db close} 258forcedelete test.db 259sqlite3 db test.db 260do_execsql_test 5.prep1 { 261 CREATE TABLE t1(a, b, PRIMARY KEY(b)); 262 CREATE TABLE t2(a PRIMARY KEY, b); 263 INSERT INTO t1 VALUES('string', 1); 264 INSERT INTO t1 VALUES(4, 2); 265 INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3); 266} 267 268do_test 6.prep2 { 269 sqlite3session M db main 270 M attach * 271 set ::c2 [changeset_from_sql { 272 INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000)); 273 INSERT INTO t2 VALUES('one', 'two'); 274 INSERT INTO t2 VALUES(1, NULL); 275 UPDATE t1 SET a = 5 WHERE a = 2; 276 }] 277 set ::c1 [changeset_from_sql { 278 DELETE FROM t2 WHERE a = 1; 279 UPDATE t1 SET a = 4 WHERE a = 2; 280 INSERT INTO t2 VALUES('x', 'y'); 281 }] 282 set ::total [changeset_to_list [M changeset]] 283 M delete 284} {} 285 286do_faultsim_test 6 -faults oom-* -body { 287 set ::result [sqlite3changeset_concat $::c1 $::c2] 288 set {} {} 289} -test { 290 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 291 if {$testrc==0} { 292 set v [changeset_to_list $::result] 293 if {$v != $::total} { error "result no good" } 294 } 295} 296 297faultsim_delete_and_reopen 298do_execsql_test 5.prep1 { 299 CREATE TABLE t1(a, b, PRIMARY KEY(a)); 300} 301faultsim_save_and_close 302 303set res [list] 304for {set ::i 0} {$::i < 480} {incr ::i 4} { 305 lappend res "INSERT t1 0 X. {} {i $::i i $::i}" 306} 307set res [lsort $res] 308do_faultsim_test 7 -faults oom-transient -prep { 309 faultsim_restore_and_reopen 310 sqlite3session S db main 311 S attach * 312} -body { 313 for {set ::i 0} {$::i < 480} {incr ::i 4} { 314 execsql {INSERT INTO t1 VALUES($::i, $::i)} 315 } 316} -test { 317 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 318 if {$testrc==0} { 319 set cres [list [catch {changeset_to_list [S changeset]} msg] $msg] 320 S delete 321 if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} { 322 error "Expected {0 $::res} Got {$cres}" 323 } 324 } else { 325 S changeset 326 S delete 327 } 328} 329 330faultsim_delete_and_reopen 331do_test 8.prep { 332 sqlite3session S db main 333 S attach * 334 execsql { 335 CREATE TABLE t1(a, b, PRIMARY KEY(a)); 336 INSERT INTO t1 VALUES(1, 2); 337 INSERT INTO t1 VALUES(3, 4); 338 INSERT INTO t1 VALUES(5, 6); 339 } 340 set ::changeset [S changeset] 341 S delete 342} {} 343 344set expected [normalize_list { 345 {INSERT t1 0 X. {} {i 1 i 2}} 346 {INSERT t1 0 X. {} {i 3 i 4}} 347 {INSERT t1 0 X. {} {i 5 i 6}} 348}] 349do_faultsim_test 8.1 -faults oom* -body { 350 set ::res [list] 351 sqlite3session_foreach -next v $::changeset { lappend ::res $v } 352 normalize_list $::res 353} -test { 354 faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM} 355} 356do_faultsim_test 8.2 -faults oom* -body { 357 set ::res [list] 358 sqlite3session_foreach v $::changeset { lappend ::res $v } 359 normalize_list $::res 360} -test { 361 faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM} 362} 363 364faultsim_delete_and_reopen 365do_test 9.1.prep { 366 execsql { 367 PRAGMA encoding = 'utf16'; 368 CREATE TABLE t1(a PRIMARY KEY, b); 369 } 370} {} 371faultsim_save_and_close 372 373set answers [list {0 {}} {1 SQLITE_NOMEM} \ 374 {1 {callback requested query abort}} \ 375 {1 {abort due to ROLLBACK}}] 376do_faultsim_test 9.1 -faults oom-transient -prep { 377 catch { unset ::c } 378 faultsim_restore_and_reopen 379 sqlite3session S db main 380 S attach * 381} -body { 382 execsql { 383 INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV'); 384 } 385 set ::c [S changeset] 386 set {} {} 387} -test { 388 S delete 389 eval faultsim_test_result $::answers 390 if {[info exists ::c]} { 391 set expected [normalize_list { 392 {INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}} 393 }] 394 if { [changeset_to_list $::c] != $expected } { 395 error "changeset mismatch" 396 } 397 } 398} 399 400faultsim_delete_and_reopen 401do_test 9.2.prep { 402 execsql { 403 PRAGMA encoding = 'utf16'; 404 CREATE TABLE t1(a PRIMARY KEY, b); 405 INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV'); 406 } 407} {} 408faultsim_save_and_close 409 410set answers [list {0 {}} {1 SQLITE_NOMEM} \ 411 {1 {callback requested query abort}} \ 412 {1 {abort due to ROLLBACK}}] 413do_faultsim_test 9.2 -faults oom-transient -prep { 414 catch { unset ::c } 415 faultsim_restore_and_reopen 416 sqlite3session S db main 417 S attach * 418} -body { 419 execsql { 420 UPDATE t1 SET b = 'xyz'; 421 } 422 set ::c [S changeset] 423 set {} {} 424} -test { 425 S delete 426 eval faultsim_test_result $::answers 427 if {[info exists ::c]} { 428 set expected [normalize_list { 429 {UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}} 430 }] 431 if { [changeset_to_list $::c] != $expected } { 432 error "changeset mismatch" 433 } 434 } 435} 436 437#------------------------------------------------------------------------- 438# Test that if a conflict-handler encounters an OOM in 439# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE 440# anyway, the OOM is picked up by the sessions module. 441set bigstr [string repeat abcdefghij 100] 442faultsim_delete_and_reopen 443do_test 10.prep.1 { 444 execsql { 445 CREATE TABLE t1(a PRIMARY KEY, b); 446 INSERT INTO t1 VALUES($bigstr, $bigstr); 447 } 448 449 sqlite3session S db main 450 S attach * 451 execsql { UPDATE t1 SET b = b||'x' } 452 set C [S changeset] 453 S delete 454 execsql { UPDATE t1 SET b = b||'xyz' } 455} {} 456faultsim_save_and_close 457 458faultsim_restore_and_reopen 459do_test 10.prep.2 { 460 proc xConflict {args} { return "ABORT" } 461 list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg 462} {1 SQLITE_ABORT} 463do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0 464do_test 10.prep.4 { 465 proc xConflict {args} { return "REPLACE" } 466 list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg 467} {0 {}} 468do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1 469db close 470 471do_faultsim_test 10 -faults oom-tra* -prep { 472 faultsim_restore_and_reopen 473} -body { 474 sqlite3changeset_apply_replace_all db $::C 475} -test { 476 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 477 if {$testrc==0} { 478 if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} { 479 error "data does not look right" 480 } 481 } 482} 483 484 485 486 487finish_test 488