***********************************************************************
Main functional and regression tests for the sqlite3 WASM API.
+
+ This mini-framework works like so:
+
+ This script adds a series of test groups, each of which contains an
+ arbitrary number of tests, into a queue. After loading of the
+ sqlite3 WASM/JS module is complete, that queue is processed. If any
+ given test fails, the whole thing fails. This script is built such
+ that it can run from the main UI thread or worker thread. Test
+ groups and individual tests can be assigned a predicate function
+ which determines whether to run them or not, and this is
+ specifically intended to be used to toggle certain tests on or off
+ for the main/worker threads.
+
+ Each test group defines a state object which gets applied as each
+ test function's `this`. Test functions can use that to, e.g., set up
+ a db in an early test and close it in a later test. Each test gets
+ passed the sqlite3 namespace object as its only argument.
*/
'use strict';
(function(){
on whether we are running in a worker thread or
the main (UI) thread.
*/
- let logHtml;
+ let logClass;
+ /* Predicate for tests/groups. */
const isUIThread = ()=>(self.window===self && self.document);
+ /* Predicate for tests/groups. */
+ const isWorker = ()=>!isUIThread();
+ /* Predicate for tests/groups. */
+ const testIsTodo = ()=>false;
+ const haveJaccwabytTests = function(){
+ return !!wasm.exports.jaccwabyt_test_int64_max;
+ };
const mapToString = (v)=>{
switch(typeof v){
case 'number': case 'string': case 'boolean':
default: break;
}
if(null===v) return 'null';
+ if(v instanceof Error){
+ v = {
+ message: v.message,
+ stack: v.stack,
+ errorClass: v.name
+ };
+ }
return JSON.stringify(v,undefined,2);
};
const normalizeArgs = (args)=>args.map(mapToString);
if( isUIThread() ){
- console.log("Running UI thread.");
- logHtml = function(cssClass,...args){
+ console.log("Running in UI thread.");
+ const logTarget = document.querySelector('#test-output');
+ logClass = function(cssClass,...args){
const ln = document.createElement('div');
if(cssClass) ln.classList.add(cssClass);
ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
- document.body.append(ln);
+ logTarget.append(ln);
};
}else{ /* Worker thread */
console.log("Running Worker thread.");
- logHtml = function(cssClass,...args){
+ logClass = function(cssClass,...args){
postMessage({
type:'log',
payload:{cssClass, args: normalizeArgs(args)}
}
const log = (...args)=>{
//console.log(...args);
- logHtml('',...args);
+ logClass('',...args);
}
const warn = (...args)=>{
console.warn(...args);
- logHtml('warning',...args);
+ logClass('warning',...args);
}
const error = (...args)=>{
console.error(...args);
- logHtml('error',...args);
+ logClass('error',...args);
};
const toss = (...args)=>{
error(...args);
throw new Error(args.join(' '));
};
+ const tossQuietly = (...args)=>{
+ throw new Error(args.join(' '));
+ };
/**
Helpers for writing sqlite3-specific tests.
/** Running total of the number of tests run via
this API. */
counter: 0,
+ /* Separator line for log messages. */
+ separator: '------------------------------------------------------------',
/**
If expr is a function, it is called and its result
is returned, coerced to a bool, else expr, coerced to
if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
return this;
},
- TestGroup: class {
- constructor(name){
+ eqApprox: (v1,v2,factor=0.05)=>(v1>=(v2-factor) && v1<=(v2+factor)),
+ TestGroup: (function(){
+ let groupCounter = 0;
+ const TestGroup = function(name, predicate){
+ this.number = ++groupCounter;
this.name = name;
+ this.predicate = predicate;
this.tests = [];
- }
- push(name,callback){
- }
- }/*TestGroup*/,
+ };
+ TestGroup.prototype = {
+ addTest: function(testObj){
+ this.tests.push(testObj);
+ return this;
+ },
+ run: async function(sqlite3){
+ if(this.predicate && !this.predicate()){
+ log("SKIPPING test group #"+this.number,this.name);
+ return;
+ }
+ log(TestUtil.separator);
+ logClass('group-start',"Group #"+this.number+':',this.name);
+ const indent = '....';
+ const assertCount = TestUtil.counter;
+ const groupState = Object.create(null);
+ const skipped = [];
+ for(let i in this.tests){
+ const t = this.tests[i];
+ const n = this.number+"."+i;
+ if(t.predicate && !t.predicate()){
+ logClass('warning',indent, n+': SKIPPING',t.name);
+ skipped.push( n+': '+t.name );
+ }else{
+ const tc = TestUtil.counter
+ log(indent, n+":", t.name);
+ await t.test.call(groupState, sqlite3);
+ //log(indent, indent, 'assertion count:',TestUtil.counter - tc);
+ }
+ }
+ logClass('green',
+ "Group #"+this.number,"assertion count:",(TestUtil.counter - assertCount));
+ if(skipped.length){
+ log("SKIPPED test(s) in group",this.number+":",skipped);
+ }
+ }
+ };
+ return TestGroup;
+ })()/*TestGroup*/,
testGroups: [],
currentTestGroup: undefined,
- addGroup: function(name){
- if(this.testGroups[name]){
- toss("Test group already exists:",name);
- }
+ addGroup: function(name, predicate){
this.testGroups.push( this.currentTestGroup = new this.TestGroup(name) );
return this;
},
addTest: function(name, callback){
- this.currentTestGroup.push(name, callback);
+ let predicate;
+ if(1===arguments.length){
+ const opt = arguments[0];
+ predicate = opt.predicate;
+ name = opt.name;
+ callback = opt.test;
+ }
+ this.currentTestGroup.addTest({
+ name, predicate, test: callback
+ });
+ return this;
},
- runTests: function(){
- toss("TODO: runTests()");
+ runTests: async function(sqlite3){
+ return new Promise(async function(pok,pnok){
+ try {
+ for(let g of this.testGroups){
+ await g.run(sqlite3);
+ }
+ log(TestUtil.separator);
+ log("Done running tests. Total assertion count:",TestUtil.counter);
+ pok();
+ }catch(e){
+ error(e);
+ pnok(e);
+ }
+ }.bind(this));
}
}/*TestUtil*/;
-
+ const T = TestUtil;
+ T.g = T.addGroup;
+ T.t = T.addTest;
+ let capi, wasm/*assigned after module init*/;
+ ////////////////////////////////////////////////////////////////////////
+ // End of infrastructure setup. Now define the tests...
+ ////////////////////////////////////////////////////////////////////////
+ T.g('Basic sanity checks')
+ .t('Namespace object checks', function(sqlite3){
+ const wasmCtypes = wasm.ctype;
+ 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);
+ [ /* 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((k)=>T.assert('number' === typeof capi[k]));
+ [/* Spot-check a few of the WASM API methods. */
+ 'alloc', 'dealloc', 'installFunction'
+ ].forEach((k)=>T.assert(wasm[k] instanceof Function));
+
+ 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');
+
+ try {
+ throw new sqlite3.WasmAllocError;
+ }catch(e){
+ T.assert(e instanceof Error)
+ .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));
+ })
+
+ .t('sqlite3.capi.wasm.pstack', function(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;
+ 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);
+ }
+ }/*pstack tests*/)
+ ;/*end of basic sanity checks*/
+
+ ////////////////////////////////////////////////////////////////////////
+ T.g('sqlite3.oo1 sanity checks')
+ .t('Create db', function(sqlite3){
+ const db = this.db = new sqlite3.oo1.DB();
+ T.assert(Number.isInteger(db.pointer)).
+ mustThrowMatching(()=>db.pointer=1, /read-only/).
+ assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)).
+ assert('main'===db.dbName(0));
+
+ // Custom db error message handling via sqlite3_prepare_v2/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"));
+ })
+ .t('DB.Stmt sanity checks', function(S){
+ let pId;
+ let st = this.db.prepare(
+ new TextEncoder('utf-8').encode("select 3 as a")
+ );
+ //debug("statement =",st);
+ try {
+ T.assert(Number.isInteger(st.pointer))
+ .mustThrowMatching(()=>st.pointer=1, /read-only/)
+ .assert(1===this.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===this.db.openStatementCount());
+ })
+ .t('Table t', function(sqlite3){
+ const db = this.db;
+ 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 && haveJaccwabytTests()){
+ 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)));
+ }
+
+ let 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 {
+ 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('Scalar UDFs', function(sqlite3){
+ const db = this.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
+ });
+ 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(T.eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
+ assert(T.eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
+
+ 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]);
+ })
+
+ .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 = [];
+ 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]));
+ };
+ 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", ()=>{
+ tossQuietly("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"));
+ })
+
+ .t({
+ name: 'Jaccwabyt-specific int-pointer tests (if compiled in)',
+ predicate: haveJaccwabytTests,
+ test: function(){
+ const w = wasm, db = this.db;
+ 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){
+ 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);
+ }
+ }
+ }/* jaccwabyt-specific tests */)
+
+ .t('Close db', function(){
+ T.assert(this.db).assert(Number.isInteger(this.db.pointer));
+ wasm.exports.sqlite3_wasm_db_reset(this.db.pointer);
+ this.db.close();
+ T.assert(!this.db.pointer);
+ })
+ ;/* end of oo1 checks */
+
+ ////////////////////////////////////////////////////////////////////////
log("Loading and initializing sqlite3 WASM module...");
if(!isUIThread()){
importScripts("sqlite3.js");
printErr: error
}).then(function(sqlite3){
//console.log('sqlite3 =',sqlite3);
- log("Done initializing. Running tests...");
- try {
- TestUtil.runTests();
- }catch(e){
- error("Tests failed:",e.message);
+ log("Done initializing WASM/JS bits. Running tests...");
+ capi = sqlite3.capi;
+ wasm = capi.wasm;
+ 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.");
}
+ TestUtil.runTests(sqlite3);
});
})();