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