4 The author disclaims copyright to this source code. In place of a
5 legal notice, here is a blessing:
7 * May you do good and not evil.
8 * May you find forgiveness for yourself and forgive others.
9 * May you share freely, never taking more than you give.
11 ***********************************************************************
13 A basic test script for sqlite3-worker1.js.
15 Note that the wrapper interface demonstrated in
16 demo-worker1-promiser.js is much easier to use from client code, as it
17 lacks the message-passing acrobatics demonstrated in this file.
21 const T = self.SqliteTestUtil;
22 const SW = new Worker("jswasm/sqlite3-worker1.js");
26 const eOutput = document.querySelector('#test-output');
27 const log = console.log.bind(console);
28 const logHtml = function(cssClass,...args){
29 log.apply(this, args);
30 const ln = document.createElement('div');
31 if(cssClass) ln.classList.add(cssClass);
32 ln.append(document.createTextNode(args.join(' ')));
35 const warn = console.warn.bind(console);
36 const error = console.error.bind(console);
37 const toss = (...args)=>{throw new Error(args.join(' '))};
39 SW.onerror = function(event){
40 error("onerror",event);
46 A queue for callbacks which are to be run in response to async
47 DB commands. See the notes in runTests() for why we need
48 this. The event-handling plumbing of this file requires that
49 any DB command which includes a `messageId` property also have
50 a queued callback entry, as the existence of that property in
51 response payloads is how it knows whether or not to shift an
52 entry off of the queue.
54 const MsgHandlerQueue = {
57 push: function(type,callback){
58 this.queue.push(callback);
59 return type + '-' + (++this.id);
62 return this.queue.shift();
66 const testCount = ()=>{
67 logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms");
70 const logEventResult = function(ev){
71 const evd = ev.result;
72 logHtml(evd.errorClass ? 'error' : '',
73 "runOneTest",ev.messageId,"Worker time =",
74 (ev.workerRespondTime - ev.workerReceivedTime),"ms.",
75 "Round-trip event time =",
76 (performance.now() - ev.departureTime),"ms.",
77 (evd.errorClass ? evd.message : "")//, JSON.stringify(evd)
81 const runOneTest = function(eventType, eventArgs, callback){
82 T.assert(eventArgs && 'object'===typeof eventArgs);
83 /* ^^^ that is for the testing and messageId-related code, not
84 a hard requirement of all of the Worker-exposed APIs. */
85 const messageId = MsgHandlerQueue.push(eventType,function(ev){
87 if(callback instanceof Function){
97 departureTime: performance.now()
99 log("Posting",eventType,"message to worker dbId="+(DbState.id||'default')+':',msg);
103 /** Methods which map directly to onmessage() event.type keys.
104 They get passed the inbound event.data. */
105 const dbMsgHandler = {
107 DbState.id = ev.dbId;
108 log("open result",ev);
111 log("exec result",ev);
113 export: function(ev){
114 log("export result",ev);
117 error("ERROR from the worker:",ev);
120 resultRowTest1: function f(ev){
121 if(undefined === f.counter) f.counter = 0;
122 if(null === ev.rowNumber){
123 /* End of result set. */
124 T.assert(undefined === ev.row)
125 .assert(Array.isArray(ev.columnNames))
126 .assert(ev.columnNames.length);
128 T.assert(ev.rowNumber > 0);
131 //log("exec() result row:",ev);
132 T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b);
137 "The problem" now is that the test results are async. We
138 know, however, that the messages posted to the worker will
139 be processed in the order they are passed to it, so we can
140 create a queue of callbacks to handle them. The problem
141 with that approach is that it's not error-handling
142 friendly, in that an error can cause us to bypass a result
143 handler queue entry. We have to perform some extra
144 acrobatics to account for that.
146 Problem #2 is that we cannot simply start posting events: we
147 first have to post an 'open' event, wait for it to respond, and
148 collect its db ID before continuing. If we don't wait, we may
149 well fire off 10+ messages before the open actually responds.
151 const runTests2 = function(){
152 const mustNotReach = ()=>{
153 throw new Error("This is not supposed to be reached.");
156 sql: ["create table t(a,b);",
157 "insert into t(a,b) values(1,2),(3,4),(5,6)"
159 resultRows: [], columnNames: []
162 T.assert(0===ev.resultRows.length)
163 .assert(0===ev.columnNames.length);
166 sql: 'select a a, b b from t order by a',
167 resultRows: [], columnNames: [], saveSql:[]
170 T.assert(3===ev.resultRows.length)
171 .assert(1===ev.resultRows[0][0])
172 .assert(6===ev.resultRows[2][1])
173 .assert(2===ev.columnNames.length)
174 .assert('b'===ev.columnNames[1]);
176 //if(1){ error("Returning prematurely for testing."); return; }
178 sql: 'select a a, b b from t order by a',
179 resultRows: [], columnNames: [],
183 T.assert(3===ev.resultRows.length)
184 .assert(1===ev.resultRows[0].a)
185 .assert(6===ev.resultRows[2].b)
187 runOneTest('exec',{sql:'intentional_error'}, mustNotReach);
188 // Ensure that the message-handler queue survives ^^^ that error...
192 //rowMode: 'array', // array is the default in the Worker interface
195 T.assert(1 === ev.resultRows.length)
196 .assert(1 === ev.resultRows[0][0]);
199 sql: 'select a a, b b from t order by a',
200 callback: 'resultRowTest1',
203 T.assert(3===dbMsgHandler.resultRowTest1.counter);
204 dbMsgHandler.resultRowTest1.counter = 0;
208 "pragma foreign_keys=0;",
209 // ^^^ arbitrary query with no result columns
210 "select a, b from t order by a desc;",
212 // exec() only honors SELECT results from the first
213 // statement with result columns (regardless of whether
219 const rows = ev.result.resultRows;
220 T.assert(3===rows.length).
223 runOneTest('exec',{sql: 'delete from t where a>3'});
225 sql: 'select count(a) from t',
229 T.assert(1===ev.resultRows.length)
230 .assert(2===ev.resultRows[0][0]);
232 runOneTest('export',{}, function(ev){
234 log("export result:",ev);
235 T.assert('string' === typeof ev.filename)
236 .assert(ev.byteArray instanceof Uint8Array)
237 .assert(ev.byteArray.length > 1024)
238 .assert('application/x-sqlite3' === ev.mimetype);
240 /***** close() tests must come last. *****/
241 runOneTest('close',{unlink:true},function(ev){
243 T.assert('string' === typeof ev.filename);
245 runOneTest('close',{unlink:true},function(ev){
247 T.assert(undefined === ev.filename);
248 logHtml('warning',"This is the final test.");
250 logHtml('warning',"Finished posting tests. Waiting on async results.");
253 const runTests = function(){
255 Design decision time: all remaining tests depend on the 'open'
256 command having succeeded. In order to support multiple DBs, the
257 upcoming commands ostensibly have to know the ID of the DB they
258 want to talk to. We have two choices:
260 1) We run 'open' and wait for its response, which contains the
263 2) We have the Worker automatically use the current "default
264 db" (the one which was most recently opened) if no db id is
265 provided in the message. When we do this, the main thread may
266 well fire off _all_ of the test messages before the 'open'
267 actually responds, but because the messages are handled on a
268 FIFO basis, those after the initial 'open' will pick up the
269 "default" db. However, if the open fails, then all pending
270 messages (until next next 'open', at least) except for 'close'
271 will fail and we have no way of cancelling them once they've
272 been posted to the worker.
274 Which approach we use below depends on the boolean value of
277 const waitForOpen = 1,
278 simulateOpenError = 0 /* if true, the remaining tests will
279 all barf if waitForOpen is
282 "Sending 'open' message and",(waitForOpen ? "" : "NOT ")+
283 "waiting for its response before continuing.");
284 startTime = performance.now();
286 filename:'testing2.sqlite3',
287 simulateError: simulateOpenError
289 log("open result",ev);
290 T.assert('testing2.sqlite3'===ev.result.filename)
292 .assert(ev.messageId)
293 .assert('string' === typeof ev.result.vfs);
294 DbState.id = ev.dbId;
295 if(waitForOpen) setTimeout(runTests2, 0);
297 if(!waitForOpen) runTests2();
300 SW.onmessage = function(ev){
301 if(!ev.data || 'object'!==typeof ev.data){
302 warn("Unknown sqlite3-worker message type:",ev);
305 ev = ev.data/*expecting a nested object*/;
306 //log("main window onmessage:",ev);
307 if(ev.result && ev.messageId){
308 /* We're expecting a queued-up callback handler. */
309 const f = MsgHandlerQueue.shift();
310 if('error'===ev.type){
311 dbMsgHandler.error(ev);
314 T.assert(f instanceof Function);
321 case 'worker1-ready':
323 self.sqlite3TestModule.setStatus(null);
327 warn("Unknown sqlite3-api message type:",ev);
331 if(dbMsgHandler.hasOwnProperty(ev.type)){
332 try{dbMsgHandler[ev.type](ev);}
334 error("Exception while handling db result message",
339 warn("Unknown sqlite3-api message type:",ev);
342 log("Init complete, but async init bits may still be running.");
343 log("Installing Worker into global scope SW for dev purposes.");