Snapshot of upstream SQLite 3.40.1
[sqlcipher.git] / ext / wasm / tester1.js
blob99fb5b31848ae1659d94a0ddd091d99009ceb2db
1 /*
2 2022-10-12
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 Main functional and regression tests for the sqlite3 WASM API.
15 This mini-framework works like so:
17 This script adds a series of test groups, each of which contains an
18 arbitrary number of tests, into a queue. After loading of the
19 sqlite3 WASM/JS module is complete, that queue is processed. If any
20 given test fails, the whole thing fails. This script is built such
21 that it can run from the main UI thread or worker thread. Test
22 groups and individual tests can be assigned a predicate function
23 which determines whether to run them or not, and this is
24 specifically intended to be used to toggle certain tests on or off
25 for the main/worker threads.
27 Each test group defines a state object which gets applied as each
28 test function's `this`. Test functions can use that to, e.g., set up
29 a db in an early test and close it in a later test. Each test gets
30 passed the sqlite3 namespace object as its only argument.
32 'use strict';
33 (function(){
34 /**
35 Set up our output channel differently depending
36 on whether we are running in a worker thread or
37 the main (UI) thread.
39 let logClass;
40 /* Predicate for tests/groups. */
41 const isUIThread = ()=>(self.window===self && self.document);
42 /* Predicate for tests/groups. */
43 const isWorker = ()=>!isUIThread();
44 /* Predicate for tests/groups. */
45 const testIsTodo = ()=>false;
46 const haveWasmCTests = ()=>{
47 return !!wasm.exports.sqlite3_wasm_test_intptr;
50 const mapToString = (v)=>{
51 switch(typeof v){
52 case 'number': case 'string': case 'boolean':
53 case 'undefined': case 'bigint':
54 return ''+v;
55 default: break;
57 if(null===v) return 'null';
58 if(v instanceof Error){
59 v = {
60 message: v.message,
61 stack: v.stack,
62 errorClass: v.name
65 return JSON.stringify(v,undefined,2);
67 const normalizeArgs = (args)=>args.map(mapToString);
68 if( isUIThread() ){
69 console.log("Running in the UI thread.");
70 const logTarget = document.querySelector('#test-output');
71 logClass = function(cssClass,...args){
72 const ln = document.createElement('div');
73 if(cssClass){
74 for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
75 ln.classList.add(c);
78 ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
79 logTarget.append(ln);
81 const cbReverse = document.querySelector('#cb-log-reverse');
82 const cbReverseKey = 'tester1:cb-log-reverse';
83 const cbReverseIt = ()=>{
84 logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
85 //localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0);
87 cbReverse.addEventListener('change', cbReverseIt, true);
88 /*if(localStorage.getItem(cbReverseKey)){
89 cbReverse.checked = !!(+localStorage.getItem(cbReverseKey));
90 }*/
91 cbReverseIt();
92 }else{ /* Worker thread */
93 console.log("Running in a Worker thread.");
94 logClass = function(cssClass,...args){
95 postMessage({
96 type:'log',
97 payload:{cssClass, args: normalizeArgs(args)}
98 });
102 const reportFinalTestStatus = function(pass){
103 if(isUIThread()){
104 const e = document.querySelector('#color-target');
105 e.classList.add(pass ? 'tests-pass' : 'tests-fail');
106 }else{
107 postMessage({type:'test-result', payload:{pass}});
110 const log = (...args)=>{
111 //console.log(...args);
112 logClass('',...args);
114 const warn = (...args)=>{
115 console.warn(...args);
116 logClass('warning',...args);
118 const error = (...args)=>{
119 console.error(...args);
120 logClass('error',...args);
123 const toss = (...args)=>{
124 error(...args);
125 throw new Error(args.join(' '));
127 const tossQuietly = (...args)=>{
128 throw new Error(args.join(' '));
131 const roundMs = (ms)=>Math.round(ms*100)/100;
134 Helpers for writing sqlite3-specific tests.
136 const TestUtil = {
137 /** Running total of the number of tests run via
138 this API. */
139 counter: 0,
140 /* Separator line for log messages. */
141 separator: '------------------------------------------------------------',
143 If expr is a function, it is called and its result
144 is returned, coerced to a bool, else expr, coerced to
145 a bool, is returned.
147 toBool: function(expr){
148 return (expr instanceof Function) ? !!expr() : !!expr;
150 /** Throws if expr is false. If expr is a function, it is called
151 and its result is evaluated. If passed multiple arguments,
152 those after the first are a message string which get applied
153 as an exception message if the assertion fails. The message
154 arguments are concatenated together with a space between each.
156 assert: function f(expr, ...msg){
157 ++this.counter;
158 if(!this.toBool(expr)){
159 throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
161 return this;
163 /** Calls f() and squelches any exception it throws. If it
164 does not throw, this function throws. */
165 mustThrow: function(f, msg){
166 ++this.counter;
167 let err;
168 try{ f(); } catch(e){err=e;}
169 if(!err) throw new Error(msg || "Expected exception.");
170 return this;
173 Works like mustThrow() but expects filter to be a regex,
174 function, or string to match/filter the resulting exception
175 against. If f() does not throw, this test fails and an Error is
176 thrown. If filter is a regex, the test passes if
177 filter.test(error.message) passes. If it's a function, the test
178 passes if filter(error) returns truthy. If it's a string, the
179 test passes if the filter matches the exception message
180 precisely. In all other cases the test fails, throwing an
181 Error.
183 If it throws, msg is used as the error report unless it's falsy,
184 in which case a default is used.
186 mustThrowMatching: function(f, filter, msg){
187 ++this.counter;
188 let err;
189 try{ f(); } catch(e){err=e;}
190 if(!err) throw new Error(msg || "Expected exception.");
191 let pass = false;
192 if(filter instanceof RegExp) pass = filter.test(err.message);
193 else if(filter instanceof Function) pass = filter(err);
194 else if('string' === typeof filter) pass = (err.message === filter);
195 if(!pass){
196 throw new Error(msg || ("Filter rejected this exception: "+err.message));
198 return this;
200 /** Throws if expr is truthy or expr is a function and expr()
201 returns truthy. */
202 throwIf: function(expr, msg){
203 ++this.counter;
204 if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
205 return this;
207 /** Throws if expr is falsy or expr is a function and expr()
208 returns falsy. */
209 throwUnless: function(expr, msg){
210 ++this.counter;
211 if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
212 return this;
214 eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)),
215 TestGroup: (function(){
216 let groupCounter = 0;
217 const TestGroup = function(name, predicate){
218 this.number = ++groupCounter;
219 this.name = name;
220 this.predicate = predicate;
221 this.tests = [];
223 TestGroup.prototype = {
224 addTest: function(testObj){
225 this.tests.push(testObj);
226 return this;
228 run: async function(sqlite3){
229 log(TestUtil.separator);
230 logClass('group-start',"Group #"+this.number+':',this.name);
231 const indent = ' ';
232 if(this.predicate && !this.predicate(sqlite3)){
233 logClass('warning',indent,
234 "SKIPPING group because predicate says to.");
235 return;
237 const assertCount = TestUtil.counter;
238 const groupState = Object.create(null);
239 const skipped = [];
240 let runtime = 0, i = 0;
241 for(const t of this.tests){
242 ++i;
243 const n = this.number+"."+i;
244 log(indent, n+":", t.name);
245 if(t.predicate && !t.predicate(sqlite3)){
246 logClass('warning', indent, indent,
247 'SKIPPING because predicate says to');
248 skipped.push( n+': '+t.name );
249 }else{
250 const tc = TestUtil.counter, now = performance.now();
251 await t.test.call(groupState, sqlite3);
252 const then = performance.now();
253 runtime += then - now;
254 logClass('faded',indent, indent,
255 TestUtil.counter - tc, 'assertion(s) in',
256 roundMs(then-now),'ms');
259 logClass('green',
260 "Group #"+this.number+":",(TestUtil.counter - assertCount),
261 "assertion(s) in",roundMs(runtime),"ms");
262 if(skipped.length){
263 logClass('warning',"SKIPPED test(s) in group",this.number+":",skipped);
267 return TestGroup;
268 })()/*TestGroup*/,
269 testGroups: [],
270 currentTestGroup: undefined,
271 addGroup: function(name, predicate){
272 this.testGroups.push( this.currentTestGroup =
273 new this.TestGroup(name, predicate) );
274 return this;
276 addTest: function(name, callback){
277 let predicate;
278 if(1===arguments.length){
279 const opt = arguments[0];
280 predicate = opt.predicate;
281 name = opt.name;
282 callback = opt.test;
284 this.currentTestGroup.addTest({
285 name, predicate, test: callback
287 return this;
289 runTests: async function(sqlite3){
290 return new Promise(async function(pok,pnok){
291 try {
292 let runtime = 0;
293 for(let g of this.testGroups){
294 const now = performance.now();
295 await g.run(sqlite3);
296 runtime += performance.now() - now;
298 log(TestUtil.separator);
299 logClass(['strong','green'],
300 "Done running tests.",TestUtil.counter,"assertions in",
301 roundMs(runtime),'ms');
302 pok();
303 reportFinalTestStatus(true);
304 }catch(e){
305 error(e);
306 pnok(e);
307 reportFinalTestStatus(false);
309 }.bind(this));
311 }/*TestUtil*/;
312 const T = TestUtil;
313 T.g = T.addGroup;
314 T.t = T.addTest;
315 let capi, wasm/*assigned after module init*/;
316 ////////////////////////////////////////////////////////////////////////
317 // End of infrastructure setup. Now define the tests...
318 ////////////////////////////////////////////////////////////////////////
320 ////////////////////////////////////////////////////////////////////
321 T.g('Basic sanity checks')
322 .t('Namespace object checks', function(sqlite3){
323 const wasmCtypes = wasm.ctype;
324 T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs').
325 assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4).
326 assert(wasmCtypes.structs[1/*sqlite3_io_methods*/
327 ].members.xFileSize.offset>0);
328 [ /* Spot-check a handful of constants to make sure they got installed... */
329 'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
330 'SQLITE_STATIC', 'SQLITE_DIRECTONLY',
331 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE'
332 ].forEach((k)=>T.assert('number' === typeof capi[k]));
333 [/* Spot-check a few of the WASM API methods. */
334 'alloc', 'dealloc', 'installFunction'
335 ].forEach((k)=>T.assert(wasm[k] instanceof Function));
337 T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0).
338 assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0).
339 assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error');
341 try {
342 throw new sqlite3.WasmAllocError;
343 }catch(e){
344 T.assert(e instanceof Error)
345 .assert(e instanceof sqlite3.WasmAllocError)
346 .assert("Allocation failed." === e.message);
348 try {
349 throw new sqlite3.WasmAllocError("test",{
350 cause: 3
352 }catch(e){
353 T.assert(3 === e.cause)
354 .assert("test" === e.message);
356 try {throw new sqlite3.WasmAllocError("test","ing",".")}
357 catch(e){T.assert("test ing ." === e.message)}
359 try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
360 catch(e){ T.assert('SQLITE_SCHEMA' === e.message) }
361 try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
362 catch(e){
363 T.assert('SQLITE_CORRUPT'===e.message)
364 .assert(true===e.cause);
367 ////////////////////////////////////////////////////////////////////
368 .t('strglob/strlike', function(sqlite3){
369 T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
370 assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
371 assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
372 assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
374 ////////////////////////////////////////////////////////////////////
375 ;/*end of basic sanity checks*/
377 ////////////////////////////////////////////////////////////////////
378 T.g('C/WASM Utilities')
379 .t('sqlite3.wasm namespace', function(sqlite3){
380 const w = wasm;
381 const chr = (x)=>x.charCodeAt(0);
382 //log("heap getters...");
384 const li = [8, 16, 32];
385 if(w.bigIntEnabled) li.push(64);
386 for(const n of li){
387 const bpe = n/8;
388 const s = w.heapForSize(n,false);
389 T.assert(bpe===s.BYTES_PER_ELEMENT).
390 assert(w.heapForSize(s.constructor) === s);
391 const u = w.heapForSize(n,true);
392 T.assert(bpe===u.BYTES_PER_ELEMENT).
393 assert(s!==u).
394 assert(w.heapForSize(u.constructor) === u);
398 // isPtr32()
400 const ip = w.isPtr32;
401 T.assert(ip(0))
402 .assert(!ip(-1))
403 .assert(!ip(1.1))
404 .assert(!ip(0xffffffff))
405 .assert(ip(0x7fffffff))
406 .assert(!ip())
407 .assert(!ip(null)/*might change: under consideration*/)
411 //log("jstrlen()...");
413 T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
416 //log("jstrcpy()...");
418 const fillChar = 10;
419 let ua = new Uint8Array(8), rc,
420 refill = ()=>ua.fill(fillChar);
421 refill();
422 rc = w.jstrcpy("hello", ua);
423 T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
424 refill();
425 ua[5] = chr('!');
426 rc = w.jstrcpy("HELLO", ua, 0, -1, false);
427 T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
428 refill();
429 rc = w.jstrcpy("the end", ua, 4);
430 //log("rc,ua",rc,ua);
431 T.assert(4===rc).assert(0===ua[7]).
432 assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
433 refill();
434 rc = w.jstrcpy("the end", ua, 4, -1, false);
435 T.assert(4===rc).assert(chr(' ')===ua[7]).
436 assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
437 refill();
438 rc = w.jstrcpy("", ua, 0, 1, true);
439 //log("rc,ua",rc,ua);
440 T.assert(1===rc).assert(0===ua[0]);
441 refill();
442 rc = w.jstrcpy("x", ua, 0, 1, true);
443 //log("rc,ua",rc,ua);
444 T.assert(1===rc).assert(0===ua[0]);
445 refill();
446 rc = w.jstrcpy('äbä', ua, 0, 1, true);
447 T.assert(1===rc, 'Must not write partial multi-byte char.')
448 .assert(0===ua[0]);
449 refill();
450 rc = w.jstrcpy('äbä', ua, 0, 2, true);
451 T.assert(1===rc, 'Must not write partial multi-byte char.')
452 .assert(0===ua[0]);
453 refill();
454 rc = w.jstrcpy('äbä', ua, 0, 2, false);
455 T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
456 }/*jstrcpy()*/
458 //log("cstrncpy()...");
460 const scope = w.scopedAllocPush();
461 try {
462 let cStr = w.scopedAllocCString("hello");
463 const n = w.cstrlen(cStr);
464 let cpy = w.scopedAlloc(n+10);
465 let rc = w.cstrncpy(cpy, cStr, n+10);
466 T.assert(n+1 === rc).
467 assert("hello" === w.cstringToJs(cpy)).
468 assert(chr('o') === w.getMemValue(cpy+n-1)).
469 assert(0 === w.getMemValue(cpy+n));
470 let cStr2 = w.scopedAllocCString("HI!!!");
471 rc = w.cstrncpy(cpy, cStr2, 3);
472 T.assert(3===rc).
473 assert("HI!lo" === w.cstringToJs(cpy)).
474 assert(chr('!') === w.getMemValue(cpy+2)).
475 assert(chr('l') === w.getMemValue(cpy+3));
476 }finally{
477 w.scopedAllocPop(scope);
481 //log("jstrToUintArray()...");
483 let a = w.jstrToUintArray("hello", false);
484 T.assert(5===a.byteLength).assert(chr('o')===a[4]);
485 a = w.jstrToUintArray("hello", true);
486 T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
487 a = w.jstrToUintArray("äbä", false);
488 T.assert(5===a.byteLength).assert(chr('b')===a[2]);
489 a = w.jstrToUintArray("äbä", true);
490 T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
493 //log("allocCString()...");
495 const cstr = w.allocCString("hällo, world");
496 const n = w.cstrlen(cstr);
497 T.assert(13 === n)
498 .assert(0===w.getMemValue(cstr+n))
499 .assert(chr('d')===w.getMemValue(cstr+n-1));
502 //log("scopedAlloc() and friends...");
504 const alloc = w.alloc, dealloc = w.dealloc;
505 w.alloc = w.dealloc = null;
506 T.assert(!w.scopedAlloc.level)
507 .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
508 .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
509 w.alloc = alloc;
510 T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
511 w.dealloc = dealloc;
512 T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
513 .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
514 .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
515 const asc = w.scopedAllocPush();
516 let asc2;
517 try {
518 const p1 = w.scopedAlloc(16),
519 p2 = w.scopedAlloc(16);
520 T.assert(1===w.scopedAlloc.level)
521 .assert(Number.isFinite(p1))
522 .assert(Number.isFinite(p2))
523 .assert(asc[0] === p1)
524 .assert(asc[1]===p2);
525 asc2 = w.scopedAllocPush();
526 const p3 = w.scopedAlloc(16);
527 T.assert(2===w.scopedAlloc.level)
528 .assert(Number.isFinite(p3))
529 .assert(2===asc.length)
530 .assert(p3===asc2[0]);
532 const [z1, z2, z3] = w.scopedAllocPtr(3);
533 T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
534 .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
535 .assert(0===w.getMemValue(z3,'i32'));
536 }finally{
537 // Pop them in "incorrect" order to make sure they behave:
538 w.scopedAllocPop(asc);
539 T.assert(0===asc.length);
540 T.mustThrowMatching(()=>w.scopedAllocPop(asc),
541 /^Invalid state object/);
542 if(asc2){
543 T.assert(2===asc2.length,'Should be p3 and z1');
544 w.scopedAllocPop(asc2);
545 T.assert(0===asc2.length);
546 T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
547 /^Invalid state object/);
550 T.assert(0===w.scopedAlloc.level);
551 w.scopedAllocCall(function(){
552 T.assert(1===w.scopedAlloc.level);
553 const [cstr, n] = w.scopedAllocCString("hello, world", true);
554 T.assert(12 === n)
555 .assert(0===w.getMemValue(cstr+n))
556 .assert(chr('d')===w.getMemValue(cstr+n-1));
558 }/*scopedAlloc()*/
560 //log("xCall()...");
562 const pJson = w.xCall('sqlite3_wasm_enum_json');
563 T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
566 //log("xWrap()...");
568 T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
569 /requires 0 arg/).
570 assert(w.xWrap.resultAdapter('i32') instanceof Function).
571 assert(w.xWrap.argAdapter('i32') instanceof Function);
572 let fw = w.xWrap('sqlite3_libversion','utf8');
573 T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
574 let rc = fw();
575 T.assert('string'===typeof rc).assert(rc.length>5);
576 rc = w.xCallWrapped('sqlite3_wasm_enum_json','*');
577 T.assert(rc>0 && Number.isFinite(rc));
578 rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
579 T.assert('string'===typeof rc).assert(rc.length>300);
580 if(haveWasmCTests()){
581 fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['i32']);
582 rc = fw(0);
583 T.assert('hello'===rc);
584 rc = fw(1);
585 T.assert(null===rc);
587 if(w.bigIntEnabled){
588 w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
589 w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
590 fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice');
591 rc = fw(1);
592 T.assert(12n===rc);
594 w.scopedAllocCall(function(){
595 let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
596 w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
597 let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
598 let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
599 T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
604 }/*WhWasmUtil*/)
606 ////////////////////////////////////////////////////////////////////
607 .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){
608 const S = sqlite3, W = S.wasm;
609 const MyStructDef = {
610 sizeof: 16,
611 members: {
612 p4: {offset: 0, sizeof: 4, signature: "i"},
613 pP: {offset: 4, sizeof: 4, signature: "P"},
614 ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
615 cstr: {offset: 12, sizeof: 4, signature: "s"}
618 if(W.bigIntEnabled){
619 const m = MyStructDef;
620 m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
621 m.sizeof += m.members.p8.sizeof;
623 const StructType = S.StructBinder.StructType;
624 const K = S.StructBinder('my_struct',MyStructDef);
625 T.mustThrowMatching(()=>K(), /via 'new'/).
626 mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
627 const k1 = new K(), k2 = new K();
628 try {
629 T.assert(k1.constructor === K).
630 assert(K.isA(k1)).
631 assert(k1 instanceof K).
632 assert(K.prototype.lookupMember('p4').key === '$p4').
633 assert(K.prototype.lookupMember('$p4').name === 'p4').
634 mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
635 assert(undefined === K.prototype.lookupMember('nope',false)).
636 assert(k1 instanceof StructType).
637 assert(StructType.isA(k1)).
638 assert(K.resolveToInstance(k1.pointer)===k1).
639 mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
640 assert(k1 === StructType.instanceForPointer(k1.pointer)).
641 mustThrowMatching(()=>k1.$ro = 1, /read-only/);
642 Object.keys(MyStructDef.members).forEach(function(key){
643 key = K.memberKey(key);
644 T.assert(0 == k1[key],
645 "Expecting allocation to zero the memory "+
646 "for "+key+" but got: "+k1[key]+
647 " from "+k1.memoryDump());
649 T.assert('number' === typeof k1.pointer).
650 mustThrowMatching(()=>k1.pointer = 1, /pointer/).
651 assert(K.instanceForPointer(k1.pointer) === k1);
652 k1.$p4 = 1; k1.$pP = 2;
653 T.assert(1 === k1.$p4).assert(2 === k1.$pP);
654 if(MyStructDef.members.$p8){
655 k1.$p8 = 1/*must not throw despite not being a BigInt*/;
656 k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
657 T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
659 T.assert(!k1.ondispose);
660 k1.setMemberCString('cstr', "A C-string.");
661 T.assert(Array.isArray(k1.ondispose)).
662 assert(k1.ondispose[0] === k1.$cstr).
663 assert('number' === typeof k1.$cstr).
664 assert('A C-string.' === k1.memberToJsString('cstr'));
665 k1.$pP = k2;
666 T.assert(k1.$pP === k2);
667 k1.$pP = null/*null is special-cased to 0.*/;
668 T.assert(0===k1.$pP);
669 let ptr = k1.pointer;
670 k1.dispose();
671 T.assert(undefined === k1.pointer).
672 assert(undefined === K.instanceForPointer(ptr)).
673 mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
674 const k3 = new K();
675 ptr = k3.pointer;
676 T.assert(k3 === K.instanceForPointer(ptr));
677 K.disposeAll();
678 T.assert(ptr).
679 assert(undefined === k2.pointer).
680 assert(undefined === k3.pointer).
681 assert(undefined === K.instanceForPointer(ptr));
682 }finally{
683 k1.dispose();
684 k2.dispose();
687 if(!W.bigIntEnabled){
688 log("Skipping WasmTestStruct tests: BigInt not enabled.");
689 return;
692 const WTStructDesc =
693 W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
694 const autoResolvePtr = true /* EXPERIMENTAL */;
695 if(autoResolvePtr){
696 WTStructDesc.members.ppV.signature = 'P';
698 const WTStruct = S.StructBinder(WTStructDesc);
699 //log(WTStruct.structName, WTStruct.structInfo);
700 const wts = new WTStruct();
701 //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
702 try{
703 T.assert(wts.constructor === WTStruct).
704 assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
705 assert(wts.memberKeys().indexOf('$v8')>=0).
706 assert(!K.isA(wts)).
707 assert(WTStruct.isA(wts)).
708 assert(wts instanceof WTStruct).
709 assert(wts instanceof StructType).
710 assert(StructType.isA(wts)).
711 assert(wts === StructType.instanceForPointer(wts.pointer));
712 T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
713 assert(0===wts.$ppV).assert(0===wts.$xFunc).
714 assert(WTStruct.instanceForPointer(wts.pointer) === wts);
715 const testFunc =
716 W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
717 let counter = 0;
718 //log("wts.pointer =",wts.pointer);
719 const wtsFunc = function(arg){
720 /*log("This from a JS function called from C, "+
721 "which itself was called from JS. arg =",arg);*/
722 ++counter;
723 T.assert(WTStruct.instanceForPointer(arg) === wts);
724 if(3===counter){
725 tossQuietly("Testing exception propagation.");
728 wts.$v4 = 10; wts.$v8 = 20;
729 wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
730 T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
731 .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
732 .assert(0 === wts.$cstr)
733 .assert(wts.memberIsString('$cstr'))
734 .assert(!wts.memberIsString('$v4'))
735 .assert(null === wts.memberToJsString('$cstr'))
736 .assert(W.functionEntry(wts.$xFunc) instanceof Function);
737 /* It might seem silly to assert that the values match
738 what we just set, but recall that all of those property
739 reads and writes are, via property interceptors,
740 actually marshaling their data to/from a raw memory
741 buffer, so merely reading them back is actually part of
742 testing the struct-wrapping API. */
744 testFunc(wts.pointer);
745 //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
746 T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
747 .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer))
748 .assert('string' === typeof wts.memberToJsString('cstr'))
749 .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
750 .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
751 /Invalid member type signature for C-string/)
753 testFunc(wts.pointer);
754 T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
755 .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer));
756 /** The 3rd call to wtsFunc throw from JS, which is called
757 from C, which is called from JS. Let's ensure that
758 that exception propagates back here... */
759 T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
760 W.uninstallFunction(wts.$xFunc);
761 wts.$xFunc = 0;
762 if(autoResolvePtr){
763 wts.$ppV = 0;
764 T.assert(!wts.$ppV);
765 //WTStruct.debugFlags(0x03);
766 wts.$ppV = wts;
767 T.assert(wts === wts.$ppV)
768 //WTStruct.debugFlags(0);
770 wts.setMemberCString('cstr', "A C-string.");
771 T.assert(Array.isArray(wts.ondispose)).
772 assert(wts.ondispose[0] === wts.$cstr).
773 assert('A C-string.' === wts.memberToJsString('cstr'));
774 const ptr = wts.pointer;
775 wts.dispose();
776 T.assert(ptr).assert(undefined === wts.pointer).
777 assert(undefined === WTStruct.instanceForPointer(ptr))
778 }finally{
779 wts.dispose();
781 }/*StructBinder*/)
783 ////////////////////////////////////////////////////////////////////
784 .t('sqlite3.StructBinder part 2', function(sqlite3){
785 // https://www.sqlite.org/c3ref/vfs.html
786 // https://www.sqlite.org/c3ref/io_methods.html
787 const sqlite3_io_methods = capi.sqlite3_io_methods,
788 sqlite3_vfs = capi.sqlite3_vfs,
789 sqlite3_file = capi.sqlite3_file;
790 //log("struct sqlite3_file", sqlite3_file.memberKeys());
791 //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
792 //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
793 const installMethod = function callee(tgt, name, func){
794 if(1===arguments.length){
795 return (n,f)=>callee(tgt,n,f);
797 if(!callee.argcProxy){
798 callee.argcProxy = function(func,sig){
799 return function(...args){
800 if(func.length!==arguments.length){
801 toss("Argument mismatch. Native signature is:",sig);
803 return func.apply(this, args);
806 callee.ondisposeRemoveFunc = function(){
807 if(this.__ondispose){
808 const who = this;
809 this.__ondispose.forEach(
810 (v)=>{
811 if('number'===typeof v){
812 try{wasm.uninstallFunction(v)}
813 catch(e){/*ignore*/}
814 }else{/*wasm function wrapper property*/
815 delete who[v];
819 delete this.__ondispose;
822 }/*static init*/
823 const sigN = tgt.memberSignature(name),
824 memKey = tgt.memberKey(name);
825 //log("installMethod",tgt, name, sigN);
826 if(!tgt.__ondispose){
827 T.assert(undefined === tgt.ondispose);
828 tgt.ondispose = [callee.ondisposeRemoveFunc];
829 tgt.__ondispose = [];
831 const fProxy = callee.argcProxy(func, sigN);
832 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
833 tgt[memKey] = pFunc;
835 ACHTUNG: function pointer IDs are from a different pool than
836 allocation IDs, starting at 1 and incrementing in steps of 1,
837 so if we set tgt[memKey] to those values, we'd very likely
838 later misinterpret them as plain old pointer addresses unless
839 unless we use some silly heuristic like "all values <5k are
840 presumably function pointers," or actually perform a function
841 lookup on every pointer to first see if it's a function. That
842 would likely work just fine, but would be kludgy.
844 It turns out that "all values less than X are functions" is
845 essentially how it works in wasm: a function pointer is
846 reported to the client as its index into the
847 __indirect_function_table.
849 So... once jaccwabyt can be told how to access the
850 function table, it could consider all pointer values less
851 than that table's size to be functions. As "real" pointer
852 values start much, much higher than the function table size,
853 that would likely work reasonably well. e.g. the object
854 pointer address for sqlite3's default VFS is (in this local
855 setup) 65104, whereas the function table has fewer than 600
856 entries.
858 const wrapperKey = '$'+memKey;
859 tgt[wrapperKey] = fProxy;
860 tgt.__ondispose.push(pFunc, wrapperKey);
861 //log("tgt.__ondispose =",tgt.__ondispose);
862 return (n,f)=>callee(tgt, n, f);
863 }/*installMethod*/;
865 const installIOMethods = function instm(iom){
866 (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
867 if(!instm._requireFileArg){
868 instm._requireFileArg = function(arg,methodName){
869 arg = capi.sqlite3_file.resolveToInstance(arg);
870 if(!arg){
871 err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
873 return arg;
875 instm._methods = {
876 // https://sqlite.org/c3ref/io_methods.html
877 xClose: /*i(P)*/function(f){
878 /* int (*xClose)(sqlite3_file*) */
879 log("xClose(",f,")");
880 if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
881 f.dispose(/*noting that f has externally-owned memory*/);
882 return 0;
884 xRead: /*i(Ppij)*/function(f,dest,n,offset){
885 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
886 log("xRead(",arguments,")");
887 if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
888 wasm.heap8().fill(0, dest + offset, n);
889 return 0;
891 xWrite: /*i(Ppij)*/function(f,dest,n,offset){
892 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
893 log("xWrite(",arguments,")");
894 if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
895 return 0;
897 xTruncate: /*i(Pj)*/function(f){
898 /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
899 log("xTruncate(",arguments,")");
900 if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
901 return 0;
903 xSync: /*i(Pi)*/function(f){
904 /* int (*xSync)(sqlite3_file*, int flags) */
905 log("xSync(",arguments,")");
906 if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
907 return 0;
909 xFileSize: /*i(Pp)*/function(f,pSz){
910 /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
911 log("xFileSize(",arguments,")");
912 if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
913 wasm.setMemValue(pSz, 0/*file size*/);
914 return 0;
916 xLock: /*i(Pi)*/function(f){
917 /* int (*xLock)(sqlite3_file*, int) */
918 log("xLock(",arguments,")");
919 if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
920 return 0;
922 xUnlock: /*i(Pi)*/function(f){
923 /* int (*xUnlock)(sqlite3_file*, int) */
924 log("xUnlock(",arguments,")");
925 if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
926 return 0;
928 xCheckReservedLock: /*i(Pp)*/function(){
929 /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
930 log("xCheckReservedLock(",arguments,")");
931 return 0;
933 xFileControl: /*i(Pip)*/function(){
934 /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
935 log("xFileControl(",arguments,")");
936 return capi.SQLITE_NOTFOUND;
938 xSectorSize: /*i(P)*/function(){
939 /* int (*xSectorSize)(sqlite3_file*) */
940 log("xSectorSize(",arguments,")");
941 return 0/*???*/;
943 xDeviceCharacteristics:/*i(P)*/function(){
944 /* int (*xDeviceCharacteristics)(sqlite3_file*) */
945 log("xDeviceCharacteristics(",arguments,")");
946 return 0;
949 }/*static init*/
950 iom.$iVersion = 1;
951 Object.keys(instm._methods).forEach(
952 (k)=>installMethod(iom, k, instm._methods[k])
954 }/*installIOMethods()*/;
956 const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
957 const err = console.error.bind(console);
958 try {
959 const IOM = sqlite3_io_methods, S3F = sqlite3_file;
960 //log("iom proto",iom,iom.constructor.prototype);
961 //log("sfile",sfile,sfile.constructor.prototype);
962 T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
963 //log("iom",iom);
964 sfile.$pMethods = iom.pointer;
965 T.assert(iom.pointer === sfile.$pMethods)
966 .assert(IOM.resolveToInstance(iom))
967 .assert(undefined ===IOM.resolveToInstance(sfile))
968 .mustThrow(()=>IOM.resolveToInstance(0,true))
969 .assert(S3F.resolveToInstance(sfile.pointer))
970 .assert(undefined===S3F.resolveToInstance(iom))
971 .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
972 T.assert(0===iom.$iVersion);
973 installIOMethods(iom);
974 T.assert(1===iom.$iVersion);
975 //log("iom.__ondispose",iom.__ondispose);
976 T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
977 }finally{
978 iom.dispose();
979 T.assert(undefined === iom.__ondispose);
982 const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
983 try {
984 const SB = sqlite3.StructBinder;
985 T.assert(dVfs instanceof SB.StructType)
986 .assert(dVfs.pointer)
987 .assert('sqlite3_vfs' === dVfs.structName)
988 .assert(!!dVfs.structInfo)
989 .assert(SB.StructType.hasExternalPointer(dVfs))
990 .assert(dVfs.$iVersion>0)
991 .assert('number'===typeof dVfs.$zName)
992 .assert('number'===typeof dVfs.$xSleep)
993 .assert(wasm.functionEntry(dVfs.$xOpen))
994 .assert(dVfs.memberIsString('zName'))
995 .assert(dVfs.memberIsString('$zName'))
996 .assert(!dVfs.memberIsString('pAppData'))
997 .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
998 /Invalid member type signature for C-string/)
999 .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
1000 .assert('string' === typeof dVfs.memberToJsString('zName'))
1001 .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
1003 //log("Default VFS: @",dVfs.pointer);
1004 Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
1005 const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
1006 addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
1007 if(1===mbr.signature.length){
1008 let sep = '?', val = undefined;
1009 switch(mbr.signature[0]){
1010 // TODO: move this into an accessor, e.g. getPreferredValue(member)
1011 case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
1012 case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
1013 case 's': sep = '=';
1014 val = dVfs.memberToJsString(mname);
1015 break;
1017 //log(prefix, sep, val);
1018 }else{
1019 //log(prefix," = funcptr @",addr, wasm.functionEntry(addr));
1022 }finally{
1023 dVfs.dispose();
1024 T.assert(undefined===dVfs.pointer);
1026 }/*StructBinder part 2*/)
1028 ////////////////////////////////////////////////////////////////////
1029 .t('sqlite3.wasm.pstack', function(sqlite3){
1030 const P = wasm.pstack;
1031 const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
1032 const stack = P.pointer;
1033 T.assert(0===stack % 8 /* must be 8-byte aligned */);
1034 try{
1035 const remaining = P.remaining;
1036 T.assert(P.quota >= 4096)
1037 .assert(remaining === P.quota)
1038 .mustThrowMatching(()=>P.alloc(0), isAllocErr)
1039 .mustThrowMatching(()=>P.alloc(-1), isAllocErr);
1040 let p1 = P.alloc(12);
1041 T.assert(p1 === stack - 16/*8-byte aligned*/)
1042 .assert(P.pointer === p1);
1043 let p2 = P.alloc(7);
1044 T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
1045 .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
1046 .assert(24 === stack - p2)
1047 .assert(P.pointer === p2);
1048 let n = remaining - (stack - p2);
1049 let p3 = P.alloc(n);
1050 T.assert(p3 === stack-remaining)
1051 .mustThrowMatching(()=>P.alloc(1), isAllocErr);
1052 }finally{
1053 P.restore(stack);
1056 T.assert(P.pointer === stack);
1057 try {
1058 const [p1, p2, p3] = P.allocChunks(3,4);
1059 T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
1060 .assert(p2 === p1 + 4)
1061 .assert(p3 === p2 + 4);
1062 T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
1063 (e)=>e instanceof sqlite3.WasmAllocError)
1064 }finally{
1065 P.restore(stack);
1068 T.assert(P.pointer === stack);
1069 try {
1070 let [p1, p2, p3] = P.allocPtr(3,false);
1071 let sPos = stack-16/*always rounded to multiple of 8*/;
1072 T.assert(P.pointer === sPos)
1073 .assert(p2 === p1 + 4)
1074 .assert(p3 === p2 + 4);
1075 [p1, p2, p3] = P.allocPtr(3);
1076 T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
1077 .assert(p2 === p1 + 8)
1078 .assert(p3 === p2 + 8);
1079 p1 = P.allocPtr();
1080 T.assert('number'===typeof p1);
1081 }finally{
1082 P.restore(stack);
1084 }/*pstack tests*/)
1086 ////////////////////////////////////////////////////////////////////
1087 ;/*end of C/WASM utils checks*/
1089 T.g('sqlite3_randomness()')
1090 .t('To memory buffer', function(sqlite3){
1091 const stack = wasm.pstack.pointer;
1092 try{
1093 const n = 520;
1094 const p = wasm.pstack.alloc(n);
1095 T.assert(0===wasm.getMemValue(p))
1096 .assert(0===wasm.getMemValue(p+n-1));
1097 T.assert(undefined === capi.sqlite3_randomness(n - 10, p));
1098 let j, check = 0;
1099 const heap = wasm.heap8u();
1100 for(j = 0; j < 10 && 0===check; ++j){
1101 check += heap[p + j];
1103 T.assert(check > 0);
1104 check = 0;
1105 // Ensure that the trailing bytes were not modified...
1106 for(j = n - 10; j < n && 0===check; ++j){
1107 check += heap[p + j];
1109 T.assert(0===check);
1110 }finally{
1111 wasm.pstack.restore(stack);
1114 .t('To byte array', function(sqlite3){
1115 const ta = new Uint8Array(117);
1116 let i, n = 0;
1117 for(i=0; i<ta.byteLength && 0===n; ++i){
1118 n += ta[i];
1120 T.assert(0===n)
1121 .assert(ta === capi.sqlite3_randomness(ta));
1122 for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
1123 n += ta[i];
1125 T.assert(n>0);
1126 const t0 = new Uint8Array(0);
1127 T.assert(t0 === capi.sqlite3_randomness(t0),
1128 "0-length array is a special case");
1130 ;/*end sqlite3_randomness() checks*/
1132 ////////////////////////////////////////////////////////////////////////
1133 T.g('sqlite3.oo1')
1134 .t('Create db', function(sqlite3){
1135 const dbFile = '/tester1.db';
1136 wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
1137 const db = this.db = new sqlite3.oo1.DB(dbFile);
1138 T.assert(Number.isInteger(db.pointer))
1139 .mustThrowMatching(()=>db.pointer=1, /read-only/)
1140 .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
1141 .assert('main'===db.dbName(0))
1142 .assert('string' === typeof db.dbVfsName());
1143 // Custom db error message handling via sqlite3_prepare_v2/v3()
1144 let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
1145 T.assert(capi.SQLITE_MISUSE === rc)
1146 .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
1147 .assert(dbFile === db.dbFilename())
1148 .assert(!db.dbFilename('nope'));
1151 ////////////////////////////////////////////////////////////////////
1152 .t('DB.Stmt', function(S){
1153 let st = this.db.prepare(
1154 new TextEncoder('utf-8').encode("select 3 as a")
1156 //debug("statement =",st);
1157 try {
1158 T.assert(Number.isInteger(st.pointer))
1159 .mustThrowMatching(()=>st.pointer=1, /read-only/)
1160 .assert(1===this.db.openStatementCount())
1161 .assert(!st._mayGet)
1162 .assert('a' === st.getColumnName(0))
1163 .assert(1===st.columnCount)
1164 .assert(0===st.parameterCount)
1165 .mustThrow(()=>st.bind(1,null))
1166 .assert(true===st.step())
1167 .assert(3 === st.get(0))
1168 .mustThrow(()=>st.get(1))
1169 .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
1170 .assert(3 === st.get(0,capi.SQLITE_INTEGER))
1171 .assert(3 === st.getInt(0))
1172 .assert('3' === st.get(0,capi.SQLITE_TEXT))
1173 .assert('3' === st.getString(0))
1174 .assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
1175 .assert(3.0 === st.getFloat(0))
1176 .assert(3 === st.get({}).a)
1177 .assert(3 === st.get([])[0])
1178 .assert(3 === st.getJSON(0))
1179 .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
1180 .assert(1===st.get(0,capi.SQLITE_BLOB).length)
1181 .assert(st.getBlob(0) instanceof Uint8Array)
1182 .assert('3'.charCodeAt(0) === st.getBlob(0)[0])
1183 .assert(st._mayGet)
1184 .assert(false===st.step())
1185 .assert(!st._mayGet)
1187 T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
1188 assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
1189 assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
1190 assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
1191 }finally{
1192 st.finalize();
1194 T.assert(!st.pointer)
1195 .assert(0===this.db.openStatementCount());
1198 ////////////////////////////////////////////////////////////////////////
1199 .t('sqlite3_js_...()', function(){
1200 const db = this.db;
1201 if(1){
1202 const vfsList = capi.sqlite3_js_vfs_list();
1203 T.assert(vfsList.length>1);
1204 T.assert('string'===typeof vfsList[0]);
1205 //log("vfsList =",vfsList);
1206 for(const v of vfsList){
1207 T.assert('string' === typeof v)
1208 .assert(capi.sqlite3_vfs_find(v) > 0);
1212 Trivia: the magic db name ":memory:" does not actually use the
1213 "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
1214 name. Instead, it uses the default VFS with an in-memory btree.
1215 Thus this.db's VFS may not be memdb even though it's an in-memory
1218 const pVfsMem = capi.sqlite3_vfs_find('memdb'),
1219 pVfsDflt = capi.sqlite3_vfs_find(0),
1220 pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
1221 T.assert(pVfsMem > 0)
1222 .assert(pVfsDflt > 0)
1223 .assert(pVfsDb > 0)
1224 .assert(pVfsMem !== pVfsDflt
1225 /* memdb lives on top of the default vfs */)
1226 .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
1228 /*const vMem = new capi.sqlite3_vfs(pVfsMem),
1229 vDflt = new capi.sqlite3_vfs(pVfsDflt),
1230 vDb = new capi.sqlite3_vfs(pVfsDb);*/
1231 const duv = capi.sqlite3_js_db_uses_vfs;
1232 T.assert(pVfsDflt === duv(db.pointer, 0)
1233 || pVfsMem === duv(db.pointer,0))
1234 .assert(!duv(db.pointer, "foo"))
1236 }/*sqlite3_js_...()*/)
1238 ////////////////////////////////////////////////////////////////////
1239 .t('Table t', function(sqlite3){
1240 const db = this.db;
1241 let list = [];
1242 let rc = db.exec({
1243 sql:['CREATE TABLE t(a,b);',
1244 // ^^^ using TEMP TABLE breaks the db export test
1245 "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
1246 "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for
1247 off-by-one bug in string-to-WASM conversion*/],
1248 saveSql: list,
1249 bind: [5,6]
1251 //debug("Exec'd SQL:", list);
1252 T.assert(rc === db)
1253 .assert(2 === list.length)
1254 .assert('string'===typeof list[1])
1255 .assert(4===db.changes());
1256 if(wasm.bigIntEnabled){
1257 T.assert(4n===db.changes(false,true));
1259 let blob = db.selectValue("select b from t where a='blob'");
1260 T.assert(blob instanceof Uint8Array).
1261 assert(0x68===blob[0] && 0x69===blob[1]);
1262 blob = null;
1263 let counter = 0, colNames = [];
1264 list.length = 0;
1265 db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
1266 rowMode: 'object',
1267 resultRows: list,
1268 columnNames: colNames,
1269 callback: function(row,stmt){
1270 ++counter;
1271 T.assert((row.a%2 && row.a<6) || 'blob'===row.a);
1274 T.assert(2 === colNames.length)
1275 .assert('a' === colNames[0])
1276 .assert(4 === counter)
1277 .assert(4 === list.length);
1278 list.length = 0;
1279 db.exec("SELECT a a, b b FROM t",{
1280 rowMode: 'array',
1281 callback: function(row,stmt){
1282 ++counter;
1283 T.assert(Array.isArray(row))
1284 .assert((0===row[1]%2 && row[1]<7)
1285 || (row[1] instanceof Uint8Array));
1288 T.assert(8 === counter);
1289 T.assert(Number.MIN_SAFE_INTEGER ===
1290 db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
1291 assert(Number.MAX_SAFE_INTEGER ===
1292 db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
1293 if(wasm.bigIntEnabled && haveWasmCTests()){
1294 const mI = wasm.xCall('sqlite3_wasm_test_int64_max');
1295 const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
1296 T.assert(b === db.selectValue("SELECT "+b)).
1297 assert(b === db.selectValue("SELECT ?", b)).
1298 assert(mI == db.selectValue("SELECT $x", {$x:mI}));
1299 }else{
1300 /* Curiously, the JS spec seems to be off by one with the definitions
1301 of MIN/MAX_SAFE_INTEGER:
1303 https://github.com/emscripten-core/emscripten/issues/17391 */
1304 T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
1305 mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
1308 let st = db.prepare("update t set b=:b where a='blob'");
1309 try {
1310 const ndx = st.getParamIndex(':b');
1311 T.assert(1===ndx);
1312 st.bindAsBlob(ndx, "ima blob").reset(true);
1313 } finally {
1314 st.finalize();
1317 try {
1318 db.prepare("/*empty SQL*/");
1319 toss("Must not be reached.");
1320 }catch(e){
1321 T.assert(e instanceof sqlite3.SQLite3Error)
1322 .assert(0==e.message.indexOf('Cannot prepare empty'));
1326 ////////////////////////////////////////////////////////////////////////
1327 .t('selectArray/Object()', function(sqlite3){
1328 const db = this.db;
1329 let rc = db.selectArray('select a, b from t where a=?', 5);
1330 T.assert(Array.isArray(rc))
1331 .assert(2===rc.length)
1332 .assert(5===rc[0] && 6===rc[1]);
1333 rc = db.selectArray('select a, b from t where b=-1');
1334 T.assert(undefined === rc);
1335 rc = db.selectObject('select a A, b b from t where b=?', 6);
1336 T.assert(rc && 'object'===typeof rc)
1337 .assert(5===rc.A)
1338 .assert(6===rc.b);
1339 rc = db.selectArray('select a, b from t where b=-1');
1340 T.assert(undefined === rc);
1343 ////////////////////////////////////////////////////////////////////////
1344 .t('sqlite3_js_db_export()', function(){
1345 const db = this.db;
1346 const xp = capi.sqlite3_js_db_export(db.pointer);
1347 T.assert(xp instanceof Uint8Array)
1348 .assert(xp.byteLength>0)
1349 .assert(0 === xp.byteLength % 512);
1350 }/*sqlite3_js_db_export()*/)
1352 ////////////////////////////////////////////////////////////////////
1353 .t('Scalar UDFs', function(sqlite3){
1354 const db = this.db;
1355 db.createFunction("foo",(pCx,a,b)=>a+b);
1356 T.assert(7===db.selectValue("select foo(3,4)")).
1357 assert(5===db.selectValue("select foo(3,?)",2)).
1358 assert(5===db.selectValue("select foo(?,?2)",[1,4])).
1359 assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
1360 db.createFunction("bar", {
1361 arity: -1,
1362 xFunc: (pCx,...args)=>{
1363 let rc = 0;
1364 for(const v of args) rc += v;
1365 return rc;
1367 }).createFunction({
1368 name: "asis",
1369 xFunc: (pCx,arg)=>arg
1371 T.assert(0===db.selectValue("select bar()")).
1372 assert(1===db.selectValue("select bar(1)")).
1373 assert(3===db.selectValue("select bar(1,2)")).
1374 assert(-1===db.selectValue("select bar(1,2,-4)")).
1375 assert('hi' === db.selectValue("select asis('hi')")).
1376 assert('hi' === db.selectValue("select ?",'hi')).
1377 assert(null === db.selectValue("select null")).
1378 assert(null === db.selectValue("select asis(null)")).
1379 assert(1 === db.selectValue("select ?",1)).
1380 assert(2 === db.selectValue("select ?",[2])).
1381 assert(3 === db.selectValue("select $a",{$a:3})).
1382 assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
1383 assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
1385 let blobArg = new Uint8Array(2);
1386 blobArg.set([0x68, 0x69], 0);
1387 let blobRc = db.selectValue("select asis(?1)", blobArg);
1388 T.assert(blobRc instanceof Uint8Array).
1389 assert(2 === blobRc.length).
1390 assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1391 blobRc = db.selectValue("select asis(X'6869')");
1392 T.assert(blobRc instanceof Uint8Array).
1393 assert(2 === blobRc.length).
1394 assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1396 blobArg = new Int8Array(2);
1397 blobArg.set([0x68, 0x69]);
1398 //debug("blobArg=",blobArg);
1399 blobRc = db.selectValue("select asis(?1)", blobArg);
1400 T.assert(blobRc instanceof Uint8Array).
1401 assert(2 === blobRc.length);
1402 //debug("blobRc=",blobRc);
1403 T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1406 ////////////////////////////////////////////////////////////////////
1407 .t({
1408 name: 'Aggregate UDFs',
1409 test: function(sqlite3){
1410 const db = this.db;
1411 const sjac = capi.sqlite3_js_aggregate_context;
1412 db.createFunction({
1413 name: 'summer',
1414 xStep: (pCtx, n)=>{
1415 const ac = sjac(pCtx, 4);
1416 wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32');
1418 xFinal: (pCtx)=>{
1419 const ac = sjac(pCtx, 0);
1420 return ac ? wasm.getMemValue(ac,'i32') : 0;
1423 let v = db.selectValue([
1424 "with cte(v) as (",
1425 "select 3 union all select 5 union all select 7",
1426 ") select summer(v), summer(v+1) from cte"
1427 /* ------------------^^^^^^^^^^^ ensures that we're handling
1428 sqlite3_aggregate_context() properly. */
1430 T.assert(15===v);
1431 T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
1432 /wrong number of arguments/);
1434 db.createFunction({
1435 name: 'summerN',
1436 arity: -1,
1437 xStep: (pCtx, ...args)=>{
1438 const ac = sjac(pCtx, 4);
1439 let sum = wasm.getMemValue(ac, 'i32');
1440 for(const v of args) sum += Number(v);
1441 wasm.setMemValue(ac, sum, 'i32');
1443 xFinal: (pCtx)=>{
1444 const ac = sjac(pCtx, 0);
1445 capi.sqlite3_result_int( pCtx, ac ? wasm.getMemValue(ac,'i32') : 0 );
1446 // xFinal() may either return its value directly or call
1447 // sqlite3_result_xyz() and return undefined. Both are
1448 // functionally equivalent.
1450 });
1451 T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
1452 T.mustThrowMatching(()=>{
1453 db.createFunction('nope',{
1454 xFunc: ()=>{}, xStep: ()=>{}
1456 }, /scalar or aggregate\?/);
1457 T.mustThrowMatching(()=>{
1458 db.createFunction('nope',{xStep: ()=>{}});
1459 }, /Missing xFinal/);
1460 T.mustThrowMatching(()=>{
1461 db.createFunction('nope',{xFinal: ()=>{}});
1462 }, /Missing xStep/);
1463 T.mustThrowMatching(()=>{
1464 db.createFunction('nope',{});
1465 }, /Missing function-type properties/);
1466 T.mustThrowMatching(()=>{
1467 db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
1468 }, /xDestroy property must be a function/);
1469 T.mustThrowMatching(()=>{
1470 db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
1471 }, /Invalid value for pApp/);
1473 }/*aggregate UDFs*/)
1475 ////////////////////////////////////////////////////////////////////////
1476 .t({
1477 name: 'Aggregate UDFs (64-bit)',
1478 predicate: ()=>wasm.bigIntEnabled,
1479 test: function(sqlite3){
1480 const db = this.db;
1481 const sjac = capi.sqlite3_js_aggregate_context;
1482 db.createFunction({
1483 name: 'summer64',
1484 xStep: (pCtx, n)=>{
1485 const ac = sjac(pCtx, 8);
1486 wasm.setMemValue(ac, wasm.getMemValue(ac,'i64') + BigInt(n), 'i64');
1488 xFinal: (pCtx)=>{
1489 const ac = sjac(pCtx, 0);
1490 return ac ? wasm.getMemValue(ac,'i64') : 0n;
1493 let v = db.selectValue([
1494 "with cte(v) as (",
1495 "select 9007199254740991 union all select 1 union all select 2",
1496 ") select summer64(v), summer64(v+1) from cte"
1498 T.assert(9007199254740994n===v);
1500 }/*aggregate UDFs*/)
1502 ////////////////////////////////////////////////////////////////////
1503 .t({
1504 name: 'Window UDFs',
1505 test: function(){
1506 /* Example window function, table, and results taken from:
1507 https://sqlite.org/windowfunctions.html#udfwinfunc */
1508 const db = this.db;
1509 const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n);
1510 const xValueFinal = (pCtx)=>{
1511 const ac = sjac(pCtx, 0);
1512 return ac ? wasm.getMemValue(ac,'i32') : 0;
1514 const xStepInverse = (pCtx, n)=>{
1515 const ac = sjac(pCtx);
1516 wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32');
1518 db.createFunction({
1519 name: 'winsumint',
1520 xStep: (pCtx, n)=>xStepInverse(pCtx, n),
1521 xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
1522 xFinal: xValueFinal,
1523 xValue: xValueFinal
1525 db.exec([
1526 "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
1527 "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
1529 let rc = db.exec({
1530 returnValue: 'resultRows',
1531 sql:[
1532 "SELECT x, winsumint(y) OVER (",
1533 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1534 ") AS sum_y ",
1535 "FROM twin ORDER BY x;"
1538 T.assert(Array.isArray(rc))
1539 .assert(5 === rc.length);
1540 let count = 0;
1541 for(const row of rc){
1542 switch(++count){
1543 case 1: T.assert('a'===row[0] && 9===row[1]); break;
1544 case 2: T.assert('b'===row[0] && 12===row[1]); break;
1545 case 3: T.assert('c'===row[0] && 16===row[1]); break;
1546 case 4: T.assert('d'===row[0] && 12===row[1]); break;
1547 case 5: T.assert('e'===row[0] && 9===row[1]); break;
1548 default: toss("Too many rows to window function.");
1551 const resultRows = [];
1552 rc = db.exec({
1553 resultRows,
1554 returnValue: 'resultRows',
1555 sql:[
1556 "SELECT x, winsumint(y) OVER (",
1557 "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1558 ") AS sum_y ",
1559 "FROM twin ORDER BY x;"
1562 T.assert(rc === resultRows)
1563 .assert(5 === rc.length);
1565 rc = db.exec({
1566 returnValue: 'saveSql',
1567 sql: "select 1; select 2; -- empty\n; select 3"
1569 T.assert(Array.isArray(rc))
1570 .assert(3===rc.length)
1571 .assert('select 1;' === rc[0])
1572 .assert('select 2;' === rc[1])
1573 .assert('-- empty\n; select 3' === rc[2]
1574 /* Strange but true. */);
1576 T.mustThrowMatching(()=>{
1577 db.exec({sql:'', returnValue: 'nope'});
1578 }, /^Invalid returnValue/);
1580 db.exec("DROP TABLE twin");
1582 }/*window UDFs*/)
1584 ////////////////////////////////////////////////////////////////////
1585 .t("ATTACH", function(){
1586 const db = this.db;
1587 const resultRows = [];
1588 db.exec({
1589 sql:new TextEncoder('utf-8').encode([
1590 // ^^^ testing string-vs-typedarray handling in exec()
1591 "attach 'session' as foo;",
1592 "create table foo.bar(a);",
1593 "insert into foo.bar(a) values(1),(2),(3);",
1594 "select a from foo.bar order by a;"
1595 ].join('')),
1596 rowMode: 0,
1597 resultRows
1599 T.assert(3===resultRows.length)
1600 .assert(2===resultRows[1]);
1601 T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
1602 let colCount = 0, rowCount = 0;
1603 const execCallback = function(pVoid, nCols, aVals, aNames){
1604 colCount = nCols;
1605 ++rowCount;
1606 T.assert(2===aVals.length)
1607 .assert(2===aNames.length)
1608 .assert(+(aVals[1]) === 2 * +(aVals[0]));
1610 let rc = capi.sqlite3_exec(
1611 db.pointer, "select a, a*2 from foo.bar", execCallback,
1612 0, 0
1614 T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
1615 rc = capi.sqlite3_exec(
1616 db.pointer, "select a from foo.bar", ()=>{
1617 tossQuietly("Testing throwing from exec() callback.");
1618 }, 0, 0
1620 T.assert(capi.SQLITE_ABORT === rc);
1621 db.exec("detach foo");
1622 T.mustThrow(()=>db.exec("select * from foo.bar"));
1625 ////////////////////////////////////////////////////////////////////
1626 .t({
1627 name: 'C-side WASM tests (if compiled in)',
1628 predicate: haveWasmCTests,
1629 test: function(){
1630 const w = wasm, db = this.db;
1631 const stack = w.scopedAllocPush();
1632 let ptrInt;
1633 const origValue = 512;
1634 const ptrValType = 'i32';
1635 try{
1636 ptrInt = w.scopedAlloc(4);
1637 w.setMemValue(ptrInt,origValue, ptrValType);
1638 const cf = w.xGet('sqlite3_wasm_test_intptr');
1639 const oldPtrInt = ptrInt;
1640 //log('ptrInt',ptrInt);
1641 //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
1642 T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
1643 const rc = cf(ptrInt);
1644 //log('cf(ptrInt)',rc);
1645 //log('ptrInt',ptrInt);
1646 //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
1647 T.assert(2*origValue === rc).
1648 assert(rc === w.getMemValue(ptrInt,ptrValType)).
1649 assert(oldPtrInt === ptrInt);
1650 const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
1651 const o64 = 0x010203040506/*>32-bit integer*/;
1652 const ptrType64 = 'i64';
1653 if(w.bigIntEnabled){
1654 w.setMemValue(pi64, o64, ptrType64);
1655 //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
1656 const v64 = ()=>w.getMemValue(pi64,ptrType64)
1657 //log("getMemValue(pi64)",v64());
1658 T.assert(v64() == o64);
1659 //T.assert(o64 === w.getMemValue(pi64, ptrType64));
1660 const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
1661 cf64w(pi64);
1662 //log("getMemValue(pi64)",v64());
1663 T.assert(v64() == BigInt(2 * o64));
1664 cf64w(pi64);
1665 T.assert(v64() == BigInt(4 * o64));
1667 const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2');
1668 T.assert(BigInt(2 * o64) ===
1669 biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
1670 in the call :/ */));
1672 const pMin = w.scopedAlloc(16);
1673 const pMax = pMin + 8;
1674 const g64 = (p)=>w.getMemValue(p,ptrType64);
1675 w.setMemValue(pMin, 0, ptrType64);
1676 w.setMemValue(pMax, 0, ptrType64);
1677 const minMaxI64 = [
1678 w.xCall('sqlite3_wasm_test_int64_min'),
1679 w.xCall('sqlite3_wasm_test_int64_max')
1681 T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
1682 assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
1683 //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
1684 w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax);
1685 T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
1686 assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
1687 //log("pMin",g64(pMin), "pMax",g64(pMax));
1688 w.setMemValue(pMin, minMaxI64[0], ptrType64);
1689 T.assert(g64(pMin) === minMaxI64[0]).
1690 assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
1691 assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
1692 const rxRange = /too big/;
1693 T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
1694 rxRange).
1695 mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
1696 (e)=>rxRange.test(e.message));
1697 }else{
1698 log("No BigInt support. Skipping related tests.");
1699 log("\"The problem\" here is that we can manipulate, at the byte level,",
1700 "heap memory to set 64-bit values, but we can't get those values",
1701 "back into JS because of the lack of 64-bit integer support.");
1703 }finally{
1704 const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
1705 //log("x=",x,"y=",y,"z=",z); // just looking at the alignment
1706 w.scopedAllocPop(stack);
1709 }/* jaccwabyt-specific tests */)
1711 .t('Close db', function(){
1712 T.assert(this.db).assert(Number.isInteger(this.db.pointer));
1713 wasm.exports.sqlite3_wasm_db_reset(this.db.pointer);
1714 this.db.close();
1715 T.assert(!this.db.pointer);
1717 ;/* end of oo1 checks */
1719 ////////////////////////////////////////////////////////////////////////
1720 T.g('kvvfs')
1721 .t('kvvfs sanity checks', function(sqlite3){
1722 if(isWorker()){
1723 T.assert(
1724 !capi.sqlite3_vfs_find('kvvfs'),
1725 "Expecting kvvfs to be unregistered."
1727 log("kvvfs is (correctly) unavailable in a Worker.");
1728 return;
1730 const filename = 'session';
1731 const pVfs = capi.sqlite3_vfs_find('kvvfs');
1732 T.assert(pVfs);
1733 const JDb = sqlite3.oo1.JsStorageDb;
1734 const unlink = ()=>JDb.clearStorage(filename);
1735 unlink();
1736 let db = new JDb(filename);
1737 try {
1738 db.exec([
1739 'create table kvvfs(a);',
1740 'insert into kvvfs(a) values(1),(2),(3)'
1742 T.assert(3 === db.selectValue('select count(*) from kvvfs'));
1743 db.close();
1744 db = new JDb(filename);
1745 db.exec('insert into kvvfs(a) values(4),(5),(6)');
1746 T.assert(6 === db.selectValue('select count(*) from kvvfs'));
1747 }finally{
1748 db.close();
1749 unlink();
1751 }/*kvvfs sanity checks*/)
1752 ;/* end kvvfs tests */
1754 ////////////////////////////////////////////////////////////////////////
1755 T.g('OPFS (Worker thread only and only in supported browsers)',
1756 (sqlite3)=>{return !!sqlite3.opfs})
1757 .t({
1758 name: 'OPFS sanity checks',
1759 test: async function(sqlite3){
1760 const opfs = sqlite3.opfs;
1761 const filename = 'sqlite3-tester1.db';
1762 const pVfs = capi.sqlite3_vfs_find('opfs');
1763 T.assert(pVfs);
1764 const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
1765 unlink();
1766 let db = new opfs.OpfsDb(filename);
1767 try {
1768 db.exec([
1769 'create table p(a);',
1770 'insert into p(a) values(1),(2),(3)'
1772 T.assert(3 === db.selectValue('select count(*) from p'));
1773 db.close();
1774 db = new opfs.OpfsDb(filename);
1775 db.exec('insert into p(a) values(4),(5),(6)');
1776 T.assert(6 === db.selectValue('select count(*) from p'));
1777 }finally{
1778 db.close();
1779 unlink();
1782 if(1){
1783 // Sanity-test sqlite3_wasm_vfs_create_file()...
1784 const fSize = 1379;
1785 let sh;
1786 try{
1787 T.assert(!(await opfs.entryExists(filename)));
1788 let rc = wasm.sqlite3_wasm_vfs_create_file(
1789 pVfs, filename, null, fSize
1791 T.assert(0===rc)
1792 .assert(await opfs.entryExists(filename));
1793 const fh = await opfs.rootDirectory.getFileHandle(filename);
1794 sh = await fh.createSyncAccessHandle();
1795 T.assert(fSize === await sh.getSize());
1796 }finally{
1797 if(sh) await sh.close();
1798 unlink();
1802 // Some sanity checks of the opfs utility functions...
1803 const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
1804 const aDir = testDir+'/test/dir';
1805 T.assert(await opfs.mkdir(aDir), "mkdir failed")
1806 .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
1807 .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
1808 .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
1809 .assert(!(await opfs.unlink(testDir+'/test/dir')),
1810 "delete 2b should have failed (dir already deleted)")
1811 .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
1812 .assert(!(await opfs.entryExists(testDir)),
1813 "entryExists(",testDir,") should have failed");
1815 }/*OPFS sanity checks*/)
1816 ;/* end OPFS tests */
1818 ////////////////////////////////////////////////////////////////////////
1819 log("Loading and initializing sqlite3 WASM module...");
1820 if(!isUIThread()){
1822 If sqlite3.js is in a directory other than this script, in order
1823 to get sqlite3.js to resolve sqlite3.wasm properly, we have to
1824 explicitly tell it where sqlite3.js is being loaded from. We do
1825 that by passing the `sqlite3.dir=theDirName` URL argument to
1826 _this_ script. That URL argument will be seen by the JS/WASM
1827 loader and it will adjust the sqlite3.wasm path accordingly. If
1828 sqlite3.js/.wasm are in the same directory as this script then
1829 that's not needed.
1831 URL arguments passed as part of the filename via importScripts()
1832 are simply lost, and such scripts see the self.location of
1833 _this_ script.
1835 let sqlite3Js = 'sqlite3.js';
1836 const urlParams = new URL(self.location.href).searchParams;
1837 if(urlParams.has('sqlite3.dir')){
1838 sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
1840 importScripts(sqlite3Js);
1842 self.sqlite3InitModule({
1843 print: log,
1844 printErr: error
1845 }).then(function(sqlite3){
1846 //console.log('sqlite3 =',sqlite3);
1847 log("Done initializing WASM/JS bits. Running tests...");
1848 capi = sqlite3.capi;
1849 wasm = sqlite3.wasm;
1850 log("sqlite3 version:",capi.sqlite3_libversion(),
1851 capi.sqlite3_sourceid());
1852 if(wasm.bigIntEnabled){
1853 log("BigInt/int64 support is enabled.");
1854 }else{
1855 logClass('warning',"BigInt/int64 support is disabled.");
1857 if(haveWasmCTests()){
1858 log("sqlite3_wasm_test_...() APIs are available.");
1859 }else{
1860 logClass('warning',"sqlite3_wasm_test_...() APIs unavailable.");
1862 TestUtil.runTests(sqlite3);
1864 })();