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 4 i 2}}
249    } { lappend y $c }
250    if {$x != $y} { error "changeset no good" }
251  }
252}
253
254finish_test
255