**
*************************************************************************
** This file contains the main application entry pointer for the JS
-** implementation of the SQLTester framework. This version is not well
-** documented because the one it's a direct port of is documented:
-** in the main SQLite3 source tree, see
-** ext/jni/src/org/sqlite/jni/tester/SQLite3Tester.java.
+** implementation of the SQLTester framework.
+**
+** This version is not well-documented because it's a direct port of
+** the Java immplementation, which is documented: in the main SQLite3
+** source tree, see ext/jni/src/org/sqlite/jni/tester/SQLite3Tester.java.
*/
import sqlite3ApiInit from '/jswasm/sqlite3.mjs';
console.log('SQLTester:',...args);
};
+/**
+ Try to install vfsName as the new default VFS. Once this succeeds
+ (returns true) then it becomes a no-op on future calls. Throws if
+ vfs registration as the default VFS fails but has no side effects
+ if vfsName is not currently registered.
+*/
const tryInstallVfs = function f(vfsName){
if(f.vfsName) return false;
const pVfs = sqlite3.capi.sqlite3_vfs_find(vfsName);
};
tryInstallVfs.vfsName = undefined;
-if( 1 ){
+if( 0 && globalThis.WorkerGlobalScope ){
// Try OPFS storage, if available...
- if(sqlite3.installOpfsSAHPoolVfs){
+ if( 0 && sqlite3.oo1.OpfsDb ){
+ /* Really slow with these tests */
+ tryInstallVfs("opfs");
+ }
+ if( sqlite3.installOpfsSAHPoolVfs ){
await sqlite3.installOpfsSAHPoolVfs({
clearOnInit: true,
initialCapacity: 15,
log("OpfsSAHPool could not load:",e);
});
}
- if(sqlite3.oo1.OpfsDb){
- tryInstallVfs("opfs");
- }
}
-const wPost = (type,...payload)=>{
- postMessage({type, payload});
-};
+const wPost = (function(){
+ return (('undefined'===typeof WorkerGlobalScope)
+ ? ()=>{}
+ : (type, payload)=>{
+ postMessage({type, payload});
+ });
+})();
+//log("WorkerGlobalScope",globalThis.WorkerGlobalScope);
// Return a new enum entry value
const newE = ()=>Object.create(null);
}
this.name = 'SQLTesterException';
}
+ /**
+ If this overrideable method returns false (the default) then
+ exceptions of that type are fatal to a whole test run, instead of
+ just the test which triggered it. If the the "keep going" flag
+ is set, this preference is ignored.
+ */
isFatal() { return false; }
}
}
class DbException extends SQLTesterException {
- constructor(testScript, pDb, rc, closeDb){
+ constructor(testScript, pDb, rc, closeDb=false){
super(testScript, "DB error #"+rc+": "+sqlite3.capi.sqlite3_errmsg(pDb));
this.name = 'DbException';
if( closeDb ) sqlite3.capi.sqlite3_close_v2(pDb);
}
}
+//! For throwing where an expression is required.
const toss = (errType, ...args)=>{
throw new errType(...args);
};
const __utf8Decoder = new TextDecoder();
const __utf8Encoder = new TextEncoder('utf-8');
+//! Workaround for Util.utf8Decode()
const __SAB = ('undefined'===typeof globalThis.SharedArrayBuffer)
? function(){} : globalThis.SharedArrayBuffer;
#verbosity = 0;
#logger = console.log.bind(console);
- constructor(){
+ constructor(func){
+ if(func) this.setFunc(func);
+ }
+
+ logger(...args){
+ if(args.length){
+ this.#logger = args[0];
+ return this;
+ }
+ return this.#logger;
}
out(...args){
- if(!this.#lnBuf.length && this.getOutputPrefix ){
+ if( this.getOutputPrefix && !this.#lnBuf.length ){
this.#lnBuf.push(this.getOutputPrefix());
}
this.#lnBuf.push(...args);
return this;
}
- outln(...args){
- if(!this.#lnBuf.length && this.getOutputPrefix ){
+
+ #outlnImpl(vLevel, ...args){
+ if( this.getOutputPrefix && !this.#lnBuf.length ){
this.#lnBuf.push(this.getOutputPrefix());
}
this.#lnBuf.push(...args,'\n');
- this.#logger(this.#lnBuf.join(''));
+ const msg = this.#lnBuf.join('');
this.#lnBuf.length = 0;
+ this.#logger(msg);
return this;
}
+ outln(...args){
+ return this.#outlnImpl(0,...args);
+ }
+
outputPrefix(){
if( 0==arguments.length ){
return (this.getOutputPrefix
}
}
- verboseN(lvl, argv){
+ verboseN(lvl, args){
if( this.#verbosity>=lvl ){
- this.outln('VERBOSE ',lvl,': ',...argv);
+ this.#outlnImpl(lvl,'VERBOSE ',lvl,': ',...args);
}
}
verbose1(...args){ return this.verboseN(1,args); }
verbose3(...args){ return this.verboseN(3,args); }
verbosity(){
- let rc;
- if(arguments.length){
- rc = this.#verbosity;
- this.#verbosity = arguments[0];
- }else{
- rc = this.#verbosity;
- }
+ const rc = this.#verbosity;
+ if(arguments.length) this.#verbosity = +arguments[0];
return rc;
}
nTestFile: 0,
//! Number of scripts which were aborted
nAbortedScript: 0,
- //! Incremented by test case handlers
+ //! Test-case count for to the current TestScript
nTest: 0
});
#emitColNames = false;
outln(...args){ return this.#outer.outln(...args); }
out(...args){ return this.#outer.out(...args); }
+ outer(...args){
+ if(args.length){
+ this.#outer = args[0];
+ return this;
+ }
+ return this.#outer;
+ }
+ verbose1(...args){ return this.#outer.verboseN(1,args); }
+ verbose2(...args){ return this.#outer.verboseN(2,args); }
+ verbose3(...args){ return this.#outer.verboseN(3,args); }
+ verbosity(...args){
+ const rc = this.#outer.verbosity(...args);
+ return args.length ? this : rc;
+ }
+ setLogger(func){
+ this.#outer.logger(func);
+ return this;
+ }
incrementTestCounter(){
++this.metrics.nTotalTest;
return this.#takeBuffer(this.#resultBuffer);
}
- verbosity(...args){ return this.#outer.verbosity(...args); }
-
nullValue(){
if( 0==arguments.length ){
return this.#nullView;
pDb = wasm.peekPtr(ppOut);
});
if( 0==rc && this.#db.initSql.length > 0){
- //this.#outer.verbose2("RUNNING DB INIT CODE: ",this.#db.initSql.toString());
+ this.#outer.verbose2("RUNNING DB INIT CODE: ",this.#db.initSql.toString());
rc = this.execSql(pDb, false, ResultBufferMode.NONE,
null, this.#db.initSql.join(''));
}
runTests(){
const tStart = (new Date()).getTime();
let isVerbose = this.verbosity();
+ this.metrics.nAbortedScript = 0;
+ this.metrics.nTotalTest = 0;
for(const ts of this.#aScripts){
this.reset();
++this.metrics.nTestFile;
const timeStart = (new Date()).getTime();
let msgTail = '';
try{
- if( isVerbose ){
- this.#outer.verbose1("Running ",ts.filename());
- }else{
- msgTail = ' '+ts.filename();
- }
ts.run(this);
}catch(e){
if(e instanceof SQLTesterException){
this.outln("🔥EXCEPTION: ",e);
++this.metrics.nAbortedScript;
if( this.#keepGoing ){
- this.outln("Continuing anyway becaure of the keep-going option.");
+ this.outln("Continuing anyway because of the keep-going option.");
+ }else if( e.isFatal() ){
+ throw e;
}
- else if( e.isFatal() ) throw e;
}else{
throw e;
}
}finally{
const timeEnd = (new Date()).getTime();
- this.outln("🏁", (threw ? "❌" : "✅"), " ", this.metrics.nTest,
- " test(s) in ", (timeEnd-timeStart),"ms.",msgTail);
+ this.out("🏁", (threw ? "❌" : "✅"), " ",
+ this.metrics.nTest, " test(s) in ",
+ (timeEnd-timeStart),"ms. ");
+ const mod = ts.moduleName();
+ if( mod ){
+ this.out( "[",mod,"] " );
+ }
+ this.outln(ts.filename());
}
}
const tEnd = (new Date()).getTime();
- this.outln("Total run-time: ",(tEnd-tStart),"ms");
Util.unlink(this.#db.initialDbName);
+ this.outln("Took ",(tEnd-tStart),"ms. test count = ",
+ this.metrics.nTotalTest,", script count = ",
+ this.#aScripts.length,(
+ this.metrics.nAbortedScript
+ ? ", aborted scripts = "+this.metrics.nAbortedScript
+ : ""
+ )
+ );
return this;
}
}
}
- /**
- Returns v or some escaped form of v, as defined in the tester's
- spec doc.
- */
#escapeSqlValue(v){
if( !v ) return "{}";
if( !Rx.special.test(v) ){
this.#cursor.src = content;
}
+ moduleName(){
+ return (0==arguments.length)
+ ? this.#moduleName : (this.#moduleName = arguments[0]);
+ }
+
testCaseName(){
return (0==arguments.length)
? this.#testCaseName : (this.#testCaseName = arguments[0]);
throw new TestScriptFailed(this,...args);
}
+ verbose1(...args){ return this.#outer.verboseN(1,args); }
+ verbose2(...args){ return this.#outer.verboseN(2,args); }
+ verbose3(...args){ return this.#outer.verboseN(3,args); }
+ verbosity(...args){
+ const rc = this.#outer.verbosity(...args);
+ return args.length ? this : rc;
+ }
+
#checkForDirective(tester,line){
if(line.startsWith("#")){
throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
run(tester){
this.reset();
- this.#outer.verbosity(tester.verbosity());
+ this.#outer.verbosity( tester.verbosity() );
+ this.#outer.logger( tester.outer().logger() );
let line, directive, argv = [];
while( null != (line = this.getLine()) ){
this.verbose3("run() input line: ",line);
cur.lineNo = cur.putbackLineNo;
}
- verbose1(...args){ return this.#outer.verboseN(1,args); }
- verbose2(...args){ return this.#outer.verboseN(2,args); }
- verbose3(...args){ return this.#outer.verboseN(3,args); }
- verbosity(...args){ return this.#outer.verbosity(...args); }
-
}/*TestScript*/;
//! --close command
<link rel="stylesheet" href="../common/testing.css"/>
<title>SQLTester</title>
</head>
+ <style>
+ fieldset {
+ display: flex;
+ flex-direction: row;
+ padding-right: 1em;
+ }
+ fieldset > :not(.legend) {
+ display: flex;
+ flex-direction: row;
+ padding-right: 1em;
+ }
+ </style>
<body>
- <p>SQLTester App.
+ <h1>SQLTester for JS/WASM</h1>
+ <p>This app reads in a build-time-defined set of SQLTester test
+ scripts and runs them through the test suite.
</p>
- <p>All stuff on this page happens in the dev console.</p>
- <hr>
+ <fieldset>
+ <legend>Options</legend>
+ <span class='input-wrapper'>
+ <input type='checkbox' id='cb-log-reverse' checked>
+ <label for='cb-log-reverse'>Reverse log order?</label>
+ </span>
+ <input type='button' id='btn-run-tests' value='Run tests'/>
+ </fieldset>
<div id='test-output'></div>
<!--script src='SQLTester.run.mjs' type='module'></script-->
<script>
- (function(){
- // Noting that Firefox can't do this yet.
+ (async function(){
const W = new Worker('SQLTester.run.mjs',{
type: 'module'
});
+ const wPost = (type,payload)=>W.postMessage({type,payload});
+ const mapToString = (v)=>{
+ switch(typeof v){
+ case 'string': return v;
+ case 'number': case 'boolean':
+ case 'undefined': case 'bigint':
+ return ''+v;
+ 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);
+ const logTarget = document.querySelector('#test-output');
+ const logClass = function(cssClass,...args){
+ const ln = document.createElement('div');
+ if(cssClass){
+ for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){
+ ln.classList.add(c);
+ }
+ }
+ ln.append(document.createTextNode(normalizeArgs(args).join(' ')));
+ logTarget.append(ln);
+ };
+ {
+ const cbReverse = document.querySelector('#cb-log-reverse');
+ const cbReverseKey = 'SQLTester:cb-log-reverse';
+ const cbReverseIt = ()=>{
+ logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
+ };
+ cbReverse.addEventListener('change', cbReverseIt, true);
+ cbReverseIt();
+ }
+
+ const btnRun = document.querySelector('#btn-run-tests');
+ const runTests = ()=>{
+ btnRun.setAttribute('disabled','disabled');
+ wPost('run-tests');
+ logTarget.innerText = 'Running tests...';
+ }
+ btnRun.addEventListener('click', runTests);
+ const log2 = function f(...args){
+ logClass('', ...args);
+ return f;
+ };
+ const log = function f(...args){
+ logClass('','index.html:',...args);
+ return f;
+ };
+ W.onmessage = function({data}){
+ switch(data.type){
+ case 'stdout': log2(data.payload.message); break;
+ case 'tests-end': btnRun.removeAttribute('disabled'); break;
+ case 'is-ready':
+ log("SQLTester.run.mjs is ready.");
+ runTests(); break;
+ default:
+ log("unhandled onmessage",data);
+ break;
+ }
+ };
+ //runTests()
+ /* Inexplicably, */
})();
</script>
</body>