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
23if 1 {
24
25forcedelete test.db2
26sqlite3 db2 test.db2
27do_common_sql {
28  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
29  INSERT INTO t1 VALUES(1, 2, 3);
30  INSERT INTO t1 VALUES(4, 5, 6);
31}
32faultsim_save_and_close
33db2 close
34
35
36#-------------------------------------------------------------------------
37# Test OOM error handling when collecting and applying a simple changeset.
38#
39# Test 1.1 attaches tables individually by name to the session object.
40# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
41# tables.
42#
43do_faultsim_test 1.1 -faults oom-* -prep {
44  catch {db2 close}
45  catch {db close}
46  faultsim_restore_and_reopen
47  sqlite3 db2 test.db2
48} -body {
49  do_then_apply_sql {
50    INSERT INTO t1 VALUES('a string value', 8, 9);
51    UPDATE t1 SET c = 10 WHERE a = 1;
52    DELETE FROM t1 WHERE a = 4;
53  }
54} -test {
55  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
56  faultsim_integrity_check
57  if {$testrc==0} { compare_db db db2 }
58}
59
60do_faultsim_test 1.2 -faults oom-* -prep {
61  catch {db2 close}
62  catch {db close}
63  faultsim_restore_and_reopen
64} -body {
65  sqlite3session S db main
66  S attach *
67  execsql {
68    INSERT INTO t1 VALUES('a string value', 8, 9);
69    UPDATE t1 SET c = 10 WHERE a = 1;
70    DELETE FROM t1 WHERE a = 4;
71  }
72  set ::changeset [S changeset]
73  set {} {}
74} -test {
75  catch { S delete }
76  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
77  faultsim_integrity_check
78  if {$testrc==0} {
79    proc xConflict {args} { return "OMIT" }
80    sqlite3 db2 test.db2
81    sqlite3changeset_apply db2 $::changeset xConflict
82    compare_db db db2
83  }
84}
85
86#-------------------------------------------------------------------------
87# The following block of tests - 2.* - are designed to check
88# the handling of faults in the sqlite3changeset_apply() function.
89#
90catch {db close}
91catch {db2 close}
92forcedelete test.db2 test.db
93sqlite3 db2 test.db2
94sqlite3 db test.db
95do_common_sql {
96  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
97  INSERT INTO t1 VALUES('apple', 'orange', 'pear');
98
99  CREATE TABLE t2(x PRIMARY KEY, y);
100}
101db2 close
102faultsim_save_and_close
103
104
105foreach {tn conflict_policy sql sql2} {
106  1 OMIT { INSERT INTO t1 VALUES('one text', 'two text', X'00ff00') } {}
107  2 OMIT { DELETE FROM t1 WHERE a = 'apple' }                         {}
108  3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' }            {}
109  4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
110    INSERT INTO t2 VALUES('keyvalue', 'value 2');
111  }
112} {
113  proc xConflict args [list return $conflict_policy]
114
115  do_faultsim_test 2.$tn -faults oom-transient -prep {
116    catch {db2 close}
117    catch {db close}
118    faultsim_restore_and_reopen
119    set ::changeset [changeset_from_sql $::sql]
120    sqlite3 db2 test.db2
121    sqlite3_db_config_lookaside db2 0 0 0
122    execsql $::sql2 db2
123  } -body {
124    sqlite3changeset_apply db2 $::changeset xConflict
125  } -test {
126    faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
127    faultsim_integrity_check
128    if {$testrc==0} { compare_db db db2 }
129  }
130}
131
132#-------------------------------------------------------------------------
133# This test case is designed so that a malloc() failure occurs while
134# resizing the session object hash-table from 256 to 512 buckets. This
135# is not an error, just a sub-optimal condition.
136#
137do_faultsim_test 3 -faults oom-* -prep {
138  catch {db2 close}
139  catch {db close}
140  faultsim_restore_and_reopen
141  sqlite3 db2 test.db2
142
143  sqlite3session S db main
144  S attach t1
145  execsql { BEGIN }
146  for {set i 0} {$i < 125} {incr i} {
147    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
148  }
149} -body {
150  for {set i 125} {$i < 133} {incr i} {
151    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
152  }
153  S changeset
154  set {} {}
155} -test {
156  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
157  if {$testrc==0} {
158    sqlite3changeset_apply db2 [S changeset] xConflict
159    compare_db db db2
160  }
161  catch { S delete }
162  faultsim_integrity_check
163}
164
165catch { db close }
166catch { db2 close }
167forcedelete test.db2 test.db
168sqlite3 db2 test.db2
169sqlite3 db test.db
170
171proc xConflict {op tbl type args} {
172  if { $type=="CONFLICT" || $type=="DATA" } {
173    return "REPLACE"
174  }
175  return "OMIT"
176}
177
178do_test 4.0 {
179  execsql {
180    PRAGMA encoding = 'utf16';
181    CREATE TABLE t1(a PRIMARY KEY, b);
182    INSERT INTO t1 VALUES(5, 32);
183  }
184  execsql {
185    PRAGMA encoding = 'utf16';
186    CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
187    INSERT INTO t1 VALUES(1, 2);
188    INSERT INTO t1 VALUES(2, 4);
189    INSERT INTO t1 VALUES(4, 16);
190  } db2
191} {}
192
193faultsim_save_and_close
194db2 close
195
196do_faultsim_test 4 -faults oom-* -prep {
197  catch {db2 close}
198  catch {db close}
199  faultsim_restore_and_reopen
200  sqlite3 db2 test.db2
201  sqlite3session S db main
202  S attach t1
203  execsql {
204    INSERT INTO t1 VALUES(1, 45);
205    INSERT INTO t1 VALUES(2, 55);
206    INSERT INTO t1 VALUES(3, 55);
207    UPDATE t1 SET a = 4 WHERE a = 5;
208  }
209} -body {
210  sqlite3changeset_apply db2 [S changeset] xConflict
211} -test {
212  catch { S delete }
213  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
214  if {$testrc==0} { compare_db db db2 }
215}
216
217#-------------------------------------------------------------------------
218# This block of tests verifies that OOM faults in the
219# sqlite3changeset_invert() function are handled correctly.
220#
221catch {db close}
222catch {db2 close}
223forcedelete test.db
224sqlite3 db test.db
225execsql {
226  CREATE TABLE t1(a, b, PRIMARY KEY(b));
227  CREATE TABLE t2(a PRIMARY KEY, b);
228  INSERT INTO t1 VALUES('string', 1);
229  INSERT INTO t1 VALUES(4, 2);
230  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
231}
232set changeset [changeset_from_sql {
233  INSERT INTO t1 VALUES('xxx', 'yyy');
234  DELETE FROM t1 WHERE a = 'string';
235  UPDATE t1 SET a = 20 WHERE b = 2;
236}]
237db close
238
239do_faultsim_test 5 -faults oom* -body {
240  set ::inverse [sqlite3changeset_invert $::changeset]
241  set {} {}
242} -test {
243  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
244  if {$testrc==0} {
245    set x [list]
246    sqlite3session_foreach c $::inverse { lappend x $c }
247    foreach c {
248        {DELETE t1 0 .X {t xxx t yyy} {}}
249        {INSERT t1 0 .X {} {t string i 1}}
250        {UPDATE t1 0 .X {i 20 {} {}} {i 4 i 2}}
251    } { lappend y $c }
252    if {$x != $y} { error "changeset no good" }
253  }
254}
255
256#-------------------------------------------------------------------------
257# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
258#
259catch {db close}
260forcedelete test.db
261sqlite3 db test.db
262do_execsql_test 5.prep1 {
263  CREATE TABLE t1(a, b, PRIMARY KEY(b));
264  CREATE TABLE t2(a PRIMARY KEY, b);
265  INSERT INTO t1 VALUES('string', 1);
266  INSERT INTO t1 VALUES(4, 2);
267  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
268}
269
270do_test 6.prep2 {
271  sqlite3session M db main
272  M attach *
273  set ::c2 [changeset_from_sql {
274    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
275    INSERT INTO t2 VALUES('one', 'two');
276    INSERT INTO t2 VALUES(1, NULL);
277    UPDATE t1 SET a = 5 WHERE a = 2;
278  }]
279  set ::c1 [changeset_from_sql {
280    DELETE FROM t2 WHERE a = 1;
281    UPDATE t1 SET a = 4 WHERE a = 2;
282    INSERT INTO t2 VALUES('x', 'y');
283  }]
284  set ::total [changeset_to_list [M changeset]]
285  M delete
286} {}
287
288do_faultsim_test 6 -faults oom-* -body {
289  set ::result [sqlite3changeset_concat $::c1 $::c2]
290  set {} {}
291} -test {
292  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
293  if {$testrc==0} {
294    set v [changeset_to_list $::result]
295    if {$v != $::total} { error "result no good" }
296  }
297}
298
299faultsim_delete_and_reopen
300do_execsql_test 5.prep1 {
301  CREATE TABLE t1(a, b, PRIMARY KEY(a));
302}
303faultsim_save_and_close
304
305set res [list]
306for {set ::i 0} {$::i < 480} {incr ::i 4} {
307  lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
308}
309set res [lsort $res]
310do_faultsim_test 7 -faults oom-transient -prep {
311  faultsim_restore_and_reopen
312  sqlite3session S db main
313  S attach *
314} -body {
315  for {set ::i 0} {$::i < 480} {incr ::i 4} {
316    execsql {INSERT INTO t1 VALUES($::i, $::i)}
317  }
318} -test {
319  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
320  if {$testrc==0} {
321    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
322    S delete
323    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
324      error "Expected {0 $::res} Got {$cres}"
325    }
326  } else {
327    S changeset
328    S delete
329  }
330}
331
332faultsim_delete_and_reopen
333do_test 8.prep {
334  sqlite3session S db main
335  S attach *
336  execsql {
337    CREATE TABLE t1(a, b, PRIMARY KEY(a));
338    INSERT INTO t1 VALUES(1, 2);
339    INSERT INTO t1 VALUES(3, 4);
340    INSERT INTO t1 VALUES(5, 6);
341  }
342  set ::changeset [S changeset]
343  S delete
344} {}
345
346set expected [normalize_list {
347  {INSERT t1 0 X. {} {i 1 i 2}}
348  {INSERT t1 0 X. {} {i 3 i 4}}
349  {INSERT t1 0 X. {} {i 5 i 6}}
350}]
351do_faultsim_test 8.1 -faults oom* -body {
352  set ::res [list]
353  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
354  normalize_list $::res
355} -test {
356  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
357}
358do_faultsim_test 8.2 -faults oom* -body {
359  set ::res [list]
360  sqlite3session_foreach v $::changeset { lappend ::res $v }
361  normalize_list $::res
362} -test {
363  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
364}
365
366faultsim_delete_and_reopen
367do_test 9.1.prep {
368  execsql {
369    PRAGMA encoding = 'utf16';
370    CREATE TABLE t1(a PRIMARY KEY, b);
371  }
372} {}
373faultsim_save_and_close
374
375set answers [list {0 {}} {1 SQLITE_NOMEM} {1 {callback requested query abort}}]
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
400}
401
402faultsim_delete_and_reopen
403do_test 9.2.prep {
404  execsql {
405    PRAGMA encoding = 'utf16';
406    CREATE TABLE t1(a PRIMARY KEY, b);
407    INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
408  }
409} {}
410faultsim_save_and_close
411
412set answers [list {0 {}} {1 SQLITE_NOMEM} {1 {callback requested query abort}}]
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
439finish_test
440