xref: /sqlite-3.40.0/ext/wasm/tester1.js (revision 690d4c54)
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          .assert("Allocation failed." === e.message);
347      }
348      try {
349        throw new sqlite3.WasmAllocError("test",{
350          cause: 3
351        });
352      }catch(e){
353        T.assert(3 === e.cause)
354          .assert("test" === e.message);
355      }
356      try {throw new sqlite3.WasmAllocError("test","ing",".")}
357      catch(e){T.assert("test ing ." === e.message)}
358
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);
365      }
366    })
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));
373    })
374  ////////////////////////////////////////////////////////////////////
375  ;/*end of basic sanity checks*/
376
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...");
383      {
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);
395        }
396      }
397
398      // isPtr32()
399      {
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*/)
408        ;
409      }
410
411      //log("jstrlen()...");
412      {
413        T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
414      }
415
416      //log("jstrcpy()...");
417      {
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()*/
457
458      //log("cstrncpy()...");
459      {
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);
478        }
479      }
480
481      //log("jstrToUintArray()...");
482      {
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]);
491      }
492
493      //log("allocCString()...");
494      {
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));
500      }
501
502      //log("scopedAlloc() and friends...");
503      {
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]);
531
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/);
548          }
549        }
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));
557        });
558      }/*scopedAlloc()*/
559
560      //log("xCall()...");
561      {
562        const pJson = w.xCall('sqlite3_wasm_enum_json');
563        T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
564      }
565
566      //log("xWrap()...");
567      {
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);
586
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);
593
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));
600            });
601          }
602        }
603      }
604    }/*WhWasmUtil*/)
605
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"}
616        }
617      };
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;
622      }
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());
648        });
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);
658        }
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();
685      }
686
687      if(!W.bigIntEnabled){
688        log("Skipping WasmTestStruct tests: BigInt not enabled.");
689        return;
690      }
691
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';
697      }
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.");
726          }
727        }
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. */
743
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/)
752        ;
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);
769        }
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();
780      }
781    }/*StructBinder*/)
782
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);
796        }
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);
802              }
803              return func.apply(this, args);
804            }
805          };
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];
816                  }
817                }
818              );
819              delete this.__ondispose;
820            }
821          };
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 = [];
830        }
831        const fProxy = callee.argcProxy(func, sigN);
832        const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
833        tgt[memKey] = pFunc;
834        /**
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.
843
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.
848
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.
857        */
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*/;
864
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.");
872            }
873            return arg;
874          };
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;
883            },
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;
890            },
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;
896            },
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;
902            },
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;
908            },
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;
915            },
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;
921            },
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;
927            },
928            xCheckReservedLock: /*i(Pp)*/function(){
929              /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
930              log("xCheckReservedLock(",arguments,")");
931              return 0;
932            },
933            xFileControl: /*i(Pip)*/function(){
934              /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
935              log("xFileControl(",arguments,")");
936              return capi.SQLITE_NOTFOUND;
937            },
938            xSectorSize: /*i(P)*/function(){
939              /* int (*xSectorSize)(sqlite3_file*) */
940              log("xSectorSize(",arguments,")");
941              return 0/*???*/;
942            },
943            xDeviceCharacteristics:/*i(P)*/function(){
944              /* int (*xDeviceCharacteristics)(sqlite3_file*) */
945              log("xDeviceCharacteristics(",arguments,")");
946              return 0;
947            }
948          };
949        }/*static init*/
950        iom.$iVersion = 1;
951        Object.keys(instm._methods).forEach(
952          (k)=>installMethod(iom, k, instm._methods[k])
953        );
954      }/*installIOMethods()*/;
955
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);
980      }
981
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'))
1002        ;
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;
1016            }
1017            //log(prefix, sep, val);
1018          }else{
1019            //log(prefix," = funcptr @",addr, wasm.functionEntry(addr));
1020          }
1021        });
1022      }finally{
1023        dVfs.dispose();
1024        T.assert(undefined===dVfs.pointer);
1025      }
1026    }/*StructBinder part 2*/)
1027
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);
1054      }
1055
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);
1066      }
1067
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);
1083      }
1084    }/*pstack tests*/)
1085
1086  ////////////////////////////////////////////////////////////////////
1087  ;/*end of C/WASM utils checks*/
1088
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];
1102        }
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];
1108        }
1109        T.assert(0===check);
1110      }finally{
1111        wasm.pstack.restore(stack);
1112      }
1113    })
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];
1119      }
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];
1124      }
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");
1129    })
1130  ;/*end sqlite3_randomness() checks*/
1131
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'));
1149    })
1150
1151  ////////////////////////////////////////////////////////////////////
1152    .t('DB.Stmt', function(S){
1153      let st = this.db.prepare(
1154        new TextEncoder('utf-8').encode("select 3 as a")
1155      );
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)
1186        ;
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();
1193      }
1194      T.assert(!st.pointer)
1195        .assert(0===this.db.openStatementCount());
1196    })
1197
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);
1209        }
1210      }
1211      /**
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
1216         db.
1217      */
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)
1227      ;
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"))
1235      ;
1236    }/*sqlite3_js_...()*/)
1237
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]
1250      });
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));
1258      }
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);
1272        }
1273      });
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));
1286        }
1287      });
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:
1302
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)));
1306      }
1307
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();
1315      }
1316
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'));
1323      }
1324    })
1325
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);
1341    })
1342
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()*/)
1351
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;
1366        }
1367      }).createFunction({
1368        name: "asis",
1369        xFunc: (pCx,arg)=>arg
1370      });
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)")));
1384
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]);
1395
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]);
1404    })
1405
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');
1417          },
1418          xFinal: (pCtx)=>{
1419            const ac = sjac(pCtx, 0);
1420            return ac ? wasm.getMemValue(ac,'i32') : 0;
1421          }
1422        });
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. */
1429        ]);
1430        T.assert(15===v);
1431        T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
1432                            /wrong number of arguments/);
1433
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');
1442          },
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.
1449          }
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: ()=>{}
1455          });
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/);
1472     }
1473    }/*aggregate UDFs*/)
1474
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');
1487          },
1488          xFinal: (pCtx)=>{
1489            const ac = sjac(pCtx, 0);
1490            return ac ? wasm.getMemValue(ac,'i64') : 0n;
1491          }
1492        });
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"
1497        ]);
1498        T.assert(9007199254740994n===v);
1499     }
1500    }/*aggregate UDFs*/)
1501
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;
1513        };
1514        const xStepInverse = (pCtx, n)=>{
1515          const ac = sjac(pCtx);
1516          wasm.setMemValue(ac, wasm.getMemValue(ac,'i32') + Number(n), 'i32');
1517        };
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
1524        });
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)"
1528        ]);
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;"
1536          ]
1537        });
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.");
1549          }
1550        }
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;"
1560          ]
1561        });
1562        T.assert(rc === resultRows)
1563          .assert(5 === rc.length);
1564
1565        rc = db.exec({
1566          returnValue: 'saveSql',
1567          sql: "select 1; select 2; -- empty\n; select 3"
1568        });
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. */);
1575
1576        T.mustThrowMatching(()=>{
1577          db.exec({sql:'', returnValue: 'nope'});
1578        }, /^Invalid returnValue/);
1579
1580        db.exec("DROP TABLE twin");
1581      }
1582    }/*window UDFs*/)
1583
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
1598      });
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]));
1609      };
1610      let rc = capi.sqlite3_exec(
1611        db.pointer, "select a, a*2 from foo.bar", execCallback,
1612        0, 0
1613      );
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
1619      );
1620      T.assert(capi.SQLITE_ABORT === rc);
1621      db.exec("detach foo");
1622      T.mustThrow(()=>db.exec("select * from foo.bar"));
1623    })
1624
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));
1666
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 :/ */));
1671
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')
1680            ];
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.");
1702          }
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);
1707        }
1708      }
1709    }/* jaccwabyt-specific tests */)
1710
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);
1716    })
1717  ;/* end of oo1 checks */
1718
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."
1726        );
1727        log("kvvfs is (correctly) unavailable in a Worker.");
1728        return;
1729      }
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)'
1741        ]);
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();
1750      }
1751    }/*kvvfs sanity checks*/)
1752  ;/* end kvvfs tests */
1753
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)'
1771          ]);
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();
1780        }
1781
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
1790            );
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();
1799          }
1800        }
1801
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");
1814      }
1815    }/*OPFS sanity checks*/)
1816  ;/* end OPFS tests */
1817
1818  ////////////////////////////////////////////////////////////////////////
1819  log("Loading and initializing sqlite3 WASM module...");
1820  if(!isUIThread()){
1821    /*
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.
1830
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.
1834    */
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;
1839    }
1840    importScripts(sqlite3Js);
1841  }
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.");
1856    }
1857    if(haveWasmCTests()){
1858      log("sqlite3_wasm_test_...() APIs are available.");
1859    }else{
1860      logClass('warning',"sqlite3_wasm_test_...() APIs unavailable.");
1861    }
1862    TestUtil.runTests(sqlite3);
1863  });
1864})();
1865