]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Move the rest of testing1.js into tester1.js and eliminate the dependency on jaccwaby...
authorstephan <stephan@noemail.net>
Thu, 13 Oct 2022 16:48:35 +0000 (16:48 +0000)
committerstephan <stephan@noemail.net>
Thu, 13 Oct 2022 16:48:35 +0000 (16:48 +0000)
FossilOrigin-Name: 4e2a8aff2dd4b6e148f45184e2523ebe47815257eca97fa3d32bcbf9625f0def

14 files changed:
ext/wasm/GNUmakefile
ext/wasm/api/sqlite3-api-glue.js
ext/wasm/api/sqlite3-api-oo1.js
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/api/sqlite3-wasm.c
ext/wasm/common/testing.css
ext/wasm/common/whwasmutil.js
ext/wasm/index.html
ext/wasm/jaccwabyt/jaccwabyt_test.c [deleted file]
ext/wasm/jaccwabyt/jaccwabyt_test.exports [deleted file]
ext/wasm/tester1.js
ext/wasm/testing1.js [deleted file]
manifest
manifest.uuid

index f6d2a50c93a9751f109a65bd262f362626d8aa82..cf0e8b108147cf6985249a8315b7da69f11608b1 100644 (file)
@@ -68,14 +68,10 @@ SQLITE_OPT = \
   -DSQLITE_TEMP_STORE=3 \
   -DSQLITE_OS_KV_OPTIONAL=1 \
   '-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \
-  -DSQLITE_USE_URI=1
-#SQLITE_OPT += -DSQLITE_ENABLE_MEMSYS5
-# ^^^ MEMSYS5 is hypothetically useful for non-Emscripten builds but
-# requires adding more infrastructure and fixing one spot in the
-# sqlite3 internals which calls malloc() early on.
-
-# SQLITE_OMIT_LOAD_EXTENSION: if this is true, sqlite3_vfs::xDlOpen
-# and friends may be NULL.
+  -DSQLITE_USE_URI=1 \
+  -DSQLITE_WASM_ENABLE_C_TESTS
+# ^^^ most flags are set in sqlite3-wasm.c but we need them
+# made explicit here for building speedtest1.c.
 
 ifneq (,$(filter release,$(MAKECMDGOALS)))
 emcc_opt ?= -Oz -flto
@@ -128,8 +124,7 @@ else
   $(info Development build. Use '$(MAKE) release' for a smaller release build.)
 endif
 
-EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api \
-    $(dir.jacc)/jaccwabyt_test.exports
+EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api
 
 EXPORTED_FUNCTIONS.api: $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE)
        cat $(EXPORTED_FUNCTIONS.api.in) > $@
@@ -294,12 +289,6 @@ sqlite3-wasm.o := $(dir.api)/sqlite3-wasm.o
 $(sqlite3-wasm.o): emcc.cflags += $(SQLITE_OPT)
 $(sqlite3-wasm.o): $(dir.top)/sqlite3.c
 sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c
-jaccwabyt_test.c := $(dir.jacc)/jaccwabyt_test.c
-# ^^^ FIXME (how?): jaccwabyt_test.c is only needed for the test apps,
-# so we don't really want to include it in release builds. However, we
-# want to test the release builds with those apps, so we cannot simply
-# elide that file in release builds. That component is critical to the
-# VFS bindings so needs to be tested along with the core APIs.
 ########################################################################
 # call-wasm-c-compile sets up build rules
 # for $1.o. $1 must be the name of a C file (with extension).
@@ -310,7 +299,7 @@ $$($(1).o): $$(MAKEFILE) $(1)
        $$(emcc.bin) $$(emcc_opt_full) $$(emcc.flags) $$(emcc.cflags) -c $(1) -o $$@
 CLEAN_FILES += $$($(1).o)
 endef
-$(foreach c,$(sqlite3-wasm.c) $(jaccwabyt_test.c),$(eval $(call call-wasm-c-compile,$(c))))
+$(foreach c,$(sqlite3-wasm.c),$(eval $(call call-wasm-c-compile,$(c))))
 $(eval $(call call-make-pre-js,sqlite3))
 $(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \
     EXPORTED_FUNCTIONS.api \
@@ -401,7 +390,7 @@ speedtest1.wasm := $(subst .js,.wasm,$(speedtest1.js))
 speedtest1.cflags := \
   -I. -I.. -I$(dir.top) \
   -DSQLITE_SPEEDTEST1_WASM
-speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c) $(jaccwabyt_test.c)
+speedtest1.cs := $(speedtest1.c) $(sqlite3-wasm.c)
 $(speedtest1.js): emcc.cflags+=
 # speedtest1 notes re. sqlite3-wasm.o vs sqlite3-wasm.c: building against
 # the latter (predictably) results in a slightly faster binary, but we're
index 6f7301ece64d813b9f86fe292c6aed610ee87e01..fd1f0c3e02efa9556a39e8b032641e007f65a418 100644 (file)
@@ -592,8 +592,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     */
     capi.sqlite3_web_rc_str = (rc)=>__rcMap[rc];
     /* Bind all registered C-side structs... */
+    const notThese = Object.assign(Object.create(null),{
+      // Structs NOT to register
+      WasmTestStruct: true
+    });
     for(const s of wasm.ctype.structs){
-      capi[s.name] = sqlite3.StructBinder(s);
+      if(!notThese[s.name]){
+        capi[s.name] = sqlite3.StructBinder(s);
+      }
     }
   }/*end C constant imports*/
 
index b78c9b68ef00433003f951460644f798b9a33cd4..fa6c6d8e5f0db1173b601392dbd6a7b27a72ed7f 100644 (file)
@@ -806,12 +806,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
        - (optionsObject)
 
        In the final two cases, the function must be defined as the
-       'callback' property of the options object. In the final
+       `callback` property of the options object (optionally called
+       `xFunc` to align with the C API documentation). In the final
        case, the function's name must be the 'name' property.
 
-       This can only be used to create scalar functions, not
-       aggregate or window functions. UDFs cannot be removed from
-       a DB handle after they're added.
+       This can currently only be used to create scalar functions, not
+       aggregate or window functions (requires only a bit of
+       refactoring to support aggregates and window functions).
+
+       UDFs cannot currently be removed from a DB handle after they're
+       added.
 
        On success, returns this object. Throws on error.
 
@@ -848,18 +852,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
        - .deterministic = SQLITE_DETERMINISTIC
        - .directOnly = SQLITE_DIRECTONLY
        - .innocuous = SQLITE_INNOCUOUS
+
+       TODO: for the (optionsObject) form, accept callbacks for
+       aggregate and window functions.
+
     */
-    createFunction: function f(name, callback,opt){
+    createFunction: function f(name, callback, opt){
       switch(arguments.length){
           case 1: /* (optionsObject) */
             opt = name;
             name = opt.name;
-            callback = opt.callback;
+            callback = opt.xFunc || opt.callback;
             break;
           case 2: /* (name, callback|optionsObject) */
             if(!(callback instanceof Function)){
               opt = callback;
-              callback = opt.callback;
+              callback = opt.xFunc || opt.callback;
             }
             break;
           default: break;
index 10d09d7404828f01b6462b355405df12cd3856c8..a267e934545e0701944e857b856d10795c1e1161 100644 (file)
@@ -949,7 +949,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
       return rc;
     },
     /**
-       A convenience wrapper for allocChunks() which sizes each chunks
+       A convenience wrapper for allocChunks() which sizes each chunk
        as either 8 bytes (safePtrSize is truthy) or wasm.ptrSizeof (if
        safePtrSize is falsy).
 
index d57dc0562dcdc3212f20174fd65cc45ca46bcd12..26771d5c9ae4a87e86f7707ccce0103db5a7dbcd 100644 (file)
 */
 
 #define SQLITE_WASM
+#ifdef SQLITE_WASM_ENABLE_C_TESTS
+/*
+** Functions blocked off by SQLITE_WASM_TESTS are intended solely for
+** use in unit/regression testing. They may be safely omitted from
+** client-side builds.
+*/
+#  define SQLITE_WASM_TESTS 1
+#else
+#  define SQLITE_WASM_TESTS 0
+#endif
 
 /*
 ** Threading and file locking: JS is single-threaded. Each Worker
 ** locking, and any similar future filesystems, threading and file
 ** locking support are unnecessary in the wasm build.
 */
+
+/*
+** Undefine any SQLITE_... config flags which we specifically do not
+** want undefined. Please keep these alphabetized.
+*/
 #undef SQLITE_OMIT_DESERIALIZE
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_DEFAULT_... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+/*
+** The OPFS impls benefit tremendously from an increased cache size
+** when working on large workloads, e.g. speedtest1 --size 50 or
+** higher. On smaller workloads, e.g. speedtest1 --size 25, they
+** clearly benefit from having 4mb of cache, but not as much as a
+** larger cache benefits the larger workloads. Speed differences
+** between 2x and nearly 3x have been measured with ample page cache.
+*/
+# define SQLITE_DEFAULT_CACHE_SIZE -16777216
+#endif
+#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE)
+/* TODO: experiment with this. */
+# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/
+#endif
 #ifndef SQLITE_DEFAULT_UNIX_VFS
 # define SQLITE_DEFAULT_UNIX_VFS "unix-none"
 #endif
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+#  define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+#  define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+#  define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+#  define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_FTS4
+#  define SQLITE_ENABLE_FTS4 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+#  define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+#  define SQLITE_ENABLE_RTREE 1
+#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+#  define SQLITE_ENABLE_STMTVTAB 1
+#endif
+#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+#  define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+#endif
+
+/**********************************************************************/
+/* SQLITE_O... */
 #ifndef SQLITE_OMIT_DEPRECATED
-# define SQLITE_OMIT_DEPRECATED
+# define SQLITE_OMIT_DEPRECATED 1
 #endif
 #ifndef SQLITE_OMIT_LOAD_EXTENSION
-# define SQLITE_OMIT_LOAD_EXTENSION
+# define SQLITE_OMIT_LOAD_EXTENSION 1
 #endif
 #ifndef SQLITE_OMIT_SHARED_CACHE
-# define SQLITE_OMIT_SHARED_CACHE
+# define SQLITE_OMIT_SHARED_CACHE 1
 #endif
 #ifndef SQLITE_OMIT_UTF16
-# define SQLITE_OMIT_UTF16
+# define SQLITE_OMIT_UTF16 1
+#endif
+#ifndef SQLITE_OMIT_WAL
+# define SQLITE_OMIT_WAL 1
 #endif
 #ifndef SQLITE_OS_KV_OPTIONAL
 # define SQLITE_OS_KV_OPTIONAL 1
 #endif
+
+/**********************************************************************/
+/* SQLITE_T... */
 #ifndef SQLITE_TEMP_STORE
 # define SQLITE_TEMP_STORE 3
 #endif
 #ifndef SQLITE_THREADSAFE
 # define SQLITE_THREADSAFE 0
 #endif
-#ifndef SQLITE_OMIT_WAL
-# define SQLITE_OMIT_WAL
-#endif
-#ifndef SQLITE_DEFAULT_CACHE_SIZE
-/*
-** The OPFS impls benefit tremendously from an increased cache size
-** when working on large workloads, e.g. speedtest1 --size 50 or
-** higher. On smaller workloads, e.g. speedtest1 --size 25, they
-** clearly benefit from having 4mb of cache, but not as much as a
-** larger cache benefits the larger workloads. Speed differences
-** between 2x and nearly 3x have been measured with ample page cache.
-*/
-# define SQLITE_DEFAULT_CACHE_SIZE -16777216
-#endif
 
-#if 0
-/*
-** TODO: experiment with this when back on the opfs-capable machine.
-*/
-#ifndef SQLITE_DEFAULT_PAGE_SIZE
-# define SQLITE_DEFAULT_PAGE_SIZE 8192 /*4096*/
-#endif
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+#  define SQLITE_USE_URI 1
 #endif
 
 #include <assert.h>
 #endif
 
 /*
-** SQLITE_WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not
-** Emscripten-specific. It explicitly marks functions for export into
-** the target wasm file without requiring explicit listing of those
-** functions in Emscripten's -sEXPORTED_FUNCTIONS=... list (or
-** equivalent in other build platforms). Any function with neither
+** SQLITE_WASM_KEEP is functionally identical to EMSCRIPTEN_KEEPALIVE
+** but is not Emscripten-specific. It explicitly marks functions for
+** export into the target wasm file without requiring explicit listing
+** of those functions in Emscripten's -sEXPORTED_FUNCTIONS=... list
+** (or equivalent in other build platforms). Any function with neither
 ** this attribute nor which is listed as an explicit export will not
 ** be exported from the wasm file (but may still be used internally
 ** within the wasm file).
@@ -256,6 +316,28 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
   return err_code;
 }
 
+#if SQLITE_WASM_TESTS
+struct WasmTestStruct {
+  int v4;
+  void * ppV;
+  const char * cstr;
+  int64_t v8;
+  void (*xFunc)(void*);
+};
+typedef struct WasmTestStruct WasmTestStruct;
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_struct(WasmTestStruct * s){
+  if(s){
+    s->v4 *= 2;
+    s->v8 = s->v4 * 2;
+    s->ppV = s;
+    s->cstr = __FILE__;
+    if(s->xFunc) s->xFunc(s);
+  }
+  return;
+}
+#endif /* SQLITE_WASM_TESTS */
+
 /*
 ** This function is NOT part of the sqlite3 public API. It is strictly
 ** for use by the sqlite project's own JS/WASM bindings. Unlike the
@@ -692,6 +774,19 @@ const char * sqlite3_wasm_enum_json(void){
       M(xDelete,"i(ss)");
       M(nKeySize,"i");
     } _StructBinder;
+#undef CurrentStruct
+
+#if SQLITE_WASM_TESTS
+#define CurrentStruct WasmTestStruct
+    StructBinder {
+      M(v4,"i");
+      M(cstr,"s");
+      M(ppV,"p");
+      M(v8,"j");
+      M(xFunc,"v(p)");
+    } _StructBinder;
+#undef CurrentStruct
+#endif
 
   } out( "]"/*structs*/);
 
@@ -926,4 +1021,56 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){
 }
 #endif /* __EMSCRIPTEN__ && SQLITE_WASM_WASMFS */
 
