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
288#-------------------------------------------------------------------------
289# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
290#
291catch {db close}
292forcedelete test.db
293sqlite3 db test.db
294do_execsql_test 5.prep1 {
295  CREATE TABLE t1(a, b, PRIMARY KEY(b));
296  CREATE TABLE t2(a PRIMARY KEY, b);
297  INSERT INTO t1 VALUES('string', 1);
298  INSERT INTO t1 VALUES(4, 2);
299  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
300}
301
302do_test 6.prep2 {
303  sqlite3session M db main
304  M attach *
305  set ::c2 [changeset_from_sql {
306    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
307    INSERT INTO t2 VALUES('one', 'two');
308    INSERT INTO t2 VALUES(1, NULL);
309    UPDATE t1 SET a = 5 WHERE a = 2;
310  }]
311  set ::c1 [changeset_from_sql {
312    DELETE FROM t2 WHERE a = 1;
313    UPDATE t1 SET a = 4 WHERE a = 2;
314    INSERT INTO t2 VALUES('x', 'y');
315  }]
316  set ::total [changeset_to_list [M changeset]]
317  M delete
318} {}
319
320do_faultsim_test 6 -faults oom-* -body {
321  set ::result [sqlite3changeset_concat $::c1 $::c2]
322  set {} {}
323} -test {
324  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
325  if {$testrc==0} {
326    set v [changeset_to_list $::result]
327    if {$v != $::total} { error "result no good" }
328  }
329}
330
331faultsim_delete_and_reopen
332do_execsql_test 5.prep1 {
333  CREATE TABLE t1(a, b, PRIMARY KEY(a));
334}
335faultsim_save_and_close
336
337set res [list]
338for {set ::i 0} {$::i < 480} {incr ::i 4} {
339  lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
340}
341set res [lsort $res]
342do_faultsim_test 7 -faults oom-transient -prep {
343  faultsim_restore_and_reopen
344  sqlite3session S db main
345  S attach *
346} -body {
347  for {set ::i 0} {$::i < 480} {incr ::i 4} {
348    execsql {INSERT INTO t1 VALUES($::i, $::i)}
349  }
350} -test {
351  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
352  if {$testrc==0} {
353    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
354    S delete
355    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
356      error "Expected {0 $::res} Got {$cres}"
357    }
358  } else {
359    S changeset
360    S delete
361  }
362}
363
364faultsim_delete_and_reopen
365do_test 8.prep {
366  sqlite3session S db main
367  S attach *
368  execsql {
369    CREATE TABLE t1(a, b, PRIMARY KEY(a));
370    INSERT INTO t1 VALUES(1, 2);
371    INSERT INTO t1 VALUES(3, 4);
372    INSERT INTO t1 VALUES(5, 6);
373  }
374  set ::changeset [S changeset]
375  S delete
376} {}
377
378set expected [normalize_list {
379  {INSERT t1 0 X. {} {i 1 i 2}}
380  {INSERT t1 0 X. {} {i 3 i 4}}
381  {INSERT t1 0 X. {} {i 5 i 6}}
382}]
383do_faultsim_test 8.1 -faults oom* -body {
384  set ::res [list]
385  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
386  normalize_list $::res
387} -test {
388  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
389}
390do_faultsim_test 8.2 -faults oom* -body {
391  set ::res [list]
392  sqlite3session_foreach v $::changeset { lappend ::res $v }
393  normalize_list $::res
394} -test {
395  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
396}
397
398faultsim_delete_and_reopen
399do_test 9.1.prep {
400  execsql {
401    PRAGMA encoding = 'utf16';
402    CREATE TABLE t1(a PRIMARY KEY, b);
403  }
404} {}
405faultsim_save_and_close
406
407set answers [list {0 {}} {1 SQLITE_NOMEM} \
408                  {1 {callback requested query abort}} \
409                  {1 {abort due to ROLLBACK}}]
410do_faultsim_test 9.1 -faults oom-transient -prep {
411  catch { unset ::c }
412  faultsim_restore_and_reopen
413  sqlite3session S db main
414  S attach *
415} -body {
416  execsql {
417    INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
418  }
419  set ::c [S changeset]
420  set {} {}
421} -test {
422  S delete
423  eval faultsim_test_result $::answers
424  if {[info exists ::c]} {
425    set expected [normalize_list {
426      {INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
427    }]
428    if { [changeset_to_list $::c] != $expected } {
429      error "changeset mismatch"
430    }
431  }
432}
433
434faultsim_delete_and_reopen
435do_test 9.2.prep {
436  execsql {
437    PRAGMA encoding = 'utf16';
438    CREATE TABLE t1(a PRIMARY KEY, b);
439    INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
440  }
441} {}
442faultsim_save_and_close
443
444set answers [list {0 {}} {1 SQLITE_NOMEM} \
445                  {1 {callback requested query abort}} \
446                  {1 {abort due to ROLLBACK}}]
447do_faultsim_test 9.2 -faults oom-transient -prep {
448  catch { unset ::c }
449  faultsim_restore_and_reopen
450  sqlite3session S db main
451  S attach *
452} -body {
453  execsql {
454    UPDATE t1 SET b = 'xyz';
455  }
456  set ::c [S changeset]
457  set {} {}
458} -test {
459  S delete
460  eval faultsim_test_result $::answers
461  if {[info exists ::c]} {
462    set expected [normalize_list {
463      {UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
464    }]
465    if { [changeset_to_list $::c] != $expected } {
466      error "changeset mismatch"
467    }
468  }
469}
470
471#-------------------------------------------------------------------------
472# Test that if a conflict-handler encounters an OOM in
473# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
474# anyway, the OOM is picked up by the sessions module.
475set bigstr [string repeat abcdefghij 100]
476faultsim_delete_and_reopen
477do_test 10.prep.1  {
478  execsql {
479    CREATE TABLE t1(a PRIMARY KEY, b);
480    INSERT INTO t1 VALUES($bigstr, $bigstr);
481  }
482
483  sqlite3session S db main
484  S attach *
485  execsql { UPDATE t1 SET b = b||'x' }
486  set C [S changeset]
487  S delete
488  execsql { UPDATE t1 SET b = b||'xyz' }
489} {}
490faultsim_save_and_close
491
492faultsim_restore_and_reopen
493do_test 10.prep.2  {
494  proc xConflict {args} { return "ABORT" }
495  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
496} {1 SQLITE_ABORT}
497do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
498do_test 10.prep.4  {
499  proc xConflict {args} { return "REPLACE" }
500  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
501} {0 {}}
502do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
503db close
504
505do_faultsim_test 10 -faults oom-tra* -prep {
506  faultsim_restore_and_reopen
507} -body {
508  sqlite3changeset_apply_replace_all db $::C
509} -test {
510  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
511  if {$testrc==0} {
512    if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
513      error "data does not look right"
514    }
515  }
516}
517
518
519
520
521finish_test
522