xref: /sqlite-3.40.0/ext/wasm/tester1.js (revision 2cdcc7f0)
1/*
2  2022-10-12
3
4  The author disclaims copyright to this source code.  In place of a
5  legal notice, here is a blessing:
6
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.
10
11  ***********************************************************************
12
13  Main functional and regression tests for the sqlite3 WASM API.
14
15  This mini-framework works like so:
16
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.
26
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.
31*/
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.
38  */
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;
48  };
49  {
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;
56      }
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
63        };
64      }
65      return JSON.stringify(v,undefined,2);
66    };
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);
76          }
77        }
78        ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
79        logTarget.append(ln);
80      };
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);
86      };
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        });
99      };
100    }
101  }
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}});
108    }
109  };
110  const log = (...args)=>{
111    //console.log(...args);
112    logClass('',...args);
113  }
114  const warn = (...args)=>{
115    console.warn(...args);
116    logClass('warning',...args);
117  }
118  const error = (...args)=>{
119    console.error(...args);
120    logClass('error',...args);
121  };
122
123  const toss = (...args)=>{
124    error(...args);
125    throw new Error(args.join(' '));
126  };
127  const tossQuietly = (...args)=>{
128    throw new Error(args.join(' '));
129  };
130
131  const roundMs = (ms)=>Math.round(ms*100)/100;
132
133  /**
134     Helpers for writing sqlite3-specific tests.
135  */
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: '------------------------------------------------------------',
142    /**
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.
146    */
147    toBool: function(expr){
148      return (expr instanceof Function) ? !!expr() : !!expr;
149    },
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.
155    */
156    assert: function f(expr, ...msg){
157      ++this.counter;
158      if(!this.toBool(expr)){
159        throw new Error(msg.length ? msg.join(' ') : "Assertion failed.");
160      }
161      return this;
162    },
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;
171    },
172    /**
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.
182
183       If it throws, msg is used as the error report unless it's falsy,
184       in which case a default is used.
185    */
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));
197      }
198      return this;
199    },
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;
206    },
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;
213    },
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 = [];
222      };
223      TestGroup.prototype = {
224        addTest: function(testObj){
225          this.tests.push(testObj);
226          return this;
227        },
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;
236          }
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');
257            }
258          }
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);
264          }
265        }
266      };
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;
275    },
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;
283      }
284      this.currentTestGroup.addTest({
285        name, predicate, test: callback
286      });
287      return this;
288    },
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;
297          }
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);
308        }
309      }.bind(this));
310    }
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  ////////////////////////////////////////////////////////////////////////
319
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));
336
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');
340
341      try {
342        throw new sqlite3.WasmAllocError;
343      }catch(e){
344        T.assert(e instanceof Error)
345          .assert(e instanceof sqlite3.WasmAllocError);
346      }
347      try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
348      catch(e){ T.assert('SQLITE_SCHEMA' === e.message) }
349      try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
350      catch(e){
351        T.assert('SQLITE_CORRUPT'===e.message)
352          .assert(true===e.cause);
353      }
354    })
355  ////////////////////////////////////////////////////////////////////
356    .t('strglob/strlike', function(sqlite3){
357      T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
358        assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
359        assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
360        assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
361    })
362  ////////////////////////////////////////////////////////////////////
363  ;/*end of basic sanity checks*/
364
365  ////////////////////////////////////////////////////////////////////
366  T.g('C/WASM Utilities')
367    .t('sqlite3.wasm namespace', function(sqlite3){
368      const w = wasm;
369      const chr = (x)=>x.charCodeAt(0);
370      //log("heap getters...");
371      {
372        const li = [8, 16, 32];
373        if(w.bigIntEnabled) li.push(64);
374        for(const n of li){
375          const bpe = n/8;
376          const s = w.heapForSize(n,false);
377          T.assert(bpe===s.BYTES_PER_ELEMENT).
378            assert(w.heapForSize(s.constructor) === s);
379          const u = w.heapForSize(n,true);
380          T.assert(bpe===u.BYTES_PER_ELEMENT).
381            assert(s!==u).
382            assert(w.heapForSize(u.constructor) === u);
383        }
384      }
385
386      // isPtr32()
387      {
388        const ip = w.isPtr32;
389        T.assert(ip(0))
390          .assert(!ip(-1))
391          .assert(!ip(1.1))
392          .assert(!ip(0xffffffff))
393          .assert(ip(0x7fffffff))
394          .assert(!ip())
395          .assert(!ip(null)/*might change: under consideration*/)
396        ;
397      }
398
399      //log("jstrlen()...");
400      {
401        T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
402      }
403
404      //log("jstrcpy()...");
405      {
406        const fillChar = 10;
407        let ua = new Uint8Array(8), rc,
408            refill = ()=>ua.fill(fillChar);
409        refill();
410        rc = w.jstrcpy("hello", ua);
411        T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
412        refill();
413        ua[5] = chr('!');
414        rc = w.jstrcpy("HELLO", ua, 0, -1, false);
415        T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
416        refill();
417        rc = w.jstrcpy("the end", ua, 4);
418        //log("rc,ua",rc,ua);
419        T.assert(4===rc).assert(0===ua[7]).
420          assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
421        refill();
422        rc = w.jstrcpy("the end", ua, 4, -1, false);
423        T.assert(4===rc).assert(chr(' ')===ua[7]).
424          assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
425        refill();
426        rc = w.jstrcpy("", ua, 0, 1, true);
427        //log("rc,ua",rc,ua);
428        T.assert(1===rc).assert(0===ua[0]);
429        refill();
430        rc = w.jstrcpy("x", ua, 0, 1, true);
431        //log("rc,ua",rc,ua);
432        T.assert(1===rc).assert(0===ua[0]);
433        refill();
434        rc = w.jstrcpy('äbä', ua, 0, 1, true);
435        T.assert(1===rc, 'Must not write partial multi-byte char.')
436          .assert(0===ua[0]);
437        refill();
438        rc = w.jstrcpy('äbä', ua, 0, 2, true);
439        T.assert(1===rc, 'Must not write partial multi-byte char.')
440          .assert(0===ua[0]);
441        refill();
442        rc = w.jstrcpy('äbä', ua, 0, 2, false);
443        T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
444      }/*jstrcpy()*/
445
446      //log("cstrncpy()...");
447      {
448        const scope = w.scopedAllocPush();
449        try {
450          let cStr = w.scopedAllocCString("hello");
451          const n = w.cstrlen(cStr);
452          let cpy = w.scopedAlloc(n+10);
453          let rc = w.cstrncpy(cpy, cStr, n+10);
454          T.assert(n+1 === rc).
455            assert("hello" === w.cstringToJs(cpy)).
456            assert(chr('o') === w.getMemValue(cpy+n-1)).
457            assert(0 === w.getMemValue(cpy+n));
458          let cStr2 = w.scopedAllocCString("HI!!!");
459          rc = w.cstrncpy(cpy, cStr2, 3);
460          T.assert(3===rc).
461            assert("HI!lo" === w.cstringToJs(cpy)).
462            assert(chr('!') === w.getMemValue(cpy+2)).
463            assert(chr('l') === w.getMemValue(cpy+3));
464        }finally{
465          w.scopedAllocPop(scope);
466        }
467      }
468
469      //log("jstrToUintArray()...");
470      {
471        let a = w.jstrToUintArray("hello", false);
472        T.assert(5===a.byteLength).assert(chr('o')===a[4]);
473        a = w.jstrToUintArray("hello", true);
474        T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
475        a = w.jstrToUintArray("äbä", false);
476        T.assert(5===a.byteLength).assert(chr('b')===a[2]);
477        a = w.jstrToUintArray("äbä", true);
478        T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
479      }
480
481      //log("allocCString()...");
482      {
483        const cstr = w.allocCString("hällo, world");
484        const n = w.cstrlen(cstr);
485        T.assert(13 === n)
486          .assert(0===w.getMemValue(cstr+n))
487          .assert(chr('d')===w.getMemValue(cstr+n-1));
488      }
489
490      //log("scopedAlloc() and friends...");
491      {
492        const alloc = w.alloc, dealloc = w.dealloc;
493        w.alloc = w.dealloc = null;
494        T.assert(!w.scopedAlloc.level)
495          .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
496          .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
497        w.alloc = alloc;
498        T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
499        w.dealloc = dealloc;
500        T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
501          .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
502          .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
503        const asc = w.scopedAllocPush();
504        let asc2;
505        try {
506          const p1 = w.scopedAlloc(16),
507                p2 = w.scopedAlloc(16);
508          T.assert(1===w.scopedAlloc.level)
509            .assert(Number.isFinite(p1))
510            .assert(Number.isFinite(p2))
511            .assert(asc[0] === p1)
512            .assert(asc[1]===p2);
513          asc2 = w.scopedAllocPush();
514          const p3 = w.scopedAlloc(16);
515          T.assert(2===w.scopedAlloc.level)
516            .assert(Number.isFinite(p3))
517            .assert(2===asc.length)
518            .assert(p3===asc2[0]);
519
520          const [z1, z2, z3] = w.scopedAllocPtr(3);
521          T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
522            .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
523            .assert(0===w.getMemValue(z3,'i32'));
524        }finally{
525          // Pop them in "incorrect" order to make sure they behave:
526          w.scopedAllocPop(asc);
527          T.assert(0===asc.length);
528          T.mustThrowMatching(()=>w.scopedAllocPop(asc),
529                              /^Invalid state object/);
530          if(asc2){
531            T.assert(2===asc2.length,'Should be p3 and z1');
532            w.scopedAllocPop(asc2);
533            T.assert(0===asc2.length);
534            T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
535                                /^Invalid state object/);
536          }
537        }
538        T.assert(0===w.scopedAlloc.level);
539        w.scopedAllocCall(function(){
540          T.assert(1===w.scopedAlloc.level);
541          const [cstr, n] = w.scopedAllocCString("hello, world", true);
542          T.assert(12 === n)
543            .assert(0===w.getMemValue(cstr+n))
544            .assert(chr('d')===w.getMemValue(cstr+n-1));
545        });
546      }/*scopedAlloc()*/
547
548      //log("xCall()...");
549      {
550        const pJson = w.xCall('sqlite3_wasm_enum_json');
551        T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
552      }
553
554      //log("xWrap()...");
555      {
556        T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
557                            /requires 0 arg/).
558          assert(w.xWrap.resultAdapter('i32') instanceof Function).
559          assert(w.xWrap.argAdapter('i32') instanceof Function);
560        let fw = w.xWrap('sqlite3_libversion','utf8');
561        T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
562        let rc = fw();
563        T.assert('string'===typeof rc).assert(rc.length>5);
564        rc = w.xCallWrapped('sqlite3_wasm_enum_json','*');
565        T.assert(rc>0 && Number.isFinite(rc));
566        rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
567        T.assert('string'===typeof rc).assert(rc.length>300);
568        if(haveWasmCTests()){
569          fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['i32']);
570          rc = fw(0);
571          T.assert('hello'===rc);
572          rc = fw(1);
573          T.assert(null===rc);
574
575          if(w.bigIntEnabled){
576            w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
577            w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
578            fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice');
579            rc = fw(1);
580            T.assert(12n===rc);
581
582            w.scopedAllocCall(function(){
583              let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
584              w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
585              let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
586              let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
587              T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
588            });
589          }
590        }
591      }
592    }/*WhWasmUtil*/)
593
594  ////////////////////////////////////////////////////////////////////
595    .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){
596      const S = sqlite3, W = S.wasm;
597      const MyStructDef = {
598        sizeof: 16,
599        members: {
600          p4: {offset: 0, sizeof: 4, signature: "i"},
601          pP: {offset: 4, sizeof: 4, signature: "P"},
602          ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
603          cstr: {offset: 12, sizeof: 4, signature: "s"}
604        }
605      };
606      if(W.bigIntEnabled){
607        const m = MyStructDef;
608        m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
609        m.sizeof += m.members.p8.sizeof;
610      }
611      const StructType = S.StructBinder.StructType;
612      const K = S.StructBinder('my_struct',MyStructDef);
613      T.mustThrowMatching(()=>K(), /via 'new'/).
614        mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
615      const k1 = new K(), k2 = new K();
616      try {
617        T.assert(k1.constructor === K).
618          assert(K.isA(k1)).
619          assert(k1 instanceof K).
620          assert(K.prototype.lookupMember('p4').key === '$p4').
621          assert(K.prototype.lookupMember('$p4').name === 'p4').
622          mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
623          assert(undefined === K.prototype.lookupMember('nope',false)).
624          assert(k1 instanceof StructType).
625          assert(StructType.isA(k1)).
626          assert(K.resolveToInstance(k1.pointer)===k1).
627          mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
628          assert(k1 === StructType.instanceForPointer(k1.pointer)).
629          mustThrowMatching(()=>k1.$ro = 1, /read-only/);
630        Object.keys(MyStructDef.members).forEach(function(key){
631          key = K.memberKey(key);
632          T.assert(0 == k1[key],
633                   "Expecting allocation to zero the memory "+
634                   "for "+key+" but got: "+k1[key]+
635                   " from "+k1.memoryDump());
636        });
637        T.assert('number' === typeof k1.pointer).
638          mustThrowMatching(()=>k1.pointer = 1, /pointer/).
639          assert(K.instanceForPointer(k1.pointer) === k1);
640        k1.$p4 = 1; k1.$pP = 2;
641        T.assert(1 === k1.$p4).assert(2 === k1.$pP);
642        if(MyStructDef.members.$p8){
643          k1.$p8 = 1/*must not throw despite not being a BigInt*/;
644          k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
645          T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
646        }
647        T.assert(!k1.ondispose);
648        k1.setMemberCString('cstr', "A C-string.");
649        T.assert(Array.isArray(k1.ondispose)).
650          assert(k1.ondispose[0] === k1.$cstr).
651          assert('number' === typeof k1.$cstr).
652          assert('A C-string.' === k1.memberToJsString('cstr'));
653        k1.$pP = k2;
654        T.assert(k1.$pP === k2);
655        k1.$pP = null/*null is special-cased to 0.*/;
656        T.assert(0===k1.$pP);
657        let ptr = k1.pointer;
658        k1.dispose();
659        T.assert(undefined === k1.pointer).
660          assert(undefined === K.instanceForPointer(ptr)).
661          mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
662        const k3 = new K();
663        ptr = k3.pointer;
664        T.assert(k3 === K.instanceForPointer(ptr));
665        K.disposeAll();
666        T.assert(ptr).
667          assert(undefined === k2.pointer).
668          assert(undefined === k3.pointer).
669          assert(undefined === K.instanceForPointer(ptr));
670      }finally{
671        k1.dispose();
672        k2.dispose();
673      }
674
675      if(!W.bigIntEnabled){
676        log("Skipping WasmTestStruct tests: BigInt not enabled.");
677        return;
678      }
679
680      const WTStructDesc =
681            W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
682      const autoResolvePtr = true /* EXPERIMENTAL */;
683      if(autoResolvePtr){
684        WTStructDesc.members.ppV.signature = 'P';
685      }
686      const WTStruct = S.StructBinder(WTStructDesc);
687      //log(WTStruct.structName, WTStruct.structInfo);
688      const wts = new WTStruct();
689      //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
690      try{
691        T.assert(wts.constructor === WTStruct).
692          assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
693          assert(wts.memberKeys().indexOf('$v8')>=0).
694          assert(!K.isA(wts)).
695          assert(WTStruct.isA(wts)).
696          assert(wts instanceof WTStruct).
697          assert(wts instanceof StructType).
698          assert(StructType.isA(wts)).
699          assert(wts === StructType.instanceForPointer(wts.pointer));
700        T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
701          assert(0===wts.$ppV).assert(0===wts.$xFunc).
702          assert(WTStruct.instanceForPointer(wts.pointer) === wts);
703        const testFunc =
704              W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
705        let counter = 0;
706        //log("wts.pointer =",wts.pointer);
707        const wtsFunc = function(arg){
708          /*log("This from a JS function called from C, "+
709              "which itself was called from JS. arg =",arg);*/
710          ++counter;
711          T.assert(WTStruct.instanceForPointer(arg) === wts);
712          if(3===counter){
713            tossQuietly("Testing exception propagation.");
714          }
715        }
716        wts.$v4 = 10; wts.$v8 = 20;
717        wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
718        T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
719          .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
720          .assert(0 === wts.$cstr)
721          .assert(wts.memberIsString('$cstr'))
722          .assert(!wts.memberIsString('$v4'))
723          .assert(null === wts.memberToJsString('$cstr'))
724          .assert(W.functionEntry(wts.$xFunc) instanceof Function);
725        /* It might seem silly to assert that the values match
726           what we just set, but recall that all of those property
727           reads and writes are, via property interceptors,
728           actually marshaling their data to/from a raw memory
729           buffer, so merely reading them back is actually part of
730           testing the struct-wrapping API. */
731
732        testFunc(wts.pointer);
733        //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
734        T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
735          .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer))
736          .assert('string' === typeof wts.memberToJsString('cstr'))
737          .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
738          .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
739                             /Invalid member type signature for C-string/)
740        ;
741        testFunc(wts.pointer);
742        T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
743          .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer));
744        /** The 3rd call to wtsFunc throw from JS, which is called
745            from C, which is called from JS. Let's ensure that
746            that exception propagates back here... */
747        T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
748        W.uninstallFunction(wts.$xFunc);
749        wts.$xFunc = 0;
750        if(autoResolvePtr){
751          wts.$ppV = 0;
752          T.assert(!wts.$ppV);
753          //WTStruct.debugFlags(0x03);
754          wts.$ppV = wts;
755          T.assert(wts === wts.$ppV)
756          //WTStruct.debugFlags(0);
757        }
758        wts.setMemberCString('cstr', "A C-string.");
759        T.assert(Array.isArray(wts.ondispose)).
760          assert(wts.ondispose[0] === wts.$cstr).
761          assert('A C-string.' === wts.memberToJsString('cstr'));
762        const ptr = wts.pointer;
763        wts.dispose();
764        T.assert(ptr).assert(undefined === wts.pointer).
765          assert(undefined === WTStruct.instanceForPointer(ptr))
766      }finally{
767        wts.dispose();
768      }
769    }/*StructBinder*/)
770
771  ////////////////////////////////////////////////////////////////////
772    .t('sqlite3.StructBinder part 2', function(sqlite3){
773      // https://www.sqlite.org/c3ref/vfs.html
774      // https://www.sqlite.org/c3ref/io_methods.html
775      const sqlite3_io_methods = capi.sqlite3_io_methods,
776            sqlite3_vfs = capi.sqlite3_vfs,
777            sqlite3_file = capi.sqlite3_file;
778      //log("struct sqlite3_file", sqlite3_file.memberKeys());
779      //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
780      //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
781      const installMethod = function callee(tgt, name, func){
782        if(1===arguments.length){
783          return (n,f)=>callee(tgt,n,f);
784        }
785        if(!callee.argcProxy){
786          callee.argcProxy = function(func,sig){
787            return function(...args){
788              if(func.length!==arguments.length){
789                toss("Argument mismatch. Native signature is:",sig);
790              }
791              return func.apply(this, args);
792            }
793          };
794          callee.ondisposeRemoveFunc = function(){
795            if(this.__ondispose){
796              const who = this;
797              this.__ondispose.forEach(
798                (v)=>{
799                  if('number'===typeof v){
800                    try{wasm.uninstallFunction(v)}
801                    catch(e){/*ignore*/}
802                  }else{/*wasm function wrapper property*/
803                    delete who[v];
804                  }
805                }
806              );
807              delete this.__ondispose;
808            }
809          };
810        }/*static init*/
811        const sigN = tgt.memberSignature(name),
812              memKey = tgt.memberKey(name);
813        //log("installMethod",tgt, name, sigN);
814        if(!tgt.__ondispose){
815          T.assert(undefined === tgt.ondispose);
816          tgt.ondispose = [callee.ondisposeRemoveFunc];
817          tgt.__ondispose = [];
818        }
819        const fProxy = callee.argcProxy(func, sigN);
820        const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
821        tgt[memKey] = pFunc;
822        /**
823           ACHTUNG: function pointer IDs are from a different pool than
824           allocation IDs, starting at 1 and incrementing in steps of 1,
825           so if we set tgt[memKey] to those values, we'd very likely
826           later misinterpret them as plain old pointer addresses unless
827           unless we use some silly heuristic like "all values <5k are
828           presumably function pointers," or actually perform a function
829           lookup on every pointer to first see if it's a function. That
830           would likely work just fine, but would be kludgy.
831
832           It turns out that "all values less than X are functions" is
833           essentially how it works in wasm: a function pointer is
834           reported to the client as its index into the
835           __indirect_function_table.
836
837           So... once jaccwabyt can be told how to access the
838           function table, it could consider all pointer values less
839           than that table's size to be functions.  As "real" pointer
840           values start much, much higher than the function table size,
841           that would likely work reasonably well. e.g. the object
842           pointer address for sqlite3's default VFS is (in this local
843           setup) 65104, whereas the function table has fewer than 600
844           entries.
845        */
846        const wrapperKey = '$'+memKey;
847        tgt[wrapperKey] = fProxy;
848        tgt.__ondispose.push(pFunc, wrapperKey);
849        //log("tgt.__ondispose =",tgt.__ondispose);
850        return (n,f)=>callee(tgt, n, f);
851      }/*installMethod*/;
852
853      const installIOMethods = function instm(iom){
854        (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
855        if(!instm._requireFileArg){
856          instm._requireFileArg = function(arg,methodName){
857            arg = capi.sqlite3_file.resolveToInstance(arg);
858            if(!arg){
859              err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
860            }
861            return arg;
862          };
863          instm._methods = {
864            // https://sqlite.org/c3ref/io_methods.html
865            xClose: /*i(P)*/function(f){
866              /* int (*xClose)(sqlite3_file*) */
867              log("xClose(",f,")");
868              if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
869              f.dispose(/*noting that f has externally-owned memory*/);
870              return 0;
871            },
872            xRead: /*i(Ppij)*/function(f,dest,n,offset){
873              /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
874              log("xRead(",arguments,")");
875              if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
876              wasm.heap8().fill(0, dest + offset, n);
877              return 0;
878            },
879            xWrite: /*i(Ppij)*/function(f,dest,n,offset){
880              /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
881              log("xWrite(",arguments,")");
882              if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
883              return 0;
884            },
885            xTruncate: /*i(Pj)*/function(f){
886              /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
887              log("xTruncate(",arguments,")");
888              if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
889              return 0;
890            },
891            xSync: /*i(Pi)*/function(f){
892              /* int (*xSync)(sqlite3_file*, int flags) */
893              log("xSync(",arguments,")");
894              if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
895              return 0;
896            },
897            xFileSize: /*i(Pp)*/function(f,pSz){
898              /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
899              log("xFileSize(",arguments,")");
900              if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
901              wasm.setMemValue(pSz, 0/*file size*/);
902              return 0;
903            },
904            xLock: /*i(Pi)*/function(f){
905              /* int (*xLock)(sqlite3_file*, int) */
906              log("xLock(",arguments,")");
907              if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
908              return 0;
909            },
910            xUnlock: /*i(Pi)*/function(f){
911              /* int (*xUnlock)(sqlite3_file*, int) */
912              log("xUnlock(",arguments,")");
913              if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
914              return 0;
915            },
916            xCheckReservedLock: /*i(Pp)*/function(){
917              /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
918              log("xCheckReservedLock(",arguments,")");
919              return 0;
920            },
921            xFileControl: /*i(Pip)*/function(){
922              /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
923              log("xFileControl(",arguments,")");
924              return capi.SQLITE_NOTFOUND;
925            },
926            xSectorSize: /*i(P)*/function(){
927              /* int (*xSectorSize)(sqlite3_file*) */
928              log("xSectorSize(",arguments,")");
929              return 0/*???*/;
930            },
931            xDeviceCharacteristics:/*i(P)*/function(){
932              /* int (*xDeviceCharacteristics)(sqlite3_file*) */
933              log("xDeviceCharacteristics(",arguments,")");
934              return 0;
935            }
936          };
937        }/*static init*/
938        iom.$iVersion = 1;
939        Object.keys(instm._methods).forEach(
940          (k)=>installMethod(iom, k, instm._methods[k])
941        );
942      }/*installIOMethods()*/;
943
944      const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
945      const err = console.error.bind(console);
946      try {
947        const IOM = sqlite3_io_methods, S3F = sqlite3_file;
948        //log("iom proto",iom,iom.constructor.prototype);
949        //log("sfile",sfile,sfile.constructor.prototype);
950        T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
951        //log("iom",iom);
952        sfile.$pMethods = iom.pointer;
953        T.assert(iom.pointer === sfile.$pMethods)
954          .assert(IOM.resolveToInstance(iom))
955          .assert(undefined ===IOM.resolveToInstance(sfile))
956          .mustThrow(()=>IOM.resolveToInstance(0,true))
957          .assert(S3F.resolveToInstance(sfile.pointer))
958          .assert(undefined===S3F.resolveToInstance(iom))
959          .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
960        T.assert(0===iom.$iVersion);
961        installIOMethods(iom);
962        T.assert(1===iom.$iVersion);
963        //log("iom.__ondispose",iom.__ondispose);
964        T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
965      }finally{
966        iom.dispose();
967        T.assert(undefined === iom.__ondispose);
968      }
969
970      const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
971      try {
972        const SB = sqlite3.StructBinder;
973        T.assert(dVfs instanceof SB.StructType)
974          .assert(dVfs.pointer)
975          .assert('sqlite3_vfs' === dVfs.structName)
976          .assert(!!dVfs.structInfo)
977          .assert(SB.StructType.hasExternalPointer(dVfs))
978          .assert(dVfs.$iVersion>0)
979          .assert('number'===typeof dVfs.$zName)
980          .assert('number'===typeof dVfs.$xSleep)
981          .assert(wasm.functionEntry(dVfs.$xOpen))
982          .assert(dVfs.memberIsString('zName'))
983          .assert(dVfs.memberIsString('$zName'))
984          .assert(!dVfs.memberIsString('pAppData'))
985          .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
986                             /Invalid member type signature for C-string/)
987          .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
988          .assert('string' === typeof dVfs.memberToJsString('zName'))
989          .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
990        ;
991        //log("Default VFS: @",dVfs.pointer);
992        Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
993          const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
994                addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
995          if(1===mbr.signature.length){
996            let sep = '?', val = undefined;
997            switch(mbr.signature[0]){
998                  // TODO: move this into an accessor, e.g. getPreferredValue(member)
999                case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
1000                case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
1001                case 's': sep = '=';
1002                  val = dVfs.memberToJsString(mname);
1003                  break;
1004            }
1005            //log(prefix, sep, val);
1006          }else{
1007            //log(prefix," = funcptr @",addr, wasm.functionEntry(addr));
1008          }
1009        });
1010      }finally{
1011        dVfs.dispose();
1012        T.assert(undefined===dVfs.pointer);
1013      }
1014    }/*StructBinder part 2*/)
1015
1016  ////////////////////////////////////////////////////////////////////
1017    .t('sqlite3.wasm.pstack', function(sqlite3){
1018      const P = wasm.pstack;
1019      const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
1020      const stack = P.pointer;
1021      T.assert(0===stack % 8 /* must be 8-byte aligned */);
1022      try{
1023        const remaining = P.remaining;
1024        T.assert(P.quota >= 4096)
1025          .assert(remaining === P.quota)
1026          .mustThrowMatching(()=>P.alloc(0), isAllocErr)
1027          .mustThrowMatching(()=>P.alloc(-1), isAllocErr);
1028        let p1 = P.alloc(12);
1029        T.assert(p1 === stack - 16/*8-byte aligned*/)
1030          .assert(P.pointer === p1);
1031        let p2 = P.alloc(7);
1032        T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
1033          .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
1034          .assert(24 === stack - p2)
1035          .assert(P.pointer === p2);
1036        let n = remaining - (stack - p2);
1037        let p3 = P.alloc(n);
1038        T.assert(p3 === stack-remaining)
1039          .mustThrowMatching(()=>P.alloc(1), isAllocErr);
1040      }finally{
1041        P.restore(stack);
1042      }
1043
1044      T.assert(P.pointer === stack);
1045      try {
1046        const [p1, p2, p3] = P.allocChunks(3,4);
1047        T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
1048          .assert(p2 === p1 + 4)
1049          .assert(p3 === p2 + 4);
1050        T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
1051                            (e)=>e instanceof sqlite3.WasmAllocError)
1052      }finally{
1053        P.restore(stack);
1054      }
1055
1056      T.assert(P.pointer === stack);
1057      try {
1058        let [p1, p2, p3] = P.allocPtr(3,false);
1059        let sPos = stack-16/*always rounded to multiple of 8*/;
1060        T.assert(P.pointer === sPos)
1061          .assert(p2 === p1 + 4)
1062          .assert(p3 === p2 + 4);
1063        [p1, p2, p3] = P.allocPtr(3);
1064        T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
1065          .assert(p2 === p1 + 8)
1066          .assert(p3 === p2 + 8);
1067        p1 = P.allocPtr();
1068        T.assert('number'===typeof p1);
1069      }finally{
1070        P.restore(stack);
1071      }
1072    }/*pstack tests*/)
1073
1074  ////////////////////////////////////////////////////////////////////
1075  ;/*end of C/WASM utils checks*/
1076
1077  T.g('sqlite3_randomness()')
1078    .t('To memory buffer', function(sqlite3){
1079      const stack = wasm.pstack.pointer;
1080      try{
1081        const n = 520;
1082        const p = wasm.pstack.alloc(n);
1083        T.assert(0===wasm.getMemValue(p))
1084          .assert(0===wasm.getMemValue(p+n-1));
1085        T.assert(undefined === capi.sqlite3_randomness(n - 10, p));
1086        let j, check = 0;
1087        const heap = wasm.heap8u();
1088        for(j = 0; j < 10 && 0===check; ++j){
1089          check += heap[p + j];
1090        }
1091        T.assert(check > 0);
1092        check = 0;
1093        // Ensure that the trailing bytes were not modified...
1094        for(j = n - 10; j < n && 0===check; ++j){
1095          check += heap[p + j];
1096        }
1097        T.assert(0===check);
1098      }finally{
1099        wasm.pstack.restore(stack);
1100      }
1101    })
1102    .t('To byte array', function(sqlite3){
1103      const ta = new Uint8Array(117);
1104      let i, n = 0;
1105      for(i=0; i<ta.byteLength && 0===n; ++i){
1106        n += ta[i];
1107      }
1108      T.assert(0===n)
1109        .assert(ta === capi.sqlite3_randomness(ta));
1110      for(i=ta.byteLength-10; i<ta.byteLength && 0===n; ++i){
1111        n += ta[i];
1112      }
1113      T.assert(n>0);
1114      const t0 = new Uint8Array(0);
1115      T.assert(t0 === capi.sqlite3_randomness(t0),
1116               "0-length array is a special case");
1117    })
1118  ;/*end sqlite3_randomness() checks*/
1119
1120  ////////////////////////////////////////////////////////////////////////
1121  T.g('sqlite3.oo1')
1122    .t('Create db', function(sqlite3){
1123      const dbFile = '/tester1.db';
1124      wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
1125      const db = this.db = new sqlite3.oo1.DB(dbFile);
1126      T.assert(Number.isInteger(db.pointer))
1127        .mustThrowMatching(()=>db.pointer=1, /read-only/)
1128        .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
1129        .assert('main'===db.dbName(0))
1130        .assert('string' === typeof db.dbVfsName());
1131      // Custom db error message handling via sqlite3_prepare_v2/v3()
1132      let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
1133      T.assert(capi.SQLITE_MISUSE === rc)
1134        .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"))
1135        .assert(dbFile === db.dbFilename())
1136        .assert(!db.dbFilename('nope'));
1137    })
1138
1139  ////////////////////////////////////////////////////////////////////
1140    .t('DB.Stmt', function(S){
1141      let st = this.db.prepare(
1142        new TextEncoder('utf-8').encode("select 3 as a")
1143      );
1144      //debug("statement =",st);
1145      try {
1146        T.assert(Number.isInteger(st.pointer))
1147          .mustThrowMatching(()=>st.pointer=1, /read-only/)
1148          .assert(1===this.db.openStatementCount())
1149          .assert(!st._mayGet)
1150          .assert('a' === st.getColumnName(0))
1151          .assert(1===st.columnCount)
1152          .assert(0===st.parameterCount)
1153          .mustThrow(()=>st.bind(1,null))
1154          .assert(true===st.step())
1155          .assert(3 === st.get(0))
1156          .mustThrow(()=>st.get(1))
1157          .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
1158          .assert(3 === st.get(0,capi.SQLITE_INTEGER))
1159          .assert(3 === st.getInt(0))
1160          .assert('3' === st.get(0,capi.SQLITE_TEXT))
1161          .assert('3' === st.getString(0))
1162          .assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
1163          .assert(3.0 === st.getFloat(0))
1164          .assert(3 === st.get({}).a)
1165          .assert(3 === st.get([])[0])
1166          .assert(3 === st.getJSON(0))
1167          .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
1168          .assert(1===st.get(0,capi.SQLITE_BLOB).length)
1169          .assert(st.getBlob(0) instanceof Uint8Array)
1170          .assert('3'.charCodeAt(0) === st.getBlob(0)[0])
1171          .assert(st._mayGet)
1172          .assert(false===st.step())
1173          .assert(!st._mayGet)
1174        ;
1175        T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
1176          assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
1177          assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
1178          assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
1179      }finally{
1180        st.finalize();
1181      }
1182      T.assert(!st.pointer)
1183        .assert(0===this.db.openStatementCount());
1184    })
1185
1186  ////////////////////////////////////////////////////////////////////////
1187    .t('sqlite3_js_...()', function(){
1188      const db = this.db;
1189      if(1){
1190        const vfsList = capi.sqlite3_js_vfs_list();
1191        T.assert(vfsList.length>1);
1192        T.assert('string'===typeof vfsList[0]);
1193        //log("vfsList =",vfsList);
1194        for(const v of vfsList){
1195          T.assert('string' === typeof v)
1196            .assert(capi.sqlite3_vfs_find(v) > 0);
1197        }
1198      }
1199      /**
1200         Trivia: the magic db name ":memory:" does not actually use the
1201         "memdb" VFS unless "memdb" is _explicitly_ provided as the VFS
1202         name. Instead, it uses the default VFS with an in-memory btree.
1203         Thus this.db's VFS may not be memdb even though it's an in-memory
1204         db.
1205      */
1206      const pVfsMem = capi.sqlite3_vfs_find('memdb'),
1207            pVfsDflt = capi.sqlite3_vfs_find(0),
1208            pVfsDb = capi.sqlite3_js_db_vfs(db.pointer);
1209      T.assert(pVfsMem > 0)
1210        .assert(pVfsDflt > 0)
1211        .assert(pVfsDb > 0)
1212        .assert(pVfsMem !== pVfsDflt
1213                /* memdb lives on top of the default vfs */)
1214        .assert(pVfsDb === pVfsDflt || pVfsdb === pVfsMem)
1215      ;
1216      /*const vMem = new capi.sqlite3_vfs(pVfsMem),
1217        vDflt = new capi.sqlite3_vfs(pVfsDflt),
1218        vDb = new capi.sqlite3_vfs(pVfsDb);*/
1219      const duv = capi.sqlite3_js_db_uses_vfs;
1220      T.assert(pVfsDflt === duv(db.pointer, 0)
1221               || pVfsMem === duv(db.pointer,0))
1222        .assert(!duv(db.pointer, "foo"))
1223      ;
1224    }/*sqlite3_js_...()*/)
1225
1226  ////////////////////////////////////////////////////////////////////
1227    .t('Table t', function(sqlite3){
1228      const db = this.db;
1229      let list = [];
1230      let rc = db.exec({
1231        sql:['CREATE TABLE t(a,b);',
1232             // ^^^ using TEMP TABLE breaks the db export test
1233             "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
1234             "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for
1235                                       off-by-one bug in string-to-WASM conversion*/],
1236        saveSql: list,
1237        bind: [5,6]
1238      });
1239      //debug("Exec'd SQL:", list);
1240      T.assert(rc === db)
1241        .assert(2 === list.length)
1242        .assert('string'===typeof list[1])
1243        .assert(4===db.changes());
1244      if(wasm.bigIntEnabled){
1245        T.assert(4n===db.changes(false,true));
1246      }
1247      let blob = db.selectValue("select b from t where a='blob'");
1248      T.assert(blob instanceof Uint8Array).
1249        assert(0x68===blob[0] && 0x69===blob[1]);
1250      blob = null;
1251      let counter = 0, colNames = [];
1252      list.length = 0;
1253      db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
1254        rowMode: 'object',
1255        resultRows: list,
1256        columnNames: colNames,
1257        callback: function(row,stmt){
1258          ++counter;
1259          T.assert((row.a%2 && row.a<6) || 'blob'===row.a);
1260        }
1261      });
1262      T.assert(2 === colNames.length)
1263        .assert('a' === colNames[0])
1264        .assert(4 === counter)
1265        .assert(4 === list.length);
1266      list.length = 0;
1267      db.exec("SELECT a a, b b FROM t",{
1268        rowMode: 'array',
1269        callback: function(row,stmt){
1270          ++counter;
1271          T.assert(Array.isArray(row))
1272            .assert((0===row[1]%2 && row[1]<7)
1273                    || (row[1] instanceof Uint8Array));
1274        }
1275      });
1276      T.assert(8 === counter);
1277      T.assert(Number.MIN_SAFE_INTEGER ===
1278               db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
1279        assert(Number.MAX_SAFE_INTEGER ===
1280               db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
1281      if(wasm.bigIntEnabled && haveWasmCTests()){
1282        const mI = wasm.xCall('sqlite3_wasm_test_int64_max');
1283        const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
1284        T.assert(b === db.selectValue("SELECT "+b)).
1285          assert(b === db.selectValue("SELECT ?", b)).
1286          assert(mI == db.selectValue("SELECT $x", {$x:mI}));
1287      }else{
1288        /* Curiously, the JS spec seems to be off by one with the definitions
1289           of MIN/MAX_SAFE_INTEGER:
1290
1291           https://github.com/emscripten-core/emscripten/issues/17391 */
1292        T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
1293          mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
1294      }
1295
1296      let st = db.prepare("update t set b=:b where a='blob'");
1297      try {
1298        const ndx = st.getParamIndex(':b');
1299        T.assert(1===ndx);
1300        st.bindAsBlob(ndx, "ima blob").reset(true);
1301      } finally {
1302        st.finalize();
1303      }
1304
1305      try {
1306        db.prepare("/*empty SQL*/");
1307        toss("Must not be reached.");
1308      }catch(e){
1309        T.assert(e instanceof sqlite3.SQLite3Error)
1310          .assert(0==e.message.indexOf('Cannot prepare empty'));
1311      }
1312    })
1313
1314  ////////////////////////////////////////////////////////////////////////
1315    .t('selectArray/Object()', function(sqlite3){
1316      const db = this.db;
1317      let rc = db.selectArray('select a, b from t where a=?', 5);
1318      T.assert(Array.isArray(rc))
1319        .assert(2===rc.length)
1320        .assert(5===rc[0] && 6===rc[1]);
1321      rc = db.selectArray('select a, b from t where b=-1');
1322      T.assert(undefined === rc);
1323      rc = db.selectObject('select a A, b b from t where b=?', 6);
1324      T.assert(rc && 'object'===typeof rc)
1325        .assert(5===rc.A)
1326        .assert(6===rc.b);
1327      rc = db.selectArray('select a, b from t where b=-1');
1328      T.assert(undefined === rc);
1329    })
1330
1331  ////////////////////////////////////////////////////////////////////////
1332    .t('sqlite3_js_db_export()', function(){
1333      const db = this.db;
1334      const xp = capi.sqlite3_js_db_export(db.pointer);
1335      T.assert(xp instanceof Uint8Array)
1336        .assert(xp.byteLength>0)
1337        .assert(0 === xp.byteLength % 512);
1338    }/*sqlite3_js_db_export()*/)
1339
1340  ////////////////////////////////////////////////////////////////////
1341    .t('Scalar UDFs', function(sqlite3){
1342      const db = this.db;
1343      db.createFunction("foo",(pCx,a,b)=>a+b);
1344      T.assert(7===db.selectValue("select foo(3,4)")).
1345        assert(5===db.selectValue("select foo(3,?)",2)).
1346        assert(5===db.selectValue("select foo(?,?2)",[1,4])).
1347        assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
1348      db.createFunction("bar", {
1349        arity: -1,
1350        xFunc: (pCx,...args)=>{
1351          let rc = 0;
1352          for(const v of args) rc += v;
1353          return rc;
1354        }
1355      }).createFunction({
1356        name: "asis",
1357        xFunc: (pCx,arg)=>arg
1358      });
1359      T.assert(0===db.selectValue("select bar()")).
1360        assert(1===db.selectValue("select bar(1)")).
1361        assert(3===db.selectValue("select bar(1,2)")).
1362        assert(-1===db.selectValue("select bar(1,2,-4)")).
1363        assert('hi' === db.selectValue("select asis('hi')")).
1364        assert('hi' === db.selectValue("select ?",'hi')).
1365        assert(null === db.selectValue("select null")).
1366        assert(null === db.selectValue("select asis(null)")).
1367        assert(1 === db.selectValue("select ?",1)).
1368        assert(2 === db.selectValue("select ?",[2])).
1369        assert(3 === db.selectValue("select $a",{$a:3})).
1370        assert(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
1371        assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
1372
1373      let blobArg = new Uint8Array(2);
1374      blobArg.set([0x68, 0x69], 0);
1375      let blobRc = db.selectValue("select asis(?1)", blobArg);
1376      T.assert(blobRc instanceof Uint8Array).
1377        assert(2 === blobRc.length).
1378        assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1379      blobRc = db.selectValue("select asis(X'6869')");
1380      T.assert(blobRc instanceof Uint8Array).
1381        assert(2 === blobRc.length).
1382        assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1383
1384      blobArg = new Int8Array(2);
1385      blobArg.set([0x68, 0x69]);
1386      //debug("blobArg=",blobArg);
1387      blobRc = db.selectValue("select asis(?1)", blobArg);
1388      T.assert(blobRc instanceof Uint8Array).
1389        assert(2 === blobRc.length);
1390      //debug("blobRc=",blobRc);
1391      T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
1392    })
1393
1394  ////////////////////////////////////////////////////////////////////
1395    .t({
1396      name: 'Aggregate UDFs',
1397      test: function(sqlite3){
1398        const db = this.db;
1399        const sjac = capi.sqlite3_js_aggregate_context;
1400        db.createFunction({
1401          name: 'summer',
1402          xStep: (pCtx, n)=>{
1403            const ac = sjac(pCtx, 4);
1404            wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32');
1405          },
1406          xFinal: (pCtx)=>{
1407            const ac = sjac(pCtx, 0);
1408            return ac ? wasm.getMemValue(ac,'i32') : 0;
1409          }
1410        });
1411        let v = db.selectValue([
1412          "with cte(v) as (",
1413          "select 3 union all select 5 union all select 7",
1414          ") select summer(v), summer(v+1) from cte"
1415          /* ------------------^^^^^^^^^^^ ensures that we're handling
1416              sqlite3_aggregate_context() properly. */
1417        ]);
1418        T.assert(15===v);
1419        T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
1420                            /wrong number of arguments/);
1421
1422        db.createFunction({
1423          name: 'summerN',
1424          arity: -1,
1425          xStep: (pCtx, ...args)=>{
1426            const ac = sjac(pCtx, 4);
1427            let sum = wasm.getMemValue(ac, 'i32');
1428            for(const v of args) sum += Number(v);
1429            wasm.setMemValue(ac, sum, 'i32');
1430          },
1431          xFinal: (pCtx)=>{
1432            const ac = sjac(pCtx, 0);
1433            capi.sqlite3_result_int( pCtx, ac ? wasm.getMemValue(ac,'i32') : 0 );
1434            // xFinal() may either return its value directly or call
1435            // sqlite3_result_xyz() and return undefined. Both are
1436            // functionally equivalent.
1437          }
1438        });
1439        T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)'));
1440        T.mustThrowMatching(()=>{
1441          db.createFunction('nope',{
1442            xFunc: ()=>{}, xStep: ()=>{}
1443          });
1444        }, /scalar or aggregate\?/);
1445        T.mustThrowMatching(()=>{
1446          db.createFunction('nope',{xStep: ()=>{}});
1447        }, /Missing xFinal/);
1448        T.mustThrowMatching(()=>{
1449          db.createFunction('nope',{xFinal: ()=>{}});
1450        }, /Missing xStep/);
1451        T.mustThrowMatching(()=>{
1452          db.createFunction('nope',{});
1453        }, /Missing function-type properties/);
1454        T.mustThrowMatching(()=>{
1455          db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
1456        }, /xDestroy property must be a function/);
1457        T.mustThrowMatching(()=>{
1458          db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
1459        }, /Invalid value for pApp/);
1460     }
1461    }/*aggregate UDFs*/)
1462
1463  ////////////////////////////////////////////////////////////////////////
1464    .t({
1465      name: 'Aggregate UDFs (64-bit)',
1466      predicate: ()=>wasm.bigIntEnabled,
1467      test: function(sqlite3){
1468        const db = this.db;
1469        const sjac = capi.sqlite3_js_aggregate_context;
1470        db.createFunction({
1471          name: 'summer64',
1472          xStep: (pCtx, n)=>{
1473            const ac = sjac(pCtx, 8);
1474            wasm.setMemValue(ac, wasm.getMemValue(ac,'i64') + BigInt(n), 'i64');
1475          },
1476          xFinal: (pCtx)=>{
1477            const ac = sjac(pCtx, 0);
1478            return ac ? wasm.getMemValue(ac,'i64') : 0n;
1479          }
1480        });
1481        let v = db.selectValue([
1482          "with cte(v) as (",
1483          "select 9007199254740991 union all select 1 union all select 2",
1484          ") select summer64(v), summer64(v+1) from cte"
1485        ]);
1486        T.assert(9007199254740994n===v);
1487     }
1488    }/*aggregate UDFs*/)
1489
1490  ////////////////////////////////////////////////////////////////////
1491    .t({
1492      name: 'Window UDFs',
1493      test: function(){
1494        /* Example window function, table, and results taken from:
1495           https://sqlite.org/windowfunctions.html#udfwinfunc */
1496        const db = this.db;
1497        const sjac = (cx,n=4)=>capi.sqlite3_js_aggregate_context(cx,n);
1498        const xValueFinal = (pCtx)=>{
1499          const ac = sjac(pCtx, 0);
1500          return ac ? wasm.getMemValue(ac,'i32') : 0;
1501        };
1502        const xStepInverse = (pCtx, n)=>{
1503          const ac = sjac(pCtx);
1504          wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32');
1505        };
1506        db.createFunction({
1507          name: 'winsumint',
1508          xStep: (pCtx, n)=>xStepInverse(pCtx, n),
1509          xInverse: (pCtx, n)=>xStepInverse(pCtx, -n),
1510          xFinal: xValueFinal,
1511          xValue: xValueFinal
1512        });
1513        db.exec([
1514          "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
1515          "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
1516        ]);
1517        let rc = db.exec({
1518          returnValue: 'resultRows',
1519          sql:[
1520            "SELECT x, winsumint(y) OVER (",
1521            "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1522            ") AS sum_y ",
1523            "FROM twin ORDER BY x;"
1524          ]
1525        });
1526        T.assert(Array.isArray(rc))
1527          .assert(5 === rc.length);
1528        let count = 0;
1529        for(const row of rc){
1530          switch(++count){
1531              case 1: T.assert('a'===row[0] && 9===row[1]); break;
1532              case 2: T.assert('b'===row[0] && 12===row[1]); break;
1533              case 3: T.assert('c'===row[0] && 16===row[1]); break;
1534              case 4: T.assert('d'===row[0] && 12===row[1]); break;
1535              case 5: T.assert('e'===row[0] && 9===row[1]); break;
1536              default: toss("Too many rows to window function.");
1537          }
1538        }
1539        const resultRows = [];
1540        rc = db.exec({
1541          resultRows,
1542          returnValue: 'resultRows',
1543          sql:[
1544            "SELECT x, winsumint(y) OVER (",
1545            "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING",
1546            ") AS sum_y ",
1547            "FROM twin ORDER BY x;"
1548          ]
1549        });
1550        T.assert(rc === resultRows)
1551          .assert(5 === rc.length);
1552
1553        rc = db.exec({
1554          returnValue: 'saveSql',
1555          sql: "select 1; select 2; -- empty\n; select 3"
1556        });
1557        T.assert(Array.isArray(rc))
1558          .assert(3===rc.length)
1559          .assert('select 1;' === rc[0])
1560          .assert('select 2;' === rc[1])
1561          .assert('-- empty\n; select 3' === rc[2]
1562                  /* Strange but true. */);
1563
1564        T.mustThrowMatching(()=>{
1565          db.exec({sql:'', returnValue: 'nope'});
1566        }, /^Invalid returnValue/);
1567
1568        db.exec("DROP TABLE twin");
1569      }
1570    }/*window UDFs*/)
1571
1572  ////////////////////////////////////////////////////////////////////
1573    .t("ATTACH", function(){
1574      const db = this.db;
1575      const resultRows = [];
1576      db.exec({
1577        sql:new TextEncoder('utf-8').encode([
1578          // ^^^ testing string-vs-typedarray handling in exec()
1579          "attach 'session' as foo;",
1580          "create table foo.bar(a);",
1581          "insert into foo.bar(a) values(1),(2),(3);",
1582          "select a from foo.bar order by a;"
1583        ].join('')),
1584        rowMode: 0,
1585        resultRows
1586      });
1587      T.assert(3===resultRows.length)
1588        .assert(2===resultRows[1]);
1589      T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
1590      let colCount = 0, rowCount = 0;
1591      const execCallback = function(pVoid, nCols, aVals, aNames){
1592        colCount = nCols;
1593        ++rowCount;
1594        T.assert(2===aVals.length)
1595          .assert(2===aNames.length)
1596          .assert(+(aVals[1]) === 2 * +(aVals[0]));
1597      };
1598      let rc = capi.sqlite3_exec(
1599        db.pointer, "select a, a*2 from foo.bar", execCallback,
1600        0, 0
1601      );
1602      T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
1603      rc = capi.sqlite3_exec(
1604        db.pointer, "select a from foo.bar", ()=>{
1605          tossQuietly("Testing throwing from exec() callback.");
1606        }, 0, 0
1607      );
1608      T.assert(capi.SQLITE_ABORT === rc);
1609      db.exec("detach foo");
1610      T.mustThrow(()=>db.exec("select * from foo.bar"));
1611    })
1612
1613  ////////////////////////////////////////////////////////////////////
1614    .t({
1615      name: 'C-side WASM tests (if compiled in)',
1616      predicate: haveWasmCTests,
1617      test: function(){
1618        const w = wasm, db = this.db;
1619        const stack = w.scopedAllocPush();
1620        let ptrInt;
1621        const origValue = 512;
1622        const ptrValType = 'i32';
1623        try{
1624          ptrInt = w.scopedAlloc(4);
1625          w.setMemValue(ptrInt,origValue, ptrValType);
1626          const cf = w.xGet('sqlite3_wasm_test_intptr');
1627          const oldPtrInt = ptrInt;
1628          //log('ptrInt',ptrInt);
1629          //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
1630          T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
1631          const rc = cf(ptrInt);
1632          //log('cf(ptrInt)',rc);
1633          //log('ptrInt',ptrInt);
1634          //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
1635          T.assert(2*origValue === rc).
1636            assert(rc === w.getMemValue(ptrInt,ptrValType)).
1637            assert(oldPtrInt === ptrInt);
1638          const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
1639          const o64 = 0x010203040506/*>32-bit integer*/;
1640          const ptrType64 = 'i64';
1641          if(w.bigIntEnabled){
1642            w.setMemValue(pi64, o64, ptrType64);
1643            //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
1644            const v64 = ()=>w.getMemValue(pi64,ptrType64)
1645            //log("getMemValue(pi64)",v64());
1646            T.assert(v64() == o64);
1647            //T.assert(o64 === w.getMemValue(pi64, ptrType64));
1648            const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
1649            cf64w(pi64);
1650            //log("getMemValue(pi64)",v64());
1651            T.assert(v64() == BigInt(2 * o64));
1652            cf64w(pi64);
1653            T.assert(v64() == BigInt(4 * o64));
1654
1655            const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2');
1656            T.assert(BigInt(2 * o64) ===
1657                     biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
1658                                           in the call :/ */));
1659
1660            const pMin = w.scopedAlloc(16);
1661            const pMax = pMin + 8;
1662            const g64 = (p)=>w.getMemValue(p,ptrType64);
1663            w.setMemValue(pMin, 0, ptrType64);
1664            w.setMemValue(pMax, 0, ptrType64);
1665            const minMaxI64 = [
1666              w.xCall('sqlite3_wasm_test_int64_min'),
1667              w.xCall('sqlite3_wasm_test_int64_max')
1668            ];
1669            T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
1670              assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
1671            //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
1672            w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax);
1673            T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
1674              assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
1675            //log("pMin",g64(pMin), "pMax",g64(pMax));
1676            w.setMemValue(pMin, minMaxI64[0], ptrType64);
1677            T.assert(g64(pMin) === minMaxI64[0]).
1678              assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
1679              assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
1680            const rxRange = /too big/;
1681            T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
1682                                rxRange).
1683              mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
1684                                (e)=>rxRange.test(e.message));
1685          }else{
1686            log("No BigInt support. Skipping related tests.");
1687            log("\"The problem\" here is that we can manipulate, at the byte level,",
1688                "heap memory to set 64-bit values, but we can't get those values",
1689                "back into JS because of the lack of 64-bit integer support.");
1690          }
1691        }finally{
1692          const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
1693          //log("x=",x,"y=",y,"z=",z); // just looking at the alignment
1694          w.scopedAllocPop(stack);
1695        }
1696      }
1697    }/* jaccwabyt-specific tests */)
1698
1699    .t('Close db', function(){
1700      T.assert(this.db).assert(Number.isInteger(this.db.pointer));
1701      wasm.exports.sqlite3_wasm_db_reset(this.db.pointer);
1702      this.db.close();
1703      T.assert(!this.db.pointer);
1704    })
1705  ;/* end of oo1 checks */
1706
1707  ////////////////////////////////////////////////////////////////////////
1708  T.g('kvvfs')
1709    .t('kvvfs sanity checks', function(sqlite3){
1710      if(isWorker()){
1711        T.assert(
1712          !capi.sqlite3_vfs_find('kvvfs'),
1713          "Expecting kvvfs to be unregistered."
1714        );
1715        log("kvvfs is (correctly) unavailable in a Worker.");
1716        return;
1717      }
1718      const filename = 'session';
1719      const pVfs = capi.sqlite3_vfs_find('kvvfs');
1720      T.assert(pVfs);
1721      const JDb = sqlite3.oo1.JsStorageDb;
1722      const unlink = ()=>JDb.clearStorage(filename);
1723      unlink();
1724      let db = new JDb(filename);
1725      try {
1726        db.exec([
1727          'create table kvvfs(a);',
1728          'insert into kvvfs(a) values(1),(2),(3)'
1729        ]);
1730        T.assert(3 === db.selectValue('select count(*) from kvvfs'));
1731        db.close();
1732        db = new JDb(filename);
1733        db.exec('insert into kvvfs(a) values(4),(5),(6)');
1734        T.assert(6 === db.selectValue('select count(*) from kvvfs'));
1735      }finally{
1736        db.close();
1737        unlink();
1738      }
1739    }/*kvvfs sanity checks*/)
1740  ;/* end kvvfs tests */
1741
1742  ////////////////////////////////////////////////////////////////////////
1743  T.g('OPFS (Worker thread only and only in supported browsers)',
1744      (sqlite3)=>{return !!sqlite3.opfs})
1745    .t({
1746      name: 'OPFS sanity checks',
1747      test: async function(sqlite3){
1748        const opfs = sqlite3.opfs;
1749        const filename = 'sqlite3-tester1.db';
1750        const pVfs = capi.sqlite3_vfs_find('opfs');
1751        T.assert(pVfs);
1752        const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
1753        unlink();
1754        let db = new opfs.OpfsDb(filename);
1755        try {
1756          db.exec([
1757            'create table p(a);',
1758            'insert into p(a) values(1),(2),(3)'
1759          ]);
1760          T.assert(3 === db.selectValue('select count(*) from p'));
1761          db.close();
1762          db = new opfs.OpfsDb(filename);
1763          db.exec('insert into p(a) values(4),(5),(6)');
1764          T.assert(6 === db.selectValue('select count(*) from p'));
1765        }finally{
1766          db.close();
1767          unlink();
1768        }
1769
1770        if(1){
1771          // Sanity-test sqlite3_wasm_vfs_create_file()...
1772          const fSize = 1379;
1773          let sh;
1774          try{
1775            T.assert(!(await opfs.entryExists(filename)));
1776            let rc = wasm.sqlite3_wasm_vfs_create_file(
1777              pVfs, filename, null, fSize
1778            );
1779            T.assert(0===rc)
1780              .assert(await opfs.entryExists(filename));
1781            const fh = await opfs.rootDirectory.getFileHandle(filename);
1782            sh = await fh.createSyncAccessHandle();
1783            T.assert(fSize === await sh.getSize());
1784          }finally{
1785            if(sh) sh.close();
1786            unlink();
1787          }
1788        }
1789
1790        // Some sanity checks of the opfs utility functions...
1791        const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
1792        const aDir = testDir+'/test/dir';
1793        T.assert(await opfs.mkdir(aDir), "mkdir failed")
1794          .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
1795          .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
1796          .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
1797          .assert(!(await opfs.unlink(testDir+'/test/dir')),
1798                  "delete 2b should have failed (dir already deleted)")
1799          .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
1800          .assert(!(await opfs.entryExists(testDir)),
1801                  "entryExists(",testDir,") should have failed");
1802      }
1803    }/*OPFS sanity checks*/)
1804  ;/* end OPFS tests */
1805
1806  ////////////////////////////////////////////////////////////////////////
1807  log("Loading and initializing sqlite3 WASM module...");
1808  if(!isUIThread()){
1809    /*
1810      If sqlite3.js is in a directory other than this script, in order
1811      to get sqlite3.js to resolve sqlite3.wasm properly, we have to
1812      explicitly tell it where sqlite3.js is being loaded from. We do
1813      that by passing the `sqlite3.dir=theDirName` URL argument to
1814      _this_ script. That URL argument will be seen by the JS/WASM
1815      loader and it will adjust the sqlite3.wasm path accordingly. If
1816      sqlite3.js/.wasm are in the same directory as this script then
1817      that's not needed.
1818
1819      URL arguments passed as part of the filename via importScripts()
1820      are simply lost, and such scripts see the self.location of
1821      _this_ script.
1822    */
1823    let sqlite3Js = 'sqlite3.js';
1824    const urlParams = new URL(self.location.href).searchParams;
1825    if(urlParams.has('sqlite3.dir')){
1826      sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
1827    }
1828    importScripts(sqlite3Js);
1829  }
1830  self.sqlite3InitModule({
1831    print: log,
1832    printErr: error
1833  }).then(function(sqlite3){
1834    //console.log('sqlite3 =',sqlite3);
1835    log("Done initializing WASM/JS bits. Running tests...");
1836    capi = sqlite3.capi;
1837    wasm = sqlite3.wasm;
1838    log("sqlite3 version:",capi.sqlite3_libversion(),
1839        capi.sqlite3_sourceid());
1840    if(wasm.bigIntEnabled){
1841      log("BigInt/int64 support is enabled.");
1842    }else{
1843      logClass('warning',"BigInt/int64 support is disabled.");
1844    }
1845    if(haveWasmCTests()){
1846      log("sqlite3_wasm_test_...() APIs are available.");
1847    }else{
1848      logClass('warning',"sqlite3_wasm_test_...() APIs unavailable.");
1849    }
1850    TestUtil.runTests(sqlite3);
1851  });
1852})();
1853