+#if SQLITE_WASM_TESTS
+
+SQLITE_WASM_KEEP
+int sqlite3_wasm_test_intptr(int * p){
+  return *p = *p * 2;
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_max(void){
+  return (int64_t)0x7fffffffffffffff;
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_min(void){
+  return ~sqlite3_wasm_test_int64_max();
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64_times2(int64_t x){
+  return x * 2;
+}
+
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){
+  *max = sqlite3_wasm_test_int64_max();
+  *min = sqlite3_wasm_test_int64_min();
+  /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
+}
+
+SQLITE_WASM_KEEP
+int64_t sqlite3_wasm_test_int64ptr(int64_t * p){
+  /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
+  return *p = *p * 2;
+}
+
+SQLITE_WASM_KEEP
+void sqlite3_wasm_test_stack_overflow(int recurse){
+  if(recurse) sqlite3_wasm_test_stack_overflow(recurse);
+}
+
+/* For testing the 'string-free' whwasmutil.xWrap() conversion. */
+SQLITE_WASM_KEEP
+char * sqlite3_wasm_test_str_hello(int fail){
+  char * s = fail ? 0 : (char *)malloc(6);
+  if(s){
+    memcpy(s, "hello", 5);
+    s[5] = 0;
+  }
+  return s;
+}
+#endif /* SQLITE_WASM_TESTS */
+
 #undef SQLITE_WASM_KEEP
index c8629a2c62941ac378481e2b462447e2cf9259e8..27f77e45c74a5b859cd3eabdf578df2b54065db8 100644 (file)
@@ -2,7 +2,6 @@ body {
   display: flex;
   flex-direction: column;
   flex-wrap: wrap;
-  white-space: break-spaces;
 }
 textarea {
     font-family: monospace;
index c98b7cea4d2a976f63feab9791c53c88f1825986..e04886de8ab60766f961a85a986a22ae47517c46 100644 (file)
@@ -1255,12 +1255,13 @@ self.WhWasmUtilInstaller = function(target){
      Would that be too much magic concentrated in one place, ready to
      backfire?
   */
-  xcv.arg.string = xcv.arg['pointer'] = xcv.arg['*'] = function(v){
-    if('string'===typeof v) return target.scopedAllocCString(v);
-    return v ? xcv.arg[ptrIR](v) : null;
-  };
-  xcv.result.string = (i)=>target.cstringToJs(i);
-  xcv.result['string:free'] = (i)=>{
+  xcv.arg.string = xcv.arg.utf8 = xcv.arg['pointer'] = xcv.arg['*']
+    = function(v){
+      if('string'===typeof v) return target.scopedAllocCString(v);
+      return v ? xcv.arg[ptrIR](v) : null;
+    };
+  xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i);
+  xcv.result['string:free'] = xcv.result['utf8:free'] = (i)=>{
     try { return i ? target.cstringToJs(i) : null }
     finally{ target.dealloc(i) }
   };
@@ -1374,31 +1375,33 @@ self.WhWasmUtilInstaller = function(target){
 
      Non-numeric conversions include:
 
-     - `string` (args): has two different semantics in order to
-       accommodate various uses of certain C APIs (e.g. output-style
-       strings)...
+     - `string` or `utf8` (args): has two different semantics in order
+       to accommodate various uses of certain C APIs
+       (e.g. output-style strings)...
 
-       - If the arg is a string, it creates a _temporary_ C-string to
-         pass to the exported function, cleaning it up before the
-         wrapper returns. If a long-lived C-string pointer is
-         required, that requires client-side code to create the
-         string, then pass its pointer to the function.
+       - If the arg is a string, it creates a _temporary_
+         UTF-8-encoded C-string to pass to the exported function,
+         cleaning it up before the wrapper returns. If a long-lived
+         C-string pointer is required, that requires client-side code
+         to create the string, then pass its pointer to the function.
 
        - Else the arg is assumed to be a pointer to a string the
          client has already allocated and it's passed on as
          a WASM pointer.
 
-     - `string` (results): treats the result value as a const C-string,
-       copies it to a JS string, and returns that JS string.
-
-     - `string:free` (results): treats the result value as a non-const
-       C-string, ownership of which has just been transfered to the
-       caller. It copies the C-string to a JS string, frees the
-       C-string, and returns the JS string. If such a result value is
-       NULL, the JS result is `null`. Achtung: when using an API which
-       returns results from a specific allocator, e.g. `my_malloc()`,
-       this conversion _is not legal_. Instead, an equivalent conversion
-       which uses the appropriate deallocator is required. For example:  
+       - `string` or `utf8` (results): treats the result value as a
+         const C-string, encoded as UTF-8, copies it to a JS string,
+         and returns that JS string.
+
+     - `string:free` or `utf8:free) (results): treats the result value
+       as a non-const UTF-8 C-string, ownership of which has just been
+       transfered to the caller. It copies the C-string to a JS
+       string, frees the C-string, and returns the JS string. If such
+       a result value is NULL, the JS result is `null`. Achtung: when
+       using an API which returns results from a specific allocator,
+       e.g. `my_malloc()`, this conversion _is not legal_. Instead, an
+       equivalent conversion which uses the appropriate deallocator is
+       required. For example:
 
 ```js
    target.xWrap.resultAdaptor('string:my_free',(i)=>{
index 20668db20e020f3d9f34505e5a9e3d23272f8d5d..01d178bb7a078317dd123353b384a961f2aec7c8 100644 (file)
     </div>
     <div>The tests and demos...
       <ul id='test-list'>
+        <li>Core-most tests
+          <ul>
+            <li><a href='tester1.html'>tester1</a>: Core unit and
+              regression tests for the various APIs and surrounding
+              utility code.</li>
+            <li><a href='tester1.html'>tester1-worker</a>: same thing
+            but running in a Worker.</li>
+          </ul>
+        </li>
         <li>High-level apps and demos...
           <ul>
             <li><a href='fiddle/fiddle.html'>fiddle</a> is an HTML front-end
@@ -68,7 +77,6 @@
         </li>
         <li>The obligatory "misc." category...
           <ul>
-            <li><a href='testing1.html'>testing1</a>: sanity tests of the core APIs and surrounding utility code.</li>
             <li><a href='testing2.html'>testing2</a>: Worker-based test of OO API #1.</li>
             <li><a href='testing-worker1-promiser.html'>testing-worker1-promiser</a>:
               tests for the Promise-based wrapper of the Worker-based API.</li>
diff --git a/ext/wasm/jaccwabyt/jaccwabyt_test.c b/ext/wasm/jaccwabyt/jaccwabyt_test.c
deleted file mode 100644 (file)
index 7e2db39..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-#include <assert.h>
-#include <string.h> /* memset() */
-#include <stddef.h> /* offsetof() */
-#include <stdio.h>  /* snprintf() */
-#include <stdint.h> /* int64_t */
-/*#include <stdlib.h>*/ /* malloc/free(), needed for emscripten exports. */
-extern void * malloc(size_t);
-extern void free(void *);
-
-/*
-**  2022-06-25
-**
-**  The author disclaims copyright to this source code.  In place of a
-**  legal notice, here is a blessing:
-**
-**  *   May you do good and not evil.
-**  *   May you find forgiveness for yourself and forgive others.
-**  *   May you share freely, never taking more than you give.
-**
-***********************************************************************
-**
-** Utility functions for use with the emscripten/WASM bits. These
-** functions ARE NOT part of the sqlite3 public API. They are strictly
-** for internal use by the JS/WASM bindings.
-**
-** This file is intended to be WASM-compiled together with sqlite3.c,
-** e.g.:
-**
-**  emcc ... sqlite3.c wasm_util.c
-*/
-
-/*
-** Experimenting with output parameters.
-*/
-int jaccwabyt_test_intptr(int * p){
-  if(1==((int)p)%3){
-    /* kludge to get emscripten to export malloc() and free() */;
-    free(malloc(0));
-  }
-  return *p = *p * 2;
-}
-int64_t jaccwabyt_test_int64_max(void){
-  return (int64_t)0x7fffffffffffffff;
-}
-int64_t jaccwabyt_test_int64_min(void){
-  return ~jaccwabyt_test_int64_max();
-}
-int64_t jaccwabyt_test_int64_times2(int64_t x){
-  return x * 2;
-}
-
-void jaccwabyt_test_int64_minmax(int64_t * min, int64_t *max){
-  *max = jaccwabyt_test_int64_max();
-  *min = jaccwabyt_test_int64_min();
-  /*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
-}
-int64_t jaccwabyt_test_int64ptr(int64_t * p){
-  /*printf("jaccwabyt_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
-  return *p = *p * 2;
-}
-
-void jaccwabyt_test_stack_overflow(int recurse){
-  if(recurse) jaccwabyt_test_stack_overflow(recurse);
-}
-
-struct WasmTestStruct {
-  int v4;
-  void * ppV;
-  const char * cstr;
-  int64_t v8;
-  void (*xFunc)(void*);
-};
-typedef struct WasmTestStruct WasmTestStruct;
-void jaccwabyt_test_struct(WasmTestStruct * s){
-  if(s){
-    s->v4 *= 2;
-    s->v8 = s->v4 * 2;
-    s->ppV = s;
-    s->cstr = __FILE__;
-    if(s->xFunc) s->xFunc(s);
-  }
-  return;
-}
-
-/** For testing the 'string-free' whwasmutil.xWrap() conversion. */
-char * jaccwabyt_test_str_hello(int fail){
-  char * s = fail ? 0 : (char *)malloc(6);
-  if(s){
-    memcpy(s, "hello", 5);
-    s[5] = 0;
-  }
-  return s;
-}
-
-/*
-** Returns a NUL-terminated string containing a JSON-format metadata
-** regarding C structs, for use with the StructBinder API. The
-** returned memory is static and is only written to the first time
-** this is called.
-*/
-const char * jaccwabyt_test_ctype_json(void){
-  static char strBuf[1024 * 8] = {0};
-  int n = 0, structCount = 0, groupCount = 0;
-  char * pos = &strBuf[1] /* skip first byte for now to help protect
-                             against a small race condition */;
-  char const * const zEnd = pos + sizeof(strBuf);
-  if(strBuf[0]) return strBuf;
-  /* Leave first strBuf[0] at 0 until the end to help guard against a
-     tiny race condition. If this is called twice concurrently, they
-     might end up both writing to strBuf, but they'll both write the
-     same thing, so that's okay. If we set byte 0 up front then the
-     2nd instance might return a partially-populated string. */
-
-  ////////////////////////////////////////////////////////////////////
-  // First we need to build up our macro framework...
-  ////////////////////////////////////////////////////////////////////
-  // Core output macros...
-#define lenCheck assert(pos < zEnd - 100)
-#define outf(format,...) \
-  pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
-  lenCheck
-#define out(TXT) outf("%s",TXT)
-#define CloseBrace(LEVEL) \
-  assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck
-
-  ////////////////////////////////////////////////////////////////////
-  // Macros for emitting StructBinder descriptions...
-#define StructBinder__(TYPE)                 \
-  n = 0;                                     \
-  outf("%s{", (structCount++ ? ", " : ""));  \
-  out("\"name\": \"" # TYPE "\",");         \
-  outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
-  out(",\"members\": {");
-#define StructBinder_(T) StructBinder__(T)
-// ^^^ indirection needed to expand CurrentStruct
-#define StructBinder StructBinder_(CurrentStruct)
-#define _StructBinder CloseBrace(2)
-#define M(MEMBER,SIG)                                         \
-  outf("%s\"%s\": "                                           \
-       "{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
-       (n++ ? ", " : ""), #MEMBER,                            \
-       (int)offsetof(CurrentStruct,MEMBER),                   \
-       (int)sizeof(((CurrentStruct*)0)->MEMBER),              \
-       SIG)
-  // End of macros
-  ////////////////////////////////////////////////////////////////////
-  
-  out("\"structs\": ["); {
-
-#define CurrentStruct WasmTestStruct
-    StructBinder {
-      M(v4,"i");
-      M(cstr,"s");
-      M(ppV,"p");
-      M(v8,"j");
-      M(xFunc,"v(p)");
-    } _StructBinder;
-#undef CurrentStruct
-
-  } out( "]"/*structs*/);
-  out("}"/*top-level object*/);
-  *pos = 0;
-  strBuf[0] = '{'/*end of the race-condition workaround*/;
-  return strBuf;
-#undef DefGroup
-#undef Def
-#undef _DefGroup
-#undef StructBinder
-#undef StructBinder_
-#undef StructBinder__
-#undef M
-#undef _StructBinder
-#undef CurrentStruct
-#undef CloseBrace
-#undef out
-#undef outf
-#undef lenCheck
-}
diff --git a/ext/wasm/jaccwabyt/jaccwabyt_test.exports b/ext/wasm/jaccwabyt/jaccwabyt_test.exports
deleted file mode 100644 (file)
index b618220..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-_jaccwabyt_test_intptr
-_jaccwabyt_test_int64ptr
-_jaccwabyt_test_int64_max
-_jaccwabyt_test_int64_min
-_jaccwabyt_test_int64_minmax
-_jaccwabyt_test_int64_times2
-_jaccwabyt_test_struct
-_jaccwabyt_test_ctype_json
-_jaccwabyt_test_stack_overflow
-_jaccwabyt_test_str_hello
index ae4cc0c94984c4df977162e464ef43c5e71f212a..37a992d61937b8f20edec31d08d89ede1c2e02bd 100644 (file)
@@ -43,8 +43,8 @@
   const isWorker = ()=>!isUIThread();
   /* Predicate for tests/groups. */
   const testIsTodo = ()=>false;
-  const haveJaccwabytTests = function(){
-    return !!wasm.exports.jaccwabyt_test_int64_max;
+  const haveWasmCTests = function(){
+    return !!wasm.exports.sqlite3_wasm_test_int64_max;
   };
   const mapToString = (v)=>{
     switch(typeof v){
   ////////////////////////////////////////////////////////////////////////
   // End of infrastructure setup. Now define the tests...
   ////////////////////////////////////////////////////////////////////////
-  
+
+  ////////////////////////////////////////////////////////////////////
   T.g('Basic sanity checks')
     .t('Namespace object checks', function(sqlite3){
       const wasmCtypes = wasm.ctype;
           .assert(e instanceof sqlite3.WasmAllocError);
       }
     })
+  ////////////////////////////////////////////////////////////////////
     .t('strglob/strlike', function(sqlite3){
       T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
         assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
         assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
         assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
     })
+  ////////////////////////////////////////////////////////////////////
+  ;/*end of basic sanity checks*/
+
+  ////////////////////////////////////////////////////////////////////
+  T.g('C/WASM Utilities')
+    .t('sqlite3.capi.wasm', function(sqlite3){
+      const w = wasm;
+      const chr = (x)=>x.charCodeAt(0);
+      //log("heap getters...");
+      {
+        const li = [8, 16, 32];
+        if(w.bigIntEnabled) li.push(64);
+        for(const n of li){
+          const bpe = n/8;
+          const s = w.heapForSize(n,false);
+          T.assert(bpe===s.BYTES_PER_ELEMENT).
+            assert(w.heapForSize(s.constructor) === s);
+          const u = w.heapForSize(n,true);
+          T.assert(bpe===u.BYTES_PER_ELEMENT).
+            assert(s!==u).
+            assert(w.heapForSize(u.constructor) === u);
+        }
+      }
+
+      //log("jstrlen()...");
+      {
+        T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
+      }
+
+      //log("jstrcpy()...");
+      {
+        const fillChar = 10;
+        let ua = new Uint8Array(8), rc,
+            refill = ()=>ua.fill(fillChar);
+        refill();
+        rc = w.jstrcpy("hello", ua);
+        T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
+        refill();
+        ua[5] = chr('!');
+        rc = w.jstrcpy("HELLO", ua, 0, -1, false);
+        T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
+        refill();
+        rc = w.jstrcpy("the end", ua, 4);
+        //log("rc,ua",rc,ua);
+        T.assert(4===rc).assert(0===ua[7]).
+          assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
+        refill();
+        rc = w.jstrcpy("the end", ua, 4, -1, false);
+        T.assert(4===rc).assert(chr(' ')===ua[7]).
+          assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
+        refill();
+        rc = w.jstrcpy("", ua, 0, 1, true);
+        //log("rc,ua",rc,ua);
+        T.assert(1===rc).assert(0===ua[0]);
+        refill();
+        rc = w.jstrcpy("x", ua, 0, 1, true);
+        //log("rc,ua",rc,ua);
+        T.assert(1===rc).assert(0===ua[0]);
+        refill();
+        rc = w.jstrcpy('äbä', ua, 0, 1, true);
+        T.assert(1===rc, 'Must not write partial multi-byte char.')
+          .assert(0===ua[0]);
+        refill();
+        rc = w.jstrcpy('äbä', ua, 0, 2, true);
+        T.assert(1===rc, 'Must not write partial multi-byte char.')
+          .assert(0===ua[0]);
+        refill();
+        rc = w.jstrcpy('äbä', ua, 0, 2, false);
+        T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
+      }/*jstrcpy()*/
+
+      //log("cstrncpy()...");
+      {
+        const scope = w.scopedAllocPush();
+        try {
+          let cStr = w.scopedAllocCString("hello");
+          const n = w.cstrlen(cStr);
+          let cpy = w.scopedAlloc(n+10);
+          let rc = w.cstrncpy(cpy, cStr, n+10);
+          T.assert(n+1 === rc).
+            assert("hello" === w.cstringToJs(cpy)).
+            assert(chr('o') === w.getMemValue(cpy+n-1)).
+            assert(0 === w.getMemValue(cpy+n));
+          let cStr2 = w.scopedAllocCString("HI!!!");
+          rc = w.cstrncpy(cpy, cStr2, 3);
+          T.assert(3===rc).
+            assert("HI!lo" === w.cstringToJs(cpy)).
+            assert(chr('!') === w.getMemValue(cpy+2)).
+            assert(chr('l') === w.getMemValue(cpy+3));
+        }finally{
+          w.scopedAllocPop(scope);
+        }
+      }
+
+      //log("jstrToUintArray()...");
+      {
+        let a = w.jstrToUintArray("hello", false);
+        T.assert(5===a.byteLength).assert(chr('o')===a[4]);
+        a = w.jstrToUintArray("hello", true);
+        T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
+        a = w.jstrToUintArray("äbä", false);
+        T.assert(5===a.byteLength).assert(chr('b')===a[2]);
+        a = w.jstrToUintArray("äbä", true);
+        T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
+      }
+
+      //log("allocCString()...");
+      {
+        const cstr = w.allocCString("hällo, world");
+        const n = w.cstrlen(cstr);
+        T.assert(13 === n)
+          .assert(0===w.getMemValue(cstr+n))
+          .assert(chr('d')===w.getMemValue(cstr+n-1));
+      }
+
+      //log("scopedAlloc() and friends...");
+      {
+        const alloc = w.alloc, dealloc = w.dealloc;
+        w.alloc = w.dealloc = null;
+        T.assert(!w.scopedAlloc.level)
+          .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
+          .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
+        w.alloc = alloc;
+        T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
+        w.dealloc = dealloc;
+        T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
+          .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
+          .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
+        const asc = w.scopedAllocPush();
+        let asc2;
+        try {
+          const p1 = w.scopedAlloc(16),
+                p2 = w.scopedAlloc(16);
+          T.assert(1===w.scopedAlloc.level)
+            .assert(Number.isFinite(p1))
+            .assert(Number.isFinite(p2))
+            .assert(asc[0] === p1)
+            .assert(asc[1]===p2);
+          asc2 = w.scopedAllocPush();
+          const p3 = w.scopedAlloc(16);
+          T.assert(2===w.scopedAlloc.level)
+            .assert(Number.isFinite(p3))
+            .assert(2===asc.length)
+            .assert(p3===asc2[0]);
+
+          const [z1, z2, z3] = w.scopedAllocPtr(3);
+          T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
+            .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
+            .assert(0===w.getMemValue(z3,'i32'));
+        }finally{
+          // Pop them in "incorrect" order to make sure they behave:
+          w.scopedAllocPop(asc);
+          T.assert(0===asc.length);
+          T.mustThrowMatching(()=>w.scopedAllocPop(asc),
+                              /^Invalid state object/);
+          if(asc2){
+            T.assert(2===asc2.length,'Should be p3 and z1');
+            w.scopedAllocPop(asc2);
+            T.assert(0===asc2.length);
+            T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
+                                /^Invalid state object/);
+          }
+        }
+        T.assert(0===w.scopedAlloc.level);
+        w.scopedAllocCall(function(){
+          T.assert(1===w.scopedAlloc.level);
+          const [cstr, n] = w.scopedAllocCString("hello, world", true);
+          T.assert(12 === n)
+            .assert(0===w.getMemValue(cstr+n))
+            .assert(chr('d')===w.getMemValue(cstr+n-1));
+        });
+      }/*scopedAlloc()*/
+
+      //log("xCall()...");
+      {
+        const pJson = w.xCall('sqlite3_wasm_enum_json');
+        T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
+      }
+
+      //log("xWrap()...");
+      {
+        T.mustThrowMatching(()=>w.xWrap('sqlite3_libversion',null,'i32'),
+                            /requires 0 arg/).
+          assert(w.xWrap.resultAdapter('i32') instanceof Function).
+          assert(w.xWrap.argAdapter('i32') instanceof Function);
+        let fw = w.xWrap('sqlite3_libversion','utf8');
+        T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
+        let rc = fw();
+        T.assert('string'===typeof rc).assert(rc.length>5);
+        rc = w.xCallWrapped('sqlite3_wasm_enum_json','*');
+        T.assert(rc>0 && Number.isFinite(rc));
+        rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
+        T.assert('string'===typeof rc).assert(rc.length>300);
+        if(haveWasmCTests()){
+          fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:free',['i32']);
+          rc = fw(0);
+          T.assert('hello'===rc);
+          rc = fw(1);
+          T.assert(null===rc);
+
+          if(w.bigIntEnabled){
+            w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
+            w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
+            fw = w.xWrap('sqlite3_wasm_test_int64_times2','thrice','twice');
+            rc = fw(1);
+            T.assert(12n===rc);
+
+            w.scopedAllocCall(function(){
+              let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
+              w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
+              let f = w.xWrap('sqlite3_wasm_test_int64_minmax',undefined,['i64*','i64*']);
+              let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
+              T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
+            });
+          }
+        }
+      }
+    }/*WhWasmUtil*/)
+
+  ////////////////////////////////////////////////////////////////////
+    .t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){
+      const S = sqlite3, W = S.capi.wasm;
+      const MyStructDef = {
+        sizeof: 16,
+        members: {
+          p4: {offset: 0, sizeof: 4, signature: "i"},
+          pP: {offset: 4, sizeof: 4, signature: "P"},
+          ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
+          cstr: {offset: 12, sizeof: 4, signature: "s"}
+        }
+      };
+      if(W.bigIntEnabled){
+        const m = MyStructDef;
+        m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
+        m.sizeof += m.members.p8.sizeof;
+      }
+      const StructType = S.StructBinder.StructType;
+      const K = S.StructBinder('my_struct',MyStructDef);
+      T.mustThrowMatching(()=>K(), /via 'new'/).
+        mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
+      const k1 = new K(), k2 = new K();
+      try {
+        T.assert(k1.constructor === K).
+          assert(K.isA(k1)).
+          assert(k1 instanceof K).
+          assert(K.prototype.lookupMember('p4').key === '$p4').
+          assert(K.prototype.lookupMember('$p4').name === 'p4').
+          mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
+          assert(undefined === K.prototype.lookupMember('nope',false)).
+          assert(k1 instanceof StructType).
+          assert(StructType.isA(k1)).
+          assert(K.resolveToInstance(k1.pointer)===k1).
+          mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
+          assert(k1 === StructType.instanceForPointer(k1.pointer)).
+          mustThrowMatching(()=>k1.$ro = 1, /read-only/);
+        Object.keys(MyStructDef.members).forEach(function(key){
+          key = K.memberKey(key);
+          T.assert(0 == k1[key],
+                   "Expecting allocation to zero the memory "+
+                   "for "+key+" but got: "+k1[key]+
+                   " from "+k1.memoryDump());
+        });
+        T.assert('number' === typeof k1.pointer).
+          mustThrowMatching(()=>k1.pointer = 1, /pointer/).
+          assert(K.instanceForPointer(k1.pointer) === k1);
+        k1.$p4 = 1; k1.$pP = 2;
+        T.assert(1 === k1.$p4).assert(2 === k1.$pP);
+        if(MyStructDef.members.$p8){
+          k1.$p8 = 1/*must not throw despite not being a BigInt*/;
+          k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
+          T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
+        }
+        T.assert(!k1.ondispose);
+        k1.setMemberCString('cstr', "A C-string.");
+        T.assert(Array.isArray(k1.ondispose)).
+          assert(k1.ondispose[0] === k1.$cstr).
+          assert('number' === typeof k1.$cstr).
+          assert('A C-string.' === k1.memberToJsString('cstr'));
+        k1.$pP = k2;
+        T.assert(k1.$pP === k2);
+        k1.$pP = null/*null is special-cased to 0.*/;
+        T.assert(0===k1.$pP);
+        let ptr = k1.pointer;
+        k1.dispose();
+        T.assert(undefined === k1.pointer).
+          assert(undefined === K.instanceForPointer(ptr)).
+          mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
+        const k3 = new K();
+        ptr = k3.pointer;
+        T.assert(k3 === K.instanceForPointer(ptr));
+        K.disposeAll();
+        T.assert(ptr).
+          assert(undefined === k2.pointer).
+          assert(undefined === k3.pointer).
+          assert(undefined === K.instanceForPointer(ptr));
+      }finally{
+        k1.dispose();
+        k2.dispose();
+      }
+
+      if(!W.bigIntEnabled){
+        log("Skipping WasmTestStruct tests: BigInt not enabled.");
+        return;
+      }
+
+      const WTStructDesc =
+            W.ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
+      const autoResolvePtr = true /* EXPERIMENTAL */;
+      if(autoResolvePtr){
+        WTStructDesc.members.ppV.signature = 'P';
+      }
+      const WTStruct = S.StructBinder(WTStructDesc);
+      //log(WTStruct.structName, WTStruct.structInfo);
+      const wts = new WTStruct();
+      //log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
+      try{
+        T.assert(wts.constructor === WTStruct).
+          assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
+          assert(wts.memberKeys().indexOf('$v8')>=0).
+          assert(!K.isA(wts)).
+          assert(WTStruct.isA(wts)).
+          assert(wts instanceof WTStruct).
+          assert(wts instanceof StructType).
+          assert(StructType.isA(wts)).
+          assert(wts === StructType.instanceForPointer(wts.pointer));
+        T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
+          assert(0===wts.$ppV).assert(0===wts.$xFunc).
+          assert(WTStruct.instanceForPointer(wts.pointer) === wts);
+        const testFunc =
+              W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
+        let counter = 0;
+        //log("wts.pointer =",wts.pointer);
+        const wtsFunc = function(arg){
+          /*log("This from a JS function called from C, "+
+              "which itself was called from JS. arg =",arg);*/
+          ++counter;
+          T.assert(WTStruct.instanceForPointer(arg) === wts);
+          if(3===counter){
+            tossQuietly("Testing exception propagation.");
+          }
+        }
+        wts.$v4 = 10; wts.$v8 = 20;
+        wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
+        T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
+          .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
+          .assert(0 === wts.$cstr)
+          .assert(wts.memberIsString('$cstr'))
+          .assert(!wts.memberIsString('$v4'))
+          .assert(null === wts.memberToJsString('$cstr'))
+          .assert(W.functionEntry(wts.$xFunc) instanceof Function);
+        /* It might seem silly to assert that the values match
+           what we just set, but recall that all of those property
+           reads and writes are, via property interceptors,
+           actually marshaling their data to/from a raw memory
+           buffer, so merely reading them back is actually part of
+           testing the struct-wrapping API. */
+
+        testFunc(wts.pointer);
+        //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
+        T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
+          .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer))
+          .assert('string' === typeof wts.memberToJsString('cstr'))
+          .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
+          .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
+                             /Invalid member type signature for C-string/)
+        ;
+        testFunc(wts.pointer);
+        T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
+          .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer));
+        /** The 3rd call to wtsFunc throw from JS, which is called
+            from C, which is called from JS. Let's ensure that
+            that exception propagates back here... */
+        T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
+        W.uninstallFunction(wts.$xFunc);
+        wts.$xFunc = 0;
+        if(autoResolvePtr){
+          wts.$ppV = 0;
+          T.assert(!wts.$ppV);
+          WTStruct.debugFlags(0x03);
+          wts.$ppV = wts;
+          T.assert(wts === wts.$ppV)
+          WTStruct.debugFlags(0);
+        }
+        wts.setMemberCString('cstr', "A C-string.");
+        T.assert(Array.isArray(wts.ondispose)).
+          assert(wts.ondispose[0] === wts.$cstr).
+          assert('A C-string.' === wts.memberToJsString('cstr'));
+        const ptr = wts.pointer;
+        wts.dispose();
+        T.assert(ptr).assert(undefined === wts.pointer).
+          assert(undefined === WTStruct.instanceForPointer(ptr))
+      }finally{
+        wts.dispose();
+      }
+    }/*StructBinder*/)
+
+  ////////////////////////////////////////////////////////////////////
+    .t('sqlite3.StructBinder part 2', function(sqlite3){
+      // https://www.sqlite.org/c3ref/vfs.html
+      // https://www.sqlite.org/c3ref/io_methods.html
+      const W = wasm;
+      const sqlite3_io_methods = capi.sqlite3_io_methods,
+            sqlite3_vfs = capi.sqlite3_vfs,
+            sqlite3_file = capi.sqlite3_file;
+      //log("struct sqlite3_file", sqlite3_file.memberKeys());
+      //log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
+      //log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
+      const installMethod = function callee(tgt, name, func){
+        if(1===arguments.length){
+          return (n,f)=>callee(tgt,n,f);
+        }
+        if(!callee.argcProxy){
+          callee.argcProxy = function(func,sig){
+            return function(...args){
+              if(func.length!==arguments.length){
+                toss("Argument mismatch. Native signature is:",sig);
+              }
+              return func.apply(this, args);
+            }
+          };
+          callee.ondisposeRemoveFunc = function(){
+            if(this.__ondispose){
+              const who = this;
+              this.__ondispose.forEach(
+                (v)=>{
+                  if('number'===typeof v){
+                    try{capi.wasm.uninstallFunction(v)}
+                    catch(e){/*ignore*/}
+                  }else{/*wasm function wrapper property*/
+                    delete who[v];
+                  }
+                }
+              );
+              delete this.__ondispose;
+            }
+          };
+        }/*static init*/
+        const sigN = tgt.memberSignature(name),
+              memKey = tgt.memberKey(name);
+        //log("installMethod",tgt, name, sigN);
+        if(!tgt.__ondispose){
+          T.assert(undefined === tgt.ondispose);
+          tgt.ondispose = [callee.ondisposeRemoveFunc];
+          tgt.__ondispose = [];
+        }
+        const fProxy = callee.argcProxy(func, sigN);
+        const pFunc = capi.wasm.installFunction(fProxy, tgt.memberSignature(name, true));
+        tgt[memKey] = pFunc;
+        /**
+           ACHTUNG: function pointer IDs are from a different pool than
+           allocation IDs, starting at 1 and incrementing in steps of 1,
+           so if we set tgt[memKey] to those values, we'd very likely
+           later misinterpret them as plain old pointer addresses unless
+           unless we use some silly heuristic like "all values <5k are
+           presumably function pointers," or actually perform a function
+           lookup on every pointer to first see if it's a function. That
+           would likely work just fine, but would be kludgy.
+
+           It turns out that "all values less than X are functions" is
+           essentially how it works in wasm: a function pointer is
+           reported to the client as its index into the
+           __indirect_function_table.
 
+           So... once jaccwabyt can be told how to access the
+           function table, it could consider all pointer values less
+           than that table's size to be functions.  As "real" pointer
+           values start much, much higher than the function table size,
+           that would likely work reasonably well. e.g. the object
+           pointer address for sqlite3's default VFS is (in this local
+           setup) 65104, whereas the function table has fewer than 600
+           entries.
+        */
+        const wrapperKey = '$'+memKey;
+        tgt[wrapperKey] = fProxy;
+        tgt.__ondispose.push(pFunc, wrapperKey);
+        //log("tgt.__ondispose =",tgt.__ondispose);
+        return (n,f)=>callee(tgt, n, f);
+      }/*installMethod*/;
+
+      const installIOMethods = function instm(iom){
+        (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
+        if(!instm._requireFileArg){
+          instm._requireFileArg = function(arg,methodName){
+            arg = capi.sqlite3_file.resolveToInstance(arg);
+            if(!arg){
+              err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
+            }
+            return arg;
+          };
+          instm._methods = {
+            // https://sqlite.org/c3ref/io_methods.html
+            xClose: /*i(P)*/function(f){
+              /* int (*xClose)(sqlite3_file*) */
+              log("xClose(",f,")");
+              if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
+              f.dispose(/*noting that f has externally-owned memory*/);
+              return 0;
+            },
+            xRead: /*i(Ppij)*/function(f,dest,n,offset){
+              /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
+              log("xRead(",arguments,")");
+              if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
+              capi.wasm.heap8().fill(0, dest + offset, n);
+              return 0;
+            },
+            xWrite: /*i(Ppij)*/function(f,dest,n,offset){
+              /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
+              log("xWrite(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
+              return 0;
+            },
+            xTruncate: /*i(Pj)*/function(f){
+              /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
+              log("xTruncate(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
+              return 0;
+            },
+            xSync: /*i(Pi)*/function(f){
+              /* int (*xSync)(sqlite3_file*, int flags) */
+              log("xSync(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
+              return 0;
+            },
+            xFileSize: /*i(Pp)*/function(f,pSz){
+              /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
+              log("xFileSize(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
+              capi.wasm.setMemValue(pSz, 0/*file size*/);
+              return 0;
+            },
+            xLock: /*i(Pi)*/function(f){
+              /* int (*xLock)(sqlite3_file*, int) */
+              log("xLock(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
+              return 0;
+            },
+            xUnlock: /*i(Pi)*/function(f){
+              /* int (*xUnlock)(sqlite3_file*, int) */
+              log("xUnlock(",arguments,")");
+              if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
+              return 0;
+            },
+            xCheckReservedLock: /*i(Pp)*/function(){
+              /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
+              log("xCheckReservedLock(",arguments,")");
+              return 0;
+            },
+            xFileControl: /*i(Pip)*/function(){
+              /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
+              log("xFileControl(",arguments,")");
+              return capi.SQLITE_NOTFOUND;
+            },
+            xSectorSize: /*i(P)*/function(){
+              /* int (*xSectorSize)(sqlite3_file*) */
+              log("xSectorSize(",arguments,")");
+              return 0/*???*/;
+            },
+            xDeviceCharacteristics:/*i(P)*/function(){
+              /* int (*xDeviceCharacteristics)(sqlite3_file*) */
+              log("xDeviceCharacteristics(",arguments,")");
+              return 0;
+            }
+          };
+        }/*static init*/
+        iom.$iVersion = 1;
+        Object.keys(instm._methods).forEach(
+          (k)=>installMethod(iom, k, instm._methods[k])
+        );
+      }/*installIOMethods()*/;
+
+      const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
+      const err = console.error.bind(console);
+      try {
+        const IOM = sqlite3_io_methods, S3F = sqlite3_file;
+        //log("iom proto",iom,iom.constructor.prototype);
+        //log("sfile",sfile,sfile.constructor.prototype);
+        T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
+        //log("iom",iom);
+        sfile.$pMethods = iom.pointer;
+        T.assert(iom.pointer === sfile.$pMethods)
+          .assert(IOM.resolveToInstance(iom))
+          .assert(undefined ===IOM.resolveToInstance(sfile))
+          .mustThrow(()=>IOM.resolveToInstance(0,true))
+          .assert(S3F.resolveToInstance(sfile.pointer))
+          .assert(undefined===S3F.resolveToInstance(iom))
+          .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
+        T.assert(0===iom.$iVersion);
+        installIOMethods(iom);
+        T.assert(1===iom.$iVersion);
+        //log("iom.__ondispose",iom.__ondispose);
+        T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
+      }finally{
+        iom.dispose();
+        T.assert(undefined === iom.__ondispose);
+      }
+
+      const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
+      try {
+        const SB = sqlite3.StructBinder;
+        T.assert(dVfs instanceof SB.StructType)
+          .assert(dVfs.pointer)
+          .assert('sqlite3_vfs' === dVfs.structName)
+          .assert(!!dVfs.structInfo)
+          .assert(SB.StructType.hasExternalPointer(dVfs))
+          .assert(dVfs.$iVersion>0)
+          .assert('number'===typeof dVfs.$zName)
+          .assert('number'===typeof dVfs.$xSleep)
+          .assert(capi.wasm.functionEntry(dVfs.$xOpen))
+          .assert(dVfs.memberIsString('zName'))
+          .assert(dVfs.memberIsString('$zName'))
+          .assert(!dVfs.memberIsString('pAppData'))
+          .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
+                             /Invalid member type signature for C-string/)
+          .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
+          .assert('string' === typeof dVfs.memberToJsString('zName'))
+          .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
+        ;
+        //log("Default VFS: @",dVfs.pointer);
+        Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
+          const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
+                addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
+          if(1===mbr.signature.length){
+            let sep = '?', val = undefined;
+            switch(mbr.signature[0]){
+                  // TODO: move this into an accessor, e.g. getPreferredValue(member)
+                case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
+                case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
+                case 's': sep = '=';
+                  val = dVfs.memberToJsString(mname);
+                  break;
+            }
+            //log(prefix, sep, val);
+          }else{
+            //log(prefix," = funcptr @",addr, capi.wasm.functionEntry(addr));
+          }
+        });
+      }finally{
+        dVfs.dispose();
+        T.assert(undefined===dVfs.pointer);
+      }
+    }/*StructBinder part 2*/)
+  
+  ////////////////////////////////////////////////////////////////////
     .t('sqlite3.capi.wasm.pstack', function(sqlite3){
       const w = sqlite3.capi.wasm, P = w.pstack;
       const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
         P.restore(stack);
       }
     }/*pstack tests*/)
-  ;/*end of basic sanity checks*/
+
+  ////////////////////////////////////////////////////////////////////
+  ;/*end of C/WASM utils checks*/
 
   ////////////////////////////////////////////////////////////////////////
   T.g('sqlite3.oo1 sanity checks')
       T.assert(capi.SQLITE_MISUSE === rc)
         .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"));
     })
+
+  ////////////////////////////////////////////////////////////////////
     .t('DB.Stmt sanity checks', function(S){
       let pId;
       let st = this.db.prepare(
       T.assert(!st.pointer)
         .assert(0===this.db.openStatementCount());
     })
+
+  ////////////////////////////////////////////////////////////////////
     .t('Table t', function(sqlite3){
       const db = this.db;
       let list = [];
                db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
         assert(Number.MAX_SAFE_INTEGER ===
                db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
-      if(capi.wasm.bigIntEnabled && haveJaccwabytTests()){
-        const mI = capi.wasm.xCall('jaccwabyt_test_int64_max');
+      if(capi.wasm.bigIntEnabled && haveWasmCTests()){
+        const mI = capi.wasm.xCall('sqlite3_wasm_test_int64_max');
         const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
         T.assert(b === db.selectValue("SELECT "+b)).
           assert(b === db.selectValue("SELECT ?", b)).
       }
     })
 
+  ////////////////////////////////////////////////////////////////////
     .t('Scalar UDFs', function(sqlite3){
       const db = this.db;
       db.createFunction("foo",(pCx,a,b)=>a+b);
       T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
     })
 
+  ////////////////////////////////////////////////////////////////////
     .t({
       name: 'Aggregate UDFs (tests are TODO)',
       predicate: testIsTodo
     })
 
+  ////////////////////////////////////////////////////////////////////
     .t({
       name: 'Window UDFs (tests are TODO)',
       predicate: testIsTodo
     })
 
+  ////////////////////////////////////////////////////////////////////
     .t("ATTACH", function(){
       const db = this.db;
       const resultRows = [];
       T.mustThrow(()=>db.exec("select * from foo.bar"));
     })
 
+  ////////////////////////////////////////////////////////////////////
     .t({
-      name: 'Jaccwabyt-specific int-pointer tests (if compiled in)',
-      predicate: haveJaccwabytTests,
+      name: 'C-side WASM tests (if compiled in)',
+      predicate: haveWasmCTests,
       test: function(){
         const w = wasm, db = this.db;
         const stack = w.scopedAllocPush();
         try{
           ptrInt = w.scopedAlloc(4);
           w.setMemValue(ptrInt,origValue, ptrValType);
-          const cf = w.xGet('jaccwabyt_test_intptr');
+          const cf = w.xGet('sqlite3_wasm_test_intptr');
           const oldPtrInt = ptrInt;
           //log('ptrInt',ptrInt);
           //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
             //log("getMemValue(pi64)",v64());
             T.assert(v64() == o64);
             //T.assert(o64 === w.getMemValue(pi64, ptrType64));
-            const cf64w = w.xGet('jaccwabyt_test_int64ptr');
+            const cf64w = w.xGet('sqlite3_wasm_test_int64ptr');
             cf64w(pi64);
             //log("getMemValue(pi64)",v64());
             T.assert(v64() == BigInt(2 * o64));
             cf64w(pi64);
             T.assert(v64() == BigInt(4 * o64));
 
-            const biTimes2 = w.xGet('jaccwabyt_test_int64_times2');
+            const biTimes2 = w.xGet('sqlite3_wasm_test_int64_times2');
             T.assert(BigInt(2 * o64) ===
                      biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
                                            in the call :/ */));
             w.setMemValue(pMin, 0, ptrType64);
             w.setMemValue(pMax, 0, ptrType64);
             const minMaxI64 = [
-              w.xCall('jaccwabyt_test_int64_min'),
-              w.xCall('jaccwabyt_test_int64_max')
+              w.xCall('sqlite3_wasm_test_int64_min'),
+              w.xCall('sqlite3_wasm_test_int64_max')
             ];
             T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
               assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
             //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
-            w.xCall('jaccwabyt_test_int64_minmax', pMin, pMax);
+            w.xCall('sqlite3_wasm_test_int64_minmax', pMin, pMax);
             T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
               assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
             //log("pMin",g64(pMin), "pMax",g64(pMax));
     log("sqlite3 version:",capi.sqlite3_libversion(),
         capi.sqlite3_sourceid());
     log("BigInt/int64 support is",(wasm.bigIntEnabled ? "enabled" : "disabled"));
-    if(haveJaccwabytTests()){
-      log("Jaccwabyt test C code found. Jaccwabyt-specific low-level tests.");
+    if(haveWasmCTests()){
+      log("sqlite3_wasm_test_...() APIs are available.");
     }
     TestUtil.runTests(sqlite3);
   });
diff --git a/ext/wasm/testing1.js b/ext/wasm/testing1.js
deleted file mode 100644 (file)
index 6bda587..0000000
+++ /dev/null
@@ -1,1169 +0,0 @@
-/*
-  2022-05-22
-
-  The author disclaims copyright to this source code.  In place of a
-  legal notice, here is a blessing:
-
-  *   May you do good and not evil.
-  *   May you find forgiveness for yourself and forgive others.
-  *   May you share freely, never taking more than you give.
-
-  ***********************************************************************
-
-  A basic test script for sqlite3-api.js. This file must be run in
-  main JS thread and sqlite3.js must have been loaded before it.
-*/
-'use strict';
-(function(){
-  const T = self.SqliteTestUtil;
-  const toss = function(...args){throw new Error(args.join(' '))};
-  const debug = console.debug.bind(console);
-  const eOutput = document.querySelector('#test-output');
-  const log = console.log.bind(console),
-        warn = console.warn.bind(console);
-  const logHtml = function(...args){
-    log.apply(this, args);
-    const ln = document.createElement('div');
-    ln.append(document.createTextNode(args.join(' ')));
-    eOutput.append(ln);
-  };
-
-  const eqApprox = function(v1,v2,factor=0.05){
-    //debug('eqApprox',v1, v2);
-    return v1>=(v2-factor) && v1<=(v2+factor);
-  };
-
-  let sqlite3 /* loaded later */;
-  
-  const testBasicSanity = function(db,sqlite3){
-    const capi = sqlite3.capi;
-    log("Basic sanity tests...");
-    T.assert(Number.isInteger(db.pointer)).
-      mustThrowMatching(()=>db.pointer=1, /read-only/).
-      assert(0===capi.sqlite3_extended_result_codes(db.pointer,1)).
-      assert('main'===db.dbName(0));
-    let pId;
-    let st = db.prepare(
-      new TextEncoder('utf-8').encode("select 3 as a")
-      /* Testing handling of Uint8Array input */
-    );
-    //debug("statement =",st);
-    try {
-      T.assert(Number.isInteger(st.pointer))
-        .mustThrowMatching(()=>st.pointer=1, /read-only/)
-        .assert(1===db.openStatementCount())
-        .assert(!st._mayGet)
-        .assert('a' === st.getColumnName(0))
-        .assert(1===st.columnCount)
-        .assert(0===st.parameterCount)
-        .mustThrow(()=>st.bind(1,null))
-        .assert(true===st.step())
-        .assert(3 === st.get(0))
-        .mustThrow(()=>st.get(1))
-        .mustThrow(()=>st.get(0,~capi.SQLITE_INTEGER))
-        .assert(3 === st.get(0,capi.SQLITE_INTEGER))
-        .assert(3 === st.getInt(0))
-        .assert('3' === st.get(0,capi.SQLITE_TEXT))
-        .assert('3' === st.getString(0))
-        .assert(3.0 === st.get(0,capi.SQLITE_FLOAT))
-        .assert(3.0 === st.getFloat(0))
-        .assert(3 === st.get({}).a)
-        .assert(3 === st.get([])[0])
-        .assert(3 === st.getJSON(0))
-        .assert(st.get(0,capi.SQLITE_BLOB) instanceof Uint8Array)
-        .assert(1===st.get(0,capi.SQLITE_BLOB).length)
-        .assert(st.getBlob(0) instanceof Uint8Array)
-        .assert('3'.charCodeAt(0) === st.getBlob(0)[0])
-        .assert(st._mayGet)
-        .assert(false===st.step())
-        .assert(!st._mayGet)
-      ;
-      pId = st.pointer;
-      T.assert(0===capi.sqlite3_strglob("*.txt", "foo.txt")).
-        assert(0!==capi.sqlite3_strglob("*.txt", "foo.xtx")).
-        assert(0===capi.sqlite3_strlike("%.txt", "foo.txt", 0)).
-        assert(0!==capi.sqlite3_strlike("%.txt", "foo.xtx", 0));
-    }finally{
-      st.finalize();
-    }
-    T.assert(!st.pointer)
-      .assert(0===db.openStatementCount());
-    let list = [];
-    db.exec({
-      sql:['CREATE TABLE t(a,b);',
-           "INSERT INTO t(a,b) VALUES(1,2),(3,4),",
-           "(?,?),('blob',X'6869')"/*intentionally missing semicolon to test for
-                                     off-by-one bug in string-to-WASM conversion*/],
-      saveSql: list,
-      bind: [5,6]
-    });
-    //debug("Exec'd SQL:", list);
-    T.assert(2 === list.length)
-      .assert('string'===typeof list[1])
-      .assert(4===db.changes());
-    if(capi.wasm.bigIntEnabled){
-      T.assert(4n===db.changes(false,true));
-    }
-    let blob = db.selectValue("select b from t where a='blob'");
-    T.assert(blob instanceof Uint8Array).
-      assert(0x68===blob[0] && 0x69===blob[1]);
-    blob = null;
-
-    let counter = 0, colNames = [];
-    list.length = 0;
-    db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
-      rowMode: 'object',
-      resultRows: list,
-      columnNames: colNames,
-      callback: function(row,stmt){
-        ++counter;
-        T.assert((row.a%2 && row.a<6) || 'blob'===row.a);
-      }
-    });
-    T.assert(2 === colNames.length)
-      .assert('a' === colNames[0])
-      .assert(4 === counter)
-      .assert(4 === list.length);
-    list.length = 0;
-    db.exec("SELECT a a, b b FROM t",{
-      rowMode: 'array',
-      callback: function(row,stmt){
-        ++counter;
-        T.assert(Array.isArray(row))
-          .assert((0===row[1]%2 && row[1]<7)
-                  || (row[1] instanceof Uint8Array));
-      }
-    });
-    T.assert(8 === counter);
-    T.assert(Number.MIN_SAFE_INTEGER ===
-             db.selectValue("SELECT "+Number.MIN_SAFE_INTEGER)).
-      assert(Number.MAX_SAFE_INTEGER ===
-             db.selectValue("SELECT "+Number.MAX_SAFE_INTEGER));
-    if(capi.wasm.bigIntEnabled){
-      const mI = capi.wasm.xCall('jaccwabyt_test_int64_max');
-      const b = BigInt(Number.MAX_SAFE_INTEGER * 2);
-      T.assert(b === db.selectValue("SELECT "+b)).
-        assert(b === db.selectValue("SELECT ?", b)).
-        assert(mI == db.selectValue("SELECT $x", {$x:mI}));
-    }else{
-      /* Curiously, the JS spec seems to be off by one with the definitions
-         of MIN/MAX_SAFE_INTEGER:
-
-         https://github.com/emscripten-core/emscripten/issues/17391 */
-      T.mustThrow(()=>db.selectValue("SELECT "+(Number.MAX_SAFE_INTEGER+1))).
-        mustThrow(()=>db.selectValue("SELECT "+(Number.MIN_SAFE_INTEGER-1)));
-    }
-
-    st = db.prepare("update t set b=:b where a='blob'");
-    try {
-      const ndx = st.getParamIndex(':b');
-      T.assert(1===ndx);
-      st.bindAsBlob(ndx, "ima blob").reset(true);
-    } finally {
-      st.finalize();
-    }
-
-    try {
-      throw new sqlite3.WasmAllocError;
-    }catch(e){
-      T.assert(e instanceof Error)
-        .assert(e instanceof sqlite3.WasmAllocError);
-    }
-
-    try {
-      db.prepare("/*empty SQL*/");
-      toss("Must not be reached.");
-    }catch(e){
-      T.assert(e instanceof sqlite3.SQLite3Error)
-        .assert(0==e.message.indexOf('Cannot prepare empty'));
-    }
-
-    T.assert(capi.sqlite3_errstr(capi.SQLITE_IOERR_ACCESS).indexOf("I/O")>=0).
-      assert(capi.sqlite3_errstr(capi.SQLITE_CORRUPT).indexOf('malformed')>0).
-      assert(capi.sqlite3_errstr(capi.SQLITE_OK) === 'not an error');
-    
-    // Custom db error message handling via sqlite3_prepare_v2/v3()
-    if(capi.wasm.exports.sqlite3_wasm_db_error){
-      log("Testing custom error message via prepare_v3()...");
-      let rc = capi.sqlite3_prepare_v3(db.pointer, {/*invalid*/}, -1, 0, null, null);
-      T.assert(capi.SQLITE_MISUSE === rc)
-        .assert(0 === capi.sqlite3_errmsg(db.pointer).indexOf("Invalid SQL"));
-      log("errmsg =",capi.sqlite3_errmsg(db.pointer));
-    }
-  }/*testBasicSanity()*/;
-
-  const testUDF = function(db){
-    db.createFunction("foo",(pCx,a,b)=>a+b);
-    T.assert(7===db.selectValue("select foo(3,4)")).
-      assert(5===db.selectValue("select foo(3,?)",2)).
-      assert(5===db.selectValue("select foo(?,?2)",[1,4])).
-      assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
-    db.createFunction("bar", {
-      arity: -1,
-      callback: function(pCx){
-        var rc = 0;
-        for(let i = 1; i < arguments.length; ++i) rc += arguments[i];
-        return rc;
-      }
-    }).createFunction({
-      name: "asis",
-      callback: (pCx,arg)=>arg
-    });
-
-    //log("Testing DB::selectValue() w/ UDF...");
-    T.assert(0===db.selectValue("select bar()")).
-      assert(1===db.selectValue("select bar(1)")).
-      assert(3===db.selectValue("select bar(1,2)")).
-      assert(-1===db.selectValue("select bar(1,2,-4)")).
-      assert('hi' === db.selectValue("select asis('hi')")).
-      assert('hi' === db.selectValue("select ?",'hi')).
-      assert(null === db.selectValue("select null")).
-      assert(null === db.selectValue("select asis(null)")).
-      assert(1 === db.selectValue("select ?",1)).
-      assert(2 === db.selectValue("select ?",[2])).
-      assert(3 === db.selectValue("select $a",{$a:3})).
-      assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
-      assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
-
-    //log("Testing binding and UDF propagation of blobs...");
-    let blobArg = new Uint8Array(2);
-    blobArg.set([0x68, 0x69], 0);
-    let blobRc = db.selectValue("select asis(?1)", blobArg);
-    T.assert(blobRc instanceof Uint8Array).
-      assert(2 === blobRc.length).
-      assert(0x68==blobRc[0] && 0x69==blobRc[1]);
-    blobRc = db.selectValue("select asis(X'6869')");
-    T.assert(blobRc instanceof Uint8Array).
-      assert(2 === blobRc.length).
-      assert(0x68==blobRc[0] && 0x69==blobRc[1]);
-
-    blobArg = new Int8Array(2);
-    blobArg.set([0x68, 0x69]);
-    //debug("blobArg=",blobArg);
-    blobRc = db.selectValue("select asis(?1)", blobArg);
-    T.assert(blobRc instanceof Uint8Array).
-      assert(2 === blobRc.length);
-    //debug("blobRc=",blobRc);
-    T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
-  };
-
-  const testAttach = function(db){
-    const resultRows = [];
-    db.exec({
-      sql:new TextEncoder('utf-8').encode([
-        // ^^^ testing string-vs-typedarray handling in exec()
-        "attach 'session' as foo;" /* name 'session' is magic for kvvfs! */,
-        "create table foo.bar(a);",
-        "insert into foo.bar(a) values(1),(2),(3);",
-        "select a from foo.bar order by a;"
-      ].join('')),
-      rowMode: 0,
-      resultRows
-    });
-    T.assert(3===resultRows.length)
-      .assert(2===resultRows[1]);
-    T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
-    let colCount = 0, rowCount = 0;
-    const execCallback = function(pVoid, nCols, aVals, aNames){
-      colCount = nCols;
-      ++rowCount;
-      T.assert(2===aVals.length)
-        .assert(2===aNames.length)
-        .assert(+(aVals[1]) === 2 * +(aVals[0]));
-    };
-    const capi = sqlite3.capi;
-    let rc = capi.sqlite3_exec(
-      db.pointer, "select a, a*2 from foo.bar", execCallback,
-      0, 0
-    );
-    T.assert(0===rc).assert(3===rowCount).assert(2===colCount);
-    rc = capi.sqlite3_exec(
-      db.pointer, "select a from foo.bar", ()=>{
-        toss("Testing throwing from exec() callback.");
-      }, 0, 0
-    );
-    T.assert(capi.SQLITE_ABORT === rc);
-    db.exec("detach foo");
-    T.mustThrow(()=>db.exec("select * from foo.bar"));
-  };
-
-  const testIntPtr = function(db,S){
-    const w = S.capi.wasm;
-    const stack = w.scopedAllocPush();
-    let ptrInt;
-    const origValue = 512;
-    const ptrValType = 'i32';
-    try{
-      ptrInt = w.scopedAlloc(4);
-      w.setMemValue(ptrInt,origValue, ptrValType);
-      const cf = w.xGet('jaccwabyt_test_intptr');
-      const oldPtrInt = ptrInt;
-      //log('ptrInt',ptrInt);
-      //log('getMemValue(ptrInt)',w.getMemValue(ptrInt));
-      T.assert(origValue === w.getMemValue(ptrInt, ptrValType));
-      const rc = cf(ptrInt);
-      //log('cf(ptrInt)',rc);
-      //log('ptrInt',ptrInt);
-      //log('getMemValue(ptrInt)',w.getMemValue(ptrInt,ptrValType));
-      T.assert(2*origValue === rc).
-        assert(rc === w.getMemValue(ptrInt,ptrValType)).
-        assert(oldPtrInt === ptrInt);
-      const pi64 = w.scopedAlloc(8)/*ptr to 64-bit integer*/;
-      const o64 = 0x010203040506/*>32-bit integer*/;
-      const ptrType64 = 'i64';
-      if(w.bigIntEnabled){
-        log("BigInt support is enabled...");
-        w.setMemValue(pi64, o64, ptrType64);
-        //log("pi64 =",pi64, "o64 = 0x",o64.toString(16), o64);
-        const v64 = ()=>w.getMemValue(pi64,ptrType64)
-        //log("getMemValue(pi64)",v64());
-        T.assert(v64() == o64);
-        //T.assert(o64 === w.getMemValue(pi64, ptrType64));
-        const cf64w = w.xGet('jaccwabyt_test_int64ptr');
-        cf64w(pi64);
-        //log("getMemValue(pi64)",v64());
-        T.assert(v64() == BigInt(2 * o64));
-        cf64w(pi64);
-        T.assert(v64() == BigInt(4 * o64));
-
-        const biTimes2 = w.xGet('jaccwabyt_test_int64_times2');
-        T.assert(BigInt(2 * o64) ===
-                 biTimes2(BigInt(o64)/*explicit conv. required to avoid TypeError
-                                       in the call :/ */));
-
-        const pMin = w.scopedAlloc(16);
-        const pMax = pMin + 8;
-        const g64 = (p)=>w.getMemValue(p,ptrType64);
-        w.setMemValue(pMin, 0, ptrType64);
-        w.setMemValue(pMax, 0, ptrType64);
-        const minMaxI64 = [
-          w.xCall('jaccwabyt_test_int64_min'),
-          w.xCall('jaccwabyt_test_int64_max')
-        ];
-        T.assert(minMaxI64[0] < BigInt(Number.MIN_SAFE_INTEGER)).
-          assert(minMaxI64[1] > BigInt(Number.MAX_SAFE_INTEGER));
-        //log("int64_min/max() =",minMaxI64, typeof minMaxI64[0]);
-        w.xCall('jaccwabyt_test_int64_minmax', pMin, pMax);
-        T.assert(g64(pMin) === minMaxI64[0], "int64 mismatch").
-          assert(g64(pMax) === minMaxI64[1], "int64 mismatch");
-        //log("pMin",g64(pMin), "pMax",g64(pMax));
-        w.setMemValue(pMin, minMaxI64[0], ptrType64);
-        T.assert(g64(pMin) === minMaxI64[0]).
-          assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
-          assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
-        const rxRange = /too big/;
-        T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
-                          rxRange).
-          mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
-                          (e)=>rxRange.test(e.message));
-      }else{
-        log("No BigInt support. Skipping related tests.");
-        log("\"The problem\" here is that we can manipulate, at the byte level,",
-            "heap memory to set 64-bit values, but we can't get those values",
-            "back into JS because of the lack of 64-bit integer support.");
-      }
-    }finally{
-      const x = w.scopedAlloc(1), y = w.scopedAlloc(1), z = w.scopedAlloc(1);
-      //log("x=",x,"y=",y,"z=",z); // just looking at the alignment
-      w.scopedAllocPop(stack);
-    }
-  }/*testIntPtr()*/;
-  
-  const testStructStuff = function(db,S,M){
-    const W = S.capi.wasm, C = S;
-    /** Maintenance reminder: the rest of this function is copy/pasted
-        from the upstream jaccwabyt tests. */
-    log("Jaccwabyt tests...");
-    const MyStructDef = {
-      sizeof: 16,
-      members: {
-        p4: {offset: 0, sizeof: 4, signature: "i"},
-        pP: {offset: 4, sizeof: 4, signature: "P"},
-        ro: {offset: 8, sizeof: 4, signature: "i", readOnly: true},
-        cstr: {offset: 12, sizeof: 4, signature: "s"}
-      }
-    };
-    if(W.bigIntEnabled){
-      const m = MyStructDef;
-      m.members.p8 = {offset: m.sizeof, sizeof: 8, signature: "j"};
-      m.sizeof += m.members.p8.sizeof;
-    }
-    const StructType = C.StructBinder.StructType;
-    const K = C.StructBinder('my_struct',MyStructDef);
-    T.mustThrowMatching(()=>K(), /via 'new'/).
-      mustThrowMatching(()=>new K('hi'), /^Invalid pointer/);
-    const k1 = new K(), k2 = new K();
-    try {
-      T.assert(k1.constructor === K).
-        assert(K.isA(k1)).
-        assert(k1 instanceof K).
-        assert(K.prototype.lookupMember('p4').key === '$p4').
-        assert(K.prototype.lookupMember('$p4').name === 'p4').
-        mustThrowMatching(()=>K.prototype.lookupMember('nope'), /not a mapped/).
-        assert(undefined === K.prototype.lookupMember('nope',false)).
-        assert(k1 instanceof StructType).
-        assert(StructType.isA(k1)).
-        assert(K.resolveToInstance(k1.pointer)===k1).
-        mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
-        assert(k1 === StructType.instanceForPointer(k1.pointer)).
-        mustThrowMatching(()=>k1.$ro = 1, /read-only/);
-      Object.keys(MyStructDef.members).forEach(function(key){
-        key = K.memberKey(key);
-        T.assert(0 == k1[key],
-                 "Expecting allocation to zero the memory "+
-                 "for "+key+" but got: "+k1[key]+
-                 " from "+k1.memoryDump());
-      });
-      T.assert('number' === typeof k1.pointer).
-        mustThrowMatching(()=>k1.pointer = 1, /pointer/).
-      assert(K.instanceForPointer(k1.pointer) === k1);
-      k1.$p4 = 1; k1.$pP = 2;
-      T.assert(1 === k1.$p4).assert(2 === k1.$pP);
-      if(MyStructDef.members.$p8){
-        k1.$p8 = 1/*must not throw despite not being a BigInt*/;
-        k1.$p8 = BigInt(Number.MAX_SAFE_INTEGER * 2);
-        T.assert(BigInt(2 * Number.MAX_SAFE_INTEGER) === k1.$p8);
-      }
-      T.assert(!k1.ondispose);
-      k1.setMemberCString('cstr', "A C-string.");
-      T.assert(Array.isArray(k1.ondispose)).
-        assert(k1.ondispose[0] === k1.$cstr).
-        assert('number' === typeof k1.$cstr).
-        assert('A C-string.' === k1.memberToJsString('cstr'));
-      k1.$pP = k2;
-      T.assert(k1.$pP === k2);
-      k1.$pP = null/*null is special-cased to 0.*/;
-      T.assert(0===k1.$pP);
-      let ptr = k1.pointer;
-      k1.dispose();
-      T.assert(undefined === k1.pointer).
-        assert(undefined === K.instanceForPointer(ptr)).
-        mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
-      const k3 = new K();
-      ptr = k3.pointer;
-      T.assert(k3 === K.instanceForPointer(ptr));
-      K.disposeAll();
-      T.assert(ptr).
-        assert(undefined === k2.pointer).
-        assert(undefined === k3.pointer).
-        assert(undefined === K.instanceForPointer(ptr));
-    }finally{
-      k1.dispose();
-      k2.dispose();
-    }
-
-    if(!W.bigIntEnabled){
-      log("Skipping WasmTestStruct tests: BigInt not enabled.");
-      return;
-    }
-
-    const ctype = W.xCallWrapped('jaccwabyt_test_ctype_json', 'json');
-    log("Struct descriptions:",ctype.structs);
-    const WTStructDesc =
-          ctype.structs.filter((e)=>'WasmTestStruct'===e.name)[0];
-    const autoResolvePtr = true /* EXPERIMENTAL */;
-    if(autoResolvePtr){
-      WTStructDesc.members.ppV.signature = 'P';
-    }
-    const WTStruct = C.StructBinder(WTStructDesc);
-    log(WTStruct.structName, WTStruct.structInfo);
-    const wts = new WTStruct();
-    log("WTStruct.prototype keys:",Object.keys(WTStruct.prototype));
-    try{
-      T.assert(wts.constructor === WTStruct).
-        assert(WTStruct.memberKeys().indexOf('$ppV')>=0).
-        assert(wts.memberKeys().indexOf('$v8')>=0).
-        assert(!K.isA(wts)).
-        assert(WTStruct.isA(wts)).
-        assert(wts instanceof WTStruct).
-        assert(wts instanceof StructType).
-        assert(StructType.isA(wts)).
-        assert(wts === StructType.instanceForPointer(wts.pointer));
-      T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
-        assert(0===wts.$ppV).assert(0===wts.$xFunc).
-        assert(WTStruct.instanceForPointer(wts.pointer) === wts);
-      const testFunc =
-            W.xGet('jaccwabyt_test_struct'/*name gets mangled in -O3 builds!*/);
-      let counter = 0;
-      log("wts.pointer =",wts.pointer);
-      const wtsFunc = function(arg){
-        log("This from a JS function called from C, "+
-            "which itself was called from JS. arg =",arg);
-        ++counter;
-        T.assert(WTStruct.instanceForPointer(arg) === wts);
-        if(3===counter){
-          toss("Testing exception propagation.");
-        }
-      }
-      wts.$v4 = 10; wts.$v8 = 20;
-      wts.$xFunc = W.installFunction(wtsFunc, wts.memberSignature('xFunc'))
-      /* ^^^ compiles wtsFunc to WASM and returns its new function pointer */;
-      T.assert(0===counter).assert(10 === wts.$v4).assert(20n === wts.$v8)
-        .assert(0 === wts.$ppV).assert('number' === typeof wts.$xFunc)
-        .assert(0 === wts.$cstr)
-        .assert(wts.memberIsString('$cstr'))
-        .assert(!wts.memberIsString('$v4'))
-        .assert(null === wts.memberToJsString('$cstr'))
-        .assert(W.functionEntry(wts.$xFunc) instanceof Function);
-      /* It might seem silly to assert that the values match
-         what we just set, but recall that all of those property
-         reads and writes are, via property interceptors,
-         actually marshaling their data to/from a raw memory
-         buffer, so merely reading them back is actually part of
-         testing the struct-wrapping API. */
-
-      testFunc(wts.pointer);
-      log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
-      T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
-        .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer))
-        .assert('string' === typeof wts.memberToJsString('cstr'))
-        .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
-        .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
-                           /Invalid member type signature for C-string/)
-      ;
-      testFunc(wts.pointer);
-      T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
-        .assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer));
-      /** The 3rd call to wtsFunc throw from JS, which is called
-          from C, which is called from JS. Let's ensure that
-          that exception propagates back here... */
-      T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
-      W.uninstallFunction(wts.$xFunc);
-      wts.$xFunc = 0;
-      if(autoResolvePtr){
-        wts.$ppV = 0;
-        T.assert(!wts.$ppV);
-        WTStruct.debugFlags(0x03);
-        wts.$ppV = wts;
-        T.assert(wts === wts.$ppV)
-        WTStruct.debugFlags(0);
-      }
-      wts.setMemberCString('cstr', "A C-string.");
-      T.assert(Array.isArray(wts.ondispose)).
-        assert(wts.ondispose[0] === wts.$cstr).
-        assert('A C-string.' === wts.memberToJsString('cstr'));
-      const ptr = wts.pointer;
-      wts.dispose();
-      T.assert(ptr).assert(undefined === wts.pointer).
-        assert(undefined === WTStruct.instanceForPointer(ptr))
-    }finally{
-      wts.dispose();
-    }
-  }/*testStructStuff()*/;
-
-  const testSqliteStructs = function(db,sqlite3,M){
-    log("Tinkering with sqlite3_io_methods...");
-    // https://www.sqlite.org/c3ref/vfs.html
-    // https://www.sqlite.org/c3ref/io_methods.html
-    const capi = sqlite3.capi, W = capi.wasm;
-    const sqlite3_io_methods = capi.sqlite3_io_methods,
-          sqlite3_vfs = capi.sqlite3_vfs,
-          sqlite3_file = capi.sqlite3_file;
-    log("struct sqlite3_file", sqlite3_file.memberKeys());
-    log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
-    log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
-
-    const installMethod = function callee(tgt, name, func){
-      if(1===arguments.length){
-        return (n,f)=>callee(tgt,n,f);
-      }
-      if(!callee.argcProxy){
-        callee.argcProxy = function(func,sig){
-          return function(...args){
-            if(func.length!==arguments.length){
-              toss("Argument mismatch. Native signature is:",sig);
-            }
-            return func.apply(this, args);
-          }
-        };
-        callee.ondisposeRemoveFunc = function(){
-          if(this.__ondispose){
-            const who = this;
-            this.__ondispose.forEach(
-              (v)=>{
-                if('number'===typeof v){
-                  try{capi.wasm.uninstallFunction(v)}
-                  catch(e){/*ignore*/}
-                }else{/*wasm function wrapper property*/
-                  delete who[v];
-                }
-              }
-            );
-            delete this.__ondispose;
-          }
-        };
-      }/*static init*/
-      const sigN = tgt.memberSignature(name),
-            memKey = tgt.memberKey(name);
-      //log("installMethod",tgt, name, sigN);
-      if(!tgt.__ondispose){
-        T.assert(undefined === tgt.ondispose);
-        tgt.ondispose = [callee.ondisposeRemoveFunc];
-        tgt.__ondispose = [];
-      }
-      const fProxy = callee.argcProxy(func, sigN);
-      const pFunc = capi.wasm.installFunction(fProxy, tgt.memberSignature(name, true));
-      tgt[memKey] = pFunc;
-      /**
-         ACHTUNG: function pointer IDs are from a different pool than
-         allocation IDs, starting at 1 and incrementing in steps of 1,
-         so if we set tgt[memKey] to those values, we'd very likely
-         later misinterpret them as plain old pointer addresses unless
-         unless we use some silly heuristic like "all values <5k are
-         presumably function pointers," or actually perform a function
-         lookup on every pointer to first see if it's a function. That
-         would likely work just fine, but would be kludgy.
-
-         It turns out that "all values less than X are functions" is
-         essentially how it works in wasm: a function pointer is
-         reported to the client as its index into the
-         __indirect_function_table.
-
-         So... once jaccwabyt can be told how to access the
-         function table, it could consider all pointer values less
-         than that table's size to be functions.  As "real" pointer
-         values start much, much higher than the function table size,
-         that would likely work reasonably well. e.g. the object
-         pointer address for sqlite3's default VFS is (in this local
-         setup) 65104, whereas the function table has fewer than 600
-         entries.
-      */
-      const wrapperKey = '$'+memKey;
-      tgt[wrapperKey] = fProxy;
-      tgt.__ondispose.push(pFunc, wrapperKey);
-      //log("tgt.__ondispose =",tgt.__ondispose);
-      return (n,f)=>callee(tgt, n, f);
-    }/*installMethod*/;
-
-    const installIOMethods = function instm(iom){
-      (iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
-      if(!instm._requireFileArg){
-        instm._requireFileArg = function(arg,methodName){
-          arg = capi.sqlite3_file.resolveToInstance(arg);
-          if(!arg){
-            err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
-          }
-          return arg;
-        };
-        instm._methods = {
-          // https://sqlite.org/c3ref/io_methods.html
-          xClose: /*i(P)*/function(f){
-            /* int (*xClose)(sqlite3_file*) */
-            log("xClose(",f,")");
-            if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
-            f.dispose(/*noting that f has externally-owned memory*/);
-            return 0;
-          },
-          xRead: /*i(Ppij)*/function(f,dest,n,offset){
-            /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
-            log("xRead(",arguments,")");
-            if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
-            capi.wasm.heap8().fill(0, dest + offset, n);
-            return 0;
-          },
-          xWrite: /*i(Ppij)*/function(f,dest,n,offset){
-            /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
-            log("xWrite(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
-            return 0;
-          },
-          xTruncate: /*i(Pj)*/function(f){
-            /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
-            log("xTruncate(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
-            return 0;
-          },
-          xSync: /*i(Pi)*/function(f){
-            /* int (*xSync)(sqlite3_file*, int flags) */
-            log("xSync(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
-            return 0;
-          },
-          xFileSize: /*i(Pp)*/function(f,pSz){
-            /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
-            log("xFileSize(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
-            capi.wasm.setMemValue(pSz, 0/*file size*/);
-            return 0;
-          },
-          xLock: /*i(Pi)*/function(f){
-            /* int (*xLock)(sqlite3_file*, int) */
-            log("xLock(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
-            return 0;
-          },
-          xUnlock: /*i(Pi)*/function(f){
-            /* int (*xUnlock)(sqlite3_file*, int) */
-            log("xUnlock(",arguments,")");
-            if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
-            return 0;
-          },
-          xCheckReservedLock: /*i(Pp)*/function(){
-            /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
-            log("xCheckReservedLock(",arguments,")");
-            return 0;
-          },
-          xFileControl: /*i(Pip)*/function(){
-            /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
-            log("xFileControl(",arguments,")");
-            return capi.SQLITE_NOTFOUND;
-          },
-          xSectorSize: /*i(P)*/function(){
-            /* int (*xSectorSize)(sqlite3_file*) */
-            log("xSectorSize(",arguments,")");
-            return 0/*???*/;
-          },
-          xDeviceCharacteristics:/*i(P)*/function(){
-            /* int (*xDeviceCharacteristics)(sqlite3_file*) */
-            log("xDeviceCharacteristics(",arguments,")");
-            return 0;
-          }
-        };
-      }/*static init*/
-      iom.$iVersion = 1;
-      Object.keys(instm._methods).forEach(
-        (k)=>installMethod(iom, k, instm._methods[k])
-      );
-    }/*installIOMethods()*/;
-
-    const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
-    const err = console.error.bind(console);
-    try {
-      const IOM = sqlite3_io_methods, S3F = sqlite3_file;
-      //log("iom proto",iom,iom.constructor.prototype);
-      //log("sfile",sfile,sfile.constructor.prototype);
-      T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
-      //log("iom",iom);
-      sfile.$pMethods = iom.pointer;
-      T.assert(iom.pointer === sfile.$pMethods)
-        .assert(IOM.resolveToInstance(iom))
-        .assert(undefined ===IOM.resolveToInstance(sfile))
-        .mustThrow(()=>IOM.resolveToInstance(0,true))
-        .assert(S3F.resolveToInstance(sfile.pointer))
-        .assert(undefined===S3F.resolveToInstance(iom))
-        .assert(iom===IOM.resolveToInstance(sfile.$pMethods));
-      T.assert(0===iom.$iVersion);
-      installIOMethods(iom);
-      T.assert(1===iom.$iVersion);
-      //log("iom.__ondispose",iom.__ondispose);
-      T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
-    }finally{
-      iom.dispose();
-      T.assert(undefined === iom.__ondispose);
-    }
-
-    const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
-    try {
-      const SB = sqlite3.StructBinder;
-      T.assert(dVfs instanceof SB.StructType)
-        .assert(dVfs.pointer)
-        .assert('sqlite3_vfs' === dVfs.structName)
-        .assert(!!dVfs.structInfo)
-        .assert(SB.StructType.hasExternalPointer(dVfs))
-        .assert(dVfs.$iVersion>0)
-        .assert('number'===typeof dVfs.$zName)
-        .assert('number'===typeof dVfs.$xSleep)
-        .assert(capi.wasm.functionEntry(dVfs.$xOpen))
-        .assert(dVfs.memberIsString('zName'))
-        .assert(dVfs.memberIsString('$zName'))
-        .assert(!dVfs.memberIsString('pAppData'))
-        .mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
-                           /Invalid member type signature for C-string/)
-        .mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
-        .assert('string' === typeof dVfs.memberToJsString('zName'))
-        .assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
-      ;
-      log("Default VFS: @",dVfs.pointer);
-      Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
-        const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
-              addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
-        if(1===mbr.signature.length){
-          let sep = '?', val = undefined;
-          switch(mbr.signature[0]){
-              // TODO: move this into an accessor, e.g. getPreferredValue(member)
-              case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
-              case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
-              case 's': sep = '=';
-                //val = capi.wasm.UTF8ToString(addr);
-                val = dVfs.memberToJsString(mname);
-                break;
-          }
-          log(prefix, sep, val);
-        }
-        else{
-          log(prefix," = funcptr @",addr, capi.wasm.functionEntry(addr));
-        }
-      });
-    }finally{
-      dVfs.dispose();
-      T.assert(undefined===dVfs.pointer);
-    }
-  }/*testSqliteStructs()*/;
-
-  const testWasmUtil = function(DB,S){
-    const w = S.capi.wasm;
-    /**
-       Maintenance reminder: the rest of this function is part of the
-       upstream Jaccwabyt tree.
-    */
-    const chr = (x)=>x.charCodeAt(0);
-    log("heap getters...");
-    {
-      const li = [8, 16, 32];
-      if(w.bigIntEnabled) li.push(64);
-      for(const n of li){
-        const bpe = n/8;
-        const s = w.heapForSize(n,false);
-        T.assert(bpe===s.BYTES_PER_ELEMENT).
-          assert(w.heapForSize(s.constructor) === s);
-        const u = w.heapForSize(n,true);
-        T.assert(bpe===u.BYTES_PER_ELEMENT).
-          assert(s!==u).
-          assert(w.heapForSize(u.constructor) === u);
-      }
-    }
-
-    log("jstrlen()...");
-    {
-      T.assert(3 === w.jstrlen("abc")).assert(4 === w.jstrlen("äbc"));
-    }
-
-    log("jstrcpy()...");
-    {
-      const fillChar = 10;
-      let ua = new Uint8Array(8), rc,
-          refill = ()=>ua.fill(fillChar);
-      refill();
-      rc = w.jstrcpy("hello", ua);
-      T.assert(6===rc).assert(0===ua[5]).assert(chr('o')===ua[4]);
-      refill();
-      ua[5] = chr('!');
-      rc = w.jstrcpy("HELLO", ua, 0, -1, false);
-      T.assert(5===rc).assert(chr('!')===ua[5]).assert(chr('O')===ua[4]);
-      refill();
-      rc = w.jstrcpy("the end", ua, 4);
-      //log("rc,ua",rc,ua);
-      T.assert(4===rc).assert(0===ua[7]).
-        assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
-      refill();
-      rc = w.jstrcpy("the end", ua, 4, -1, false);
-      T.assert(4===rc).assert(chr(' ')===ua[7]).
-        assert(chr('e')===ua[6]).assert(chr('t')===ua[4]);
-      refill();
-      rc = w.jstrcpy("", ua, 0, 1, true);
-      //log("rc,ua",rc,ua);
-      T.assert(1===rc).assert(0===ua[0]);
-      refill();
-      rc = w.jstrcpy("x", ua, 0, 1, true);
-      //log("rc,ua",rc,ua);
-      T.assert(1===rc).assert(0===ua[0]);
-      refill();
-      rc = w.jstrcpy('äbä', ua, 0, 1, true);
-      T.assert(1===rc, 'Must not write partial multi-byte char.')
-        .assert(0===ua[0]);
-      refill();
-      rc = w.jstrcpy('äbä', ua, 0, 2, true);
-      T.assert(1===rc, 'Must not write partial multi-byte char.')
-        .assert(0===ua[0]);
-      refill();
-      rc = w.jstrcpy('äbä', ua, 0, 2, false);
-      T.assert(2===rc).assert(fillChar!==ua[1]).assert(fillChar===ua[2]);
-    }/*jstrcpy()*/
-
-    log("cstrncpy()...");
-    {
-      const scope = w.scopedAllocPush();
-      try {
-        let cStr = w.scopedAllocCString("hello");
-        const n = w.cstrlen(cStr);
-        let cpy = w.scopedAlloc(n+10);
-        let rc = w.cstrncpy(cpy, cStr, n+10);
-        T.assert(n+1 === rc).
-          assert("hello" === w.cstringToJs(cpy)).
-          assert(chr('o') === w.getMemValue(cpy+n-1)).
-          assert(0 === w.getMemValue(cpy+n));
-        let cStr2 = w.scopedAllocCString("HI!!!");
-        rc = w.cstrncpy(cpy, cStr2, 3);
-        T.assert(3===rc).
-          assert("HI!lo" === w.cstringToJs(cpy)).
-          assert(chr('!') === w.getMemValue(cpy+2)).
-          assert(chr('l') === w.getMemValue(cpy+3));
-      }finally{
-        w.scopedAllocPop(scope);
-      }
-    }
-
-    log("jstrToUintArray()...");
-    {
-      let a = w.jstrToUintArray("hello", false);
-      T.assert(5===a.byteLength).assert(chr('o')===a[4]);
-      a = w.jstrToUintArray("hello", true);
-      T.assert(6===a.byteLength).assert(chr('o')===a[4]).assert(0===a[5]);
-      a = w.jstrToUintArray("äbä", false);
-      T.assert(5===a.byteLength).assert(chr('b')===a[2]);
-      a = w.jstrToUintArray("äbä", true);
-      T.assert(6===a.byteLength).assert(chr('b')===a[2]).assert(0===a[5]);
-    }
-
-    log("allocCString()...");
-    {
-      const cstr = w.allocCString("hällo, world");
-      const n = w.cstrlen(cstr);
-      T.assert(13 === n)
-        .assert(0===w.getMemValue(cstr+n))
-        .assert(chr('d')===w.getMemValue(cstr+n-1));
-    }
-
-    log("scopedAlloc() and friends...");
-    {
-      const alloc = w.alloc, dealloc = w.dealloc;
-      w.alloc = w.dealloc = null;
-      T.assert(!w.scopedAlloc.level)
-        .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
-        .mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
-      w.alloc = alloc;
-      T.mustThrowMatching(()=>w.scopedAllocPush(), /missing alloc/);
-      w.dealloc = dealloc;
-      T.mustThrowMatching(()=>w.scopedAllocPop(), /^Invalid state/)
-        .mustThrowMatching(()=>w.scopedAlloc(1), /^No scopedAllocPush/)
-        .mustThrowMatching(()=>w.scopedAlloc.level=0, /read-only/);
-      const asc = w.scopedAllocPush();
-      let asc2;
-      try {
-        const p1 = w.scopedAlloc(16),
-              p2 = w.scopedAlloc(16);
-        T.assert(1===w.scopedAlloc.level)
-          .assert(Number.isFinite(p1))
-          .assert(Number.isFinite(p2))
-          .assert(asc[0] === p1)
-          .assert(asc[1]===p2);
-        asc2 = w.scopedAllocPush();
-        const p3 = w.scopedAlloc(16);
-        T.assert(2===w.scopedAlloc.level)
-          .assert(Number.isFinite(p3))
-          .assert(2===asc.length)
-          .assert(p3===asc2[0]);
-
-        const [z1, z2, z3] = w.scopedAllocPtr(3);
-        T.assert('number'===typeof z1).assert(z2>z1).assert(z3>z2)
-          .assert(0===w.getMemValue(z1,'i32'), 'allocPtr() must zero the targets')
-          .assert(0===w.getMemValue(z3,'i32'));
-      }finally{
-        // Pop them in "incorrect" order to make sure they behave:
-        w.scopedAllocPop(asc);
-        T.assert(0===asc.length);
-        T.mustThrowMatching(()=>w.scopedAllocPop(asc),
-                            /^Invalid state object/);
-        if(asc2){
-          T.assert(2===asc2.length,'Should be p3 and z1');
-          w.scopedAllocPop(asc2);
-          T.assert(0===asc2.length);
-          T.mustThrowMatching(()=>w.scopedAllocPop(asc2),
-                              /^Invalid state object/);
-        }
-      }
-      T.assert(0===w.scopedAlloc.level);
-      w.scopedAllocCall(function(){
-        T.assert(1===w.scopedAlloc.level);
-        const [cstr, n] = w.scopedAllocCString("hello, world", true);
-        T.assert(12 === n)
-          .assert(0===w.getMemValue(cstr+n))
-          .assert(chr('d')===w.getMemValue(cstr+n-1));
-      });
-    }/*scopedAlloc()*/
-
-    log("xCall()...");
-    {
-      const pJson = w.xCall('jaccwabyt_test_ctype_json');
-      T.assert(Number.isFinite(pJson)).assert(w.cstrlen(pJson)>300);
-    }
-
-    log("xWrap()...");
-    {
-      //int jaccwabyt_test_intptr(int * p);
-      //int64_t jaccwabyt_test_int64_max(void)
-      //int64_t jaccwabyt_test_int64_min(void)
-      //int64_t jaccwabyt_test_int64_times2(int64_t x)
-      //void jaccwabyt_test_int64_minmax(int64_t * min, int64_t *max)
-      //int64_t jaccwabyt_test_int64ptr(int64_t * p)
-      //const char * jaccwabyt_test_ctype_json(void)
-      T.mustThrowMatching(()=>w.xWrap('jaccwabyt_test_ctype_json',null,'i32'),
-                          /requires 0 arg/).
-        assert(w.xWrap.resultAdapter('i32') instanceof Function).
-        assert(w.xWrap.argAdapter('i32') instanceof Function);
-      let fw = w.xWrap('jaccwabyt_test_ctype_json','string');
-      T.mustThrowMatching(()=>fw(1), /requires 0 arg/);
-      let rc = fw();
-      T.assert('string'===typeof rc).assert(rc.length>300);
-      rc = w.xCallWrapped('jaccwabyt_test_ctype_json','*');
-      T.assert(rc>0 && Number.isFinite(rc));
-      rc = w.xCallWrapped('jaccwabyt_test_ctype_json','string');
-      T.assert('string'===typeof rc).assert(rc.length>300);
-      fw = w.xWrap('jaccwabyt_test_str_hello', 'string:free',['i32']);
-      rc = fw(0);
-      T.assert('hello'===rc);
-      rc = fw(1);
-      T.assert(null===rc);
-
-      w.xWrap.resultAdapter('thrice', (v)=>3n*BigInt(v));
-      w.xWrap.argAdapter('twice', (v)=>2n*BigInt(v));
-      fw = w.xWrap('jaccwabyt_test_int64_times2','thrice','twice');
-      rc = fw(1);
-      T.assert(12n===rc);
-
-      w.scopedAllocCall(function(){
-        let pI1 = w.scopedAlloc(8), pI2 = pI1+4;
-        w.setMemValue(pI1, 0,'*')(pI2, 0, '*');
-        let f = w.xWrap('jaccwabyt_test_int64_minmax',undefined,['i64*','i64*']);
-        let r1 = w.getMemValue(pI1, 'i64'), r2 = w.getMemValue(pI2, 'i64');
-        T.assert(!Number.isSafeInteger(r1)).assert(!Number.isSafeInteger(r2));
-      });
-    }
-  }/*testWasmUtil()*/;
-
-
-  /**
-     Tests for sqlite3.capi.wasm.pstack().
-   */
-  const testPstack = function(db,sqlite3){
-    const w = sqlite3.capi.wasm, P = w.pstack;
-    const isAllocErr = (e)=>e instanceof sqlite3.WasmAllocError;
-    const stack = P.pointer;
-    T.assert(0===stack % 8 /* must be 8-byte aligned */);
-    try{
-      const remaining = P.remaining;
-      log("pstack quota, remaining",P.quota,remaining);
-      T.assert(P.quota >= 4096)
-        .assert(remaining === P.quota)
-        .mustThrowMatching(()=>P.alloc(0), isAllocErr)
-        .mustThrowMatching(()=>P.alloc(-1), isAllocErr);
-      let p1 = P.alloc(12);
-      T.assert(p1 === stack - 16/*8-byte aligned*/)
-        .assert(P.pointer === p1);
-      let p2 = P.alloc(7);
-      T.assert(p2 === p1-8/*8-byte aligned, stack grows downwards*/)
-        .mustThrowMatching(()=>P.alloc(remaining), isAllocErr)
-        .assert(24 === stack - p2)
-        .assert(P.pointer === p2);
-      let n = remaining - (stack - p2);
-      let p3 = P.alloc(n);
-      T.assert(p3 === stack-remaining)
-        .mustThrowMatching(()=>P.alloc(1), isAllocErr);
-    }finally{
-      P.restore(stack);
-    }
-
-    T.assert(P.pointer === stack);
-    try {
-      const [p1, p2, p3] = P.allocChunks(3,4);
-      T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/)
-        .assert(p2 === p1 + 4)
-        .assert(p3 === p2 + 4);
-      T.mustThrowMatching(()=>P.allocChunks(1024, 1024 * 16),
-                          (e)=>e instanceof sqlite3.WasmAllocError)
-    }finally{
-      P.restore(stack);
-    }
-
-    T.assert(P.pointer === stack);
-    try {
-      let [p1, p2, p3] = P.allocPtr(3,false);
-      let sPos = stack-16/*always rounded to multiple of 8*/;
-      T.assert(P.pointer === sPos)
-        .assert(p2 === p1 + 4)
-        .assert(p3 === p2 + 4);
-      [p1, p2, p3] = P.allocPtr(3);
-      T.assert(P.pointer === sPos-24/*3 x 8 bytes*/)
-        .assert(p2 === p1 + 8)
-        .assert(p3 === p2 + 8);
-      p1 = P.allocPtr();
-      T.assert('number'===typeof p1);
-    }finally{
-      P.restore(stack);
-    }
-  }/*testPstack()*/;
-
-  const clearKvvfs = function(){
-    const sz = sqlite3.capi.sqlite3_web_kvvfs_size();
-    const n = sqlite3.capi.sqlite3_web_kvvfs_clear('');
-    log("Cleared kvvfs local/sessionStorage:",
-        n,"entries totaling approximately",sz,"bytes.");
-  };
-
-  const runTests = function(_sqlite3){
-    sqlite3 = _sqlite3;
-    const capi = sqlite3.capi,
-          oo = sqlite3.oo1,
-          wasm = capi.wasm;
-    log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
-    log("Build options:",wasm.compileOptionUsed());
-    capi.sqlite3_wasmfs_opfs_dir()/*will install OPFS if available, plus a and non-locking VFS*/;
-    if(1){
-      /* Let's grab those last few lines of test coverage for
-         sqlite3-api.js... */
-      const rc = wasm.compileOptionUsed(['COMPILER']);
-      T.assert(1 === rc.COMPILER);
-      const obj = {COMPILER:undefined};
-      wasm.compileOptionUsed(obj);
-      T.assert(1 === obj.COMPILER);
-    }
-    log("WASM heap size =",wasm.heap8().length);
-    //log("capi.wasm.exports.__indirect_function_table",capi.wasm.exports.__indirect_function_table);
-
-    const wasmCtypes = wasm.ctype;
-    //log("wasmCtypes",wasmCtypes);
-    T.assert(wasmCtypes.structs[0].name==='sqlite3_vfs').
-      assert(wasmCtypes.structs[0].members.szOsFile.sizeof>=4).
-      assert(wasmCtypes.structs[1/*sqlite3_io_methods*/
-                             ].members.xFileSize.offset>0);
-    //log(wasmCtypes.structs[0].name,"members",wasmCtypes.structs[0].members);
-    [ /* Spot-check a handful of constants to make sure they got installed... */
-      'SQLITE_SCHEMA','SQLITE_NULL','SQLITE_UTF8',
-      'SQLITE_STATIC', 'SQLITE_DIRECTONLY',
-      'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE'
-    ].forEach(function(k){
-      T.assert('number' === typeof capi[k]);
-    });
-    [/* Spot-check a few of the WASM API methods. */
-      'alloc', 'dealloc', 'installFunction'
-    ].forEach(function(k){
-      T.assert(capi.wasm[k] instanceof Function);
-    });
-
-    let dbName = "/testing1.sqlite3";
-    let vfsName = undefined;
-    if(capi.sqlite3_web_db_uses_vfs(0,"kvvfs")){
-      dbName = "local";
-      vfsName = 'kvvfs';
-      logHtml("Found kvvfs. Clearing db(s) from sessionStorage and localStorage",
-              "and selecting kvvfs-friendly db name:",dbName);
-      clearKvvfs();
-    }
-    const db = new oo.DB(dbName,'c',vfsName), startTime = performance.now();
-    log("db is kvvfs?",capi.sqlite3_web_db_uses_vfs(db.pointer,"kvvfs"));
-    try {
-      log("db.filename =",db.filename,"db.fileName() =",db.getFilename());
-      const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
-            banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
-      [
-        testWasmUtil, testBasicSanity, testUDF,
-        testAttach, testIntPtr, testStructStuff,
-        testSqliteStructs, testPstack
-      ].forEach((f)=>{
-        const t = T.counter, n = performance.now();
-        logHtml(banner1,"Running",f.name+"()...");
-        f(db, sqlite3);
-        logHtml(banner2,f.name+"():",T.counter - t,'tests in',(performance.now() - n),"ms");
-      });
-    }finally{
-      db.close();
-      if('kvvfs'===vfsName) clearKvvfs();
-    }
-    logHtml("Total Test count:",T.counter,"in",(performance.now() - startTime),"ms");
-    log('capi.wasm.exports',capi.wasm.exports);
-  };
-
-  self.sqlite3TestModule.initSqlite3().then((S)=>{
-    runTests(S);
-  });
-})();
index 98f55bf41896605d4193aaf93c8c7d91968bd050..13be78b77683c9877379d5d5ac87b03b9de2e067 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Optimize\sthe\sIS\sNULL\sand\sIS\sNOT\sNULL\soperators\sso\sthat\sthey\savoid\sloading\nlarge\sstrings\sor\sblobs\soff\sof\sdisk\sif\sall\sit\sneeds\sto\sknow\sis\swhether\sor\nnot\sthe\sstring\sor\sblob\sis\sNULL.
-D 2022-10-13T15:09:44.252
+C Move\sthe\srest\sof\stesting1.js\sinto\stester1.js\sand\seliminate\sthe\sdependency\son\sjaccwabyt_test.c.\sExtend\sthe\slist\sof\sdefault\sconfig-related\s#defines\sin\ssqlite3-wasm.c\sand\sreorganize\sthem\sfor\smaintainability.
+D 2022-10-13T16:48:35.302
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -473,7 +473,7 @@ F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
 F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
 F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle 0e88c8cfc3719e4b7e74980d9da664c709e68acf863e48386cda376edfd3bfb0
-F ext/wasm/GNUmakefile 8ab74ed186a15d956a21b28fa0800b84af2b8a289392ae2dff8126ff033bd3f9
+F ext/wasm/GNUmakefile 4ec270532b921c7c4b437fbdb06f6a0ce41f3bd874395ce70dbc933c8553efa9
 F ext/wasm/README.md 1e5b28158b74ab3ffc9d54fcbc020f0bbeb82c2ff8bbd904214c86c70e8a3066
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 89983a8d122c35a90c65ec667844b95a78bcd04f3198a99c1e0c8368c1a0b03a
 F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
@@ -484,19 +484,19 @@ F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba814
 F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1ecc5ba9e64ef90a8b
 F ext/wasm/api/pre-js.js 2db711eb637991b383fc6b5c0f3df65ec48a7201e5730e304beba8de2d3f9b0b
 F ext/wasm/api/sqlite3-api-cleanup.js 5d22d1d3818ecacb23bfa223d5970cd0617d8cdbb48c8bc4bbd463f05b021a99
-F ext/wasm/api/sqlite3-api-glue.js 3b2a43c7b2ceb2b60f5f4a1afefbd93508c2fe0f2e667eea278afe570be4b606
-F ext/wasm/api/sqlite3-api-oo1.js ac1e08d36bdfb5aa0a2d75b7d4c892fd51819d34c932370c3282810672bcc086
+F ext/wasm/api/sqlite3-api-glue.js 842dc03783aecc951a543a209523343a6fda9af258fb0bee08e8cc2dc3c4d8ae
+F ext/wasm/api/sqlite3-api-oo1.js 00f5cfce0989d2e08d7b21765d703c69234245d03a0cce8fcb32ccfcd53ffdbb
 F ext/wasm/api/sqlite3-api-opfs.js 5a8ab3b76880c8ada8710ca9ba1ca5b160872edfd8bd5322e4f179a7f41cc616
-F ext/wasm/api/sqlite3-api-prologue.js 5c56056810333974c971f6310c5c9698cf7ca8b06f6d2f1986cec819d0f5bbad
+F ext/wasm/api/sqlite3-api-prologue.js b7c82a22d50658a48463fa646a23135273bc2cfa843aedda32627ff281c12e4d
 F ext/wasm/api/sqlite3-api-worker1.js 7f4f46cb6b512a48572d7567233896e6a9c46570c44bdc3d13419730c7c221c8
 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
-F ext/wasm/api/sqlite3-wasm.c a321f12ceedac8611c1377ccfb5df0c0547bd9395f7fd7613827de365d994948
+F ext/wasm/api/sqlite3-wasm.c 4c131945ced4b08a694d287abcdb066b896d961ef79ee5241805ecc37e83d63a
 F ext/wasm/batch-runner.html cf1a410c92bad50fcec2ddc71390b4e9df63a6ea1bef12a5163a66a0af4d78d9
 F ext/wasm/batch-runner.js 5bae81684728b6be157d1f92b39824153f0fd019345b39f2ab8930f7ee2a57d8
 F ext/wasm/common/SqliteTestUtil.js 647bf014bd30bdd870a7e9001e251d12fc1c9ec9ce176a1004b838a4b33c5c05
 F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/wasm/common/testing.css 12bd88c69eea1b61649ad9add3ff24f0fbc3b79a7616d74ab3997b7271cd8482
-F ext/wasm/common/whwasmutil.js bc8522a071f4754af7b50f53807b95f691d2f9e44fc3b3e8c65dff6ef2485c0d
+F ext/wasm/common/testing.css 53394885077edd3db22d2a0896192334dfc06fb3d1da0b646eb12a332d22f18e
+F ext/wasm/common/whwasmutil.js 50d2ede0b0fa01c1d467e1801fab79f5e46bb02bcbd2b0232e4fdc6090a47818
 F ext/wasm/demo-123-worker.html e50b51dc7271b9d3cc830cb7c2fba294d622f56b7acb199f7257d11195a63d49
 F ext/wasm/demo-123.html 7c239c9951d1b113f9f532969ac039294cf1dcfee2b3ae0a2c1ed2b3d59f8dfa
 F ext/wasm/demo-123.js d563cf9d725692ccd940c46df1c026d87863e0544942a2ba2015f17fba3f6f74
@@ -507,11 +507,9 @@ F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d695
 F ext/wasm/fiddle/fiddle-worker.js 531859a471924a0ea48afa218e6877f0c164ca324d51e15843ed6ecc1c65c7ee
 F ext/wasm/fiddle/fiddle.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
 F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5653284071715
-F ext/wasm/index.html 63b370619e4f849ac76f1baed435c05edc29dbb6795bc7c1c935561ff667dd27
+F ext/wasm/index.html 9ae9f9629310ed3ee88901aa14e20195815583806af245da08c2ecb43ced2243
 F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f32817456a0f3937fcfd934afeb32154ca33580ab264dab6c285e6dbbd215
 F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e
-F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
-F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19
 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
 F ext/wasm/scratchpad-wasmfs-main.js 1aa32c1035cf1440a226a28fefcbb5762fbbcb020ccbe5895f8736d701695c63
 F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
@@ -528,11 +526,10 @@ F ext/wasm/test-opfs-vfs.html eb69dda21eb414b8f5e3f7c1cc0f774103cc9c0f87b2d28a33
 F ext/wasm/test-opfs-vfs.js 56c3d725044c668fa7910451e96c1195d25ad95825f9ac79f747a7759d1973d0
 F ext/wasm/tester1-worker.html 0af7a22025ff1da72a84765d64f8f221844a57c6e6e314acf3a30f176101fd3f
 F ext/wasm/tester1.html fde0e0bdeaaa2c39877c749dc86a8c1c306f771c3d75b89a6289a5ed11243e9d
-F ext/wasm/tester1.js d25cf7615bd39ab9ab4be2f231fecf1fff1378227e695083dc35adc4fdff668e
+F ext/wasm/tester1.js d6a66fc36c7571b8050314b6d0e93e69cd6def88df14fb3ef2403e7aa244ec36
 F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
 F ext/wasm/testing-worker1-promiser.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e
 F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
-F ext/wasm/testing1.js 2034cf6972ab1506e75e41e532ca7cd3060e194c37edab3ff65d00d8a8af8ba2
 F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
 F ext/wasm/testing2.js 88f40ef3cd8201bdadd120a711c36bbf0ce56cc0eab1d5e7debb71fed7822494
 F ext/wasm/wasmfs.make 3cce1820006196de140f90f2da4b4ea657083fb5bfee7d125be43f7a85748c8f
@@ -2034,9 +2031,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 043e76e6166da5cf8e213cce46aaccb1f910e1fdbdb5556576eafb81b3bc5faa 5e9c67ba18b701aabbb0546acdfc532c9e8f0d27fb0a2c899415a5c47096c90b
-R c0d9c663bdd4b8318254b199c65d7e13
-T +closed 5e9c67ba18b701aabbb0546acdfc532c9e8f0d27fb0a2c899415a5c47096c90b
-U drh
-Z 57cc74128fdf4ddaf1ca461d6df422e4
+P cb94350185f555c333b628ee846c47bcc9df5f76bb82de569b8322f30dbbe1bc
+R 32dab828066657d904ede486bb592456
+U stephan
+Z 3fd1b23c49b42c2df54d60ffd9b89d7f
 # Remove this line to create a well-formed Fossil manifest.
index 82a31133559e7dd7240380b4ceb8e2a36aa087c4..7e72bfdd21dd89cfafbfbf30faefbf481bd7aaf8 100644 (file)
@@ -1 +1 @@
-cb94350185f555c333b628ee846c47bcc9df5f76bb82de569b8322f30dbbe1bc
\ No newline at end of file
+4e2a8aff2dd4b6e148f45184e2523ebe47815257eca97fa3d32bcbf9625f0def
\ No newline at end of file