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