|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import sqlite3ApiInit from '/jswasm/sqlite3.mjs'; |
|
|
|
|
|
const sqlite3 = await sqlite3ApiInit(); |
|
|
|
|
|
const log = (...args)=>{ |
|
|
console.log('SQLTester:',...args); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tryInstallVfs = function f(vfsName){ |
|
|
if(f.vfsName) return false; |
|
|
const pVfs = sqlite3.capi.sqlite3_vfs_find(vfsName); |
|
|
if(pVfs){ |
|
|
log("Installing",'"'+vfsName+'"',"as default VFS."); |
|
|
const rc = sqlite3.capi.sqlite3_vfs_register(pVfs, 1); |
|
|
if(rc){ |
|
|
sqlite3.SQLite3Error.toss(rc,"While trying to register",vfsName,"vfs."); |
|
|
} |
|
|
f.vfsName = vfsName; |
|
|
} |
|
|
return !!pVfs; |
|
|
}; |
|
|
tryInstallVfs.vfsName = undefined; |
|
|
|
|
|
if( 0 && globalThis.WorkerGlobalScope ){ |
|
|
|
|
|
if( 1 && sqlite3.oo1.OpfsDb ){ |
|
|
|
|
|
tryInstallVfs("opfs"); |
|
|
}else if( sqlite3.installOpfsSAHPoolVfs ){ |
|
|
await sqlite3.installOpfsSAHPoolVfs({ |
|
|
clearOnInit: true, |
|
|
initialCapacity: 15, |
|
|
name: 'opfs-SQLTester' |
|
|
}).then(pool=>{ |
|
|
tryInstallVfs(pool.vfsName); |
|
|
}).catch(e=>{ |
|
|
log("OpfsSAHPool could not load:",e); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
const newE = ()=>Object.create(null); |
|
|
|
|
|
const newObj = (props)=>Object.assign(newE(), props); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ResultBufferMode = Object.assign(Object.create(null),{ |
|
|
|
|
|
NONE: newE(), |
|
|
|
|
|
ESCAPED: newE(), |
|
|
|
|
|
ASIS: newE() |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ResultRowMode = newObj({ |
|
|
|
|
|
ONLINE: newE(), |
|
|
|
|
|
NEWLINE: newE() |
|
|
}); |
|
|
|
|
|
class SQLTesterException extends globalThis.Error { |
|
|
constructor(testScript, ...args){ |
|
|
if(testScript){ |
|
|
super( [testScript.getOutputPrefix()+": ", ...args].join('') ); |
|
|
}else{ |
|
|
super( args.join('') ); |
|
|
} |
|
|
this.name = 'SQLTesterException'; |
|
|
} |
|
|
isFatal() { return false; } |
|
|
} |
|
|
|
|
|
SQLTesterException.toss = (...args)=>{ |
|
|
throw new SQLTesterException(...args); |
|
|
} |
|
|
|
|
|
class DbException extends SQLTesterException { |
|
|
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); |
|
|
} |
|
|
isFatal() { return true; } |
|
|
} |
|
|
|
|
|
class TestScriptFailed extends SQLTesterException { |
|
|
constructor(testScript, ...args){ |
|
|
super(testScript,...args); |
|
|
this.name = 'TestScriptFailed'; |
|
|
} |
|
|
isFatal() { return true; } |
|
|
} |
|
|
|
|
|
class UnknownCommand extends SQLTesterException { |
|
|
constructor(testScript, cmdName){ |
|
|
super(testScript, cmdName); |
|
|
this.name = 'UnknownCommand'; |
|
|
} |
|
|
isFatal() { return true; } |
|
|
} |
|
|
|
|
|
class IncompatibleDirective extends SQLTesterException { |
|
|
constructor(testScript, ...args){ |
|
|
super(testScript,...args); |
|
|
this.name = 'IncompatibleDirective'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const toss = (errType, ...args)=>{ |
|
|
throw new errType(...args); |
|
|
}; |
|
|
|
|
|
const __utf8Decoder = new TextDecoder(); |
|
|
const __utf8Encoder = new TextEncoder('utf-8'); |
|
|
|
|
|
const __SAB = ('undefined'===typeof globalThis.SharedArrayBuffer) |
|
|
? function(){} : globalThis.SharedArrayBuffer; |
|
|
|
|
|
|
|
|
|
|
|
const Rx = newObj({ |
|
|
requiredProperties: / REQUIRED_PROPERTIES:[ \t]*(\S.*)\s*$/, |
|
|
scriptModuleName: / SCRIPT_MODULE_NAME:[ \t]*(\S+)\s*$/, |
|
|
mixedModuleName: / ((MIXED_)?MODULE_NAME):[ \t]*(\S+)\s*$/, |
|
|
command: /^--(([a-z-]+)( .*)?)$/, |
|
|
|
|
|
special: /[\x00-\x20\x22\x5c\x7b\x7d]/, |
|
|
squiggly: /[{}]/ |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const Util = newObj({ |
|
|
toss, |
|
|
|
|
|
unlink: function f(fn){ |
|
|
if(!f.unlink){ |
|
|
f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int', |
|
|
['*','string']); |
|
|
} |
|
|
return 0==f.unlink(0,fn); |
|
|
}, |
|
|
|
|
|
argvToString: (list)=>{ |
|
|
const m = [...list]; |
|
|
m.shift() ; |
|
|
return m.join(" ") |
|
|
}, |
|
|
|
|
|
utf8Decode: function(arrayBuffer, begin, end){ |
|
|
return __utf8Decoder.decode( |
|
|
(arrayBuffer.buffer instanceof __SAB) |
|
|
? arrayBuffer.slice(begin, end) |
|
|
: arrayBuffer.subarray(begin, end) |
|
|
); |
|
|
}, |
|
|
|
|
|
utf8Encode: (str)=>__utf8Encoder.encode(str), |
|
|
|
|
|
strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int', |
|
|
['string','string']) |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Outer { |
|
|
#lnBuf = []; |
|
|
#verbosity = 0; |
|
|
#logger = console.log.bind(console); |
|
|
|
|
|
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.getOutputPrefix && !this.#lnBuf.length ){ |
|
|
this.#lnBuf.push(this.getOutputPrefix()); |
|
|
} |
|
|
this.#lnBuf.push(...args); |
|
|
return this; |
|
|
} |
|
|
|
|
|
#outlnImpl(vLevel, ...args){ |
|
|
if( this.getOutputPrefix && !this.#lnBuf.length ){ |
|
|
this.#lnBuf.push(this.getOutputPrefix()); |
|
|
} |
|
|
this.#lnBuf.push(...args,'\n'); |
|
|
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 |
|
|
? (this.getOutputPrefix() ?? '') : ''); |
|
|
}else{ |
|
|
this.getOutputPrefix = arguments[0]; |
|
|
return this; |
|
|
} |
|
|
} |
|
|
|
|
|
static #verboseLabel = ["🔈","🔊","📢"]; |
|
|
verboseN(lvl, args){ |
|
|
if( this.#verbosity>=lvl ){ |
|
|
this.#outlnImpl(lvl, Outer.#verboseLabel[lvl-1],': ',...args); |
|
|
} |
|
|
} |
|
|
verbose1(...args){ return this.verboseN(1,args); } |
|
|
verbose2(...args){ return this.verboseN(2,args); } |
|
|
verbose3(...args){ return this.verboseN(3,args); } |
|
|
|
|
|
verbosity(){ |
|
|
const rc = this.#verbosity; |
|
|
if(arguments.length) this.#verbosity = +arguments[0]; |
|
|
return rc; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
class SQLTester { |
|
|
|
|
|
|
|
|
#outer = new Outer().outputPrefix( ()=>'SQLTester: ' ); |
|
|
|
|
|
#aScripts = []; |
|
|
|
|
|
#inputBuffer = []; |
|
|
|
|
|
#resultBuffer = []; |
|
|
|
|
|
#nullView; |
|
|
metrics = newObj({ |
|
|
|
|
|
nTotalTest: 0, |
|
|
|
|
|
nTestFile: 0, |
|
|
|
|
|
nTest: 0, |
|
|
|
|
|
failedScripts: [] |
|
|
}); |
|
|
#emitColNames = false; |
|
|
|
|
|
#keepGoing = false; |
|
|
#db = newObj({ |
|
|
|
|
|
list: new Array(7), |
|
|
|
|
|
iCurrentDb: 0, |
|
|
|
|
|
initialDbName: "test.db", |
|
|
|
|
|
initSql: ['select 1;'], |
|
|
|
|
|
currentDb: function(){ |
|
|
return this.list[this.iCurrentDb]; |
|
|
} |
|
|
}); |
|
|
|
|
|
constructor(){ |
|
|
this.reset(); |
|
|
} |
|
|
|
|
|
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; |
|
|
++this.metrics.nTest; |
|
|
} |
|
|
|
|
|
reset(){ |
|
|
this.clearInputBuffer(); |
|
|
this.clearResultBuffer(); |
|
|
this.#clearBuffer(this.#db.initSql); |
|
|
this.closeAllDbs(); |
|
|
this.metrics.nTest = 0; |
|
|
this.#nullView = "nil"; |
|
|
this.#emitColNames = false; |
|
|
this.#db.iCurrentDb = 0; |
|
|
|
|
|
} |
|
|
|
|
|
appendInput(line, addNL){ |
|
|
this.#inputBuffer.push(line); |
|
|
if( addNL ) this.#inputBuffer.push('\n'); |
|
|
} |
|
|
appendResult(line, addNL){ |
|
|
this.#resultBuffer.push(line); |
|
|
if( addNL ) this.#resultBuffer.push('\n'); |
|
|
} |
|
|
appendDbInitSql(sql){ |
|
|
this.#db.initSql.push(sql); |
|
|
if( this.currentDb() ){ |
|
|
this.execSql(null, true, ResultBufferMode.NONE, null, sql); |
|
|
} |
|
|
} |
|
|
|
|
|
#runInitSql(pDb){ |
|
|
let rc = 0; |
|
|
for(const sql of this.#db.initSql){ |
|
|
this.#outer.verbose2("RUNNING DB INIT CODE: ",sql); |
|
|
rc = this.execSql(pDb, false, ResultBufferMode.NONE, null, sql); |
|
|
if( rc ) break; |
|
|
} |
|
|
return rc; |
|
|
} |
|
|
|
|
|
#clearBuffer(buffer){ |
|
|
buffer.length = 0; |
|
|
return buffer; |
|
|
} |
|
|
|
|
|
clearInputBuffer(){ return this.#clearBuffer(this.#inputBuffer); } |
|
|
clearResultBuffer(){return this.#clearBuffer(this.#resultBuffer); } |
|
|
|
|
|
getInputText(){ return this.#inputBuffer.join(''); } |
|
|
getResultText(){ return this.#resultBuffer.join(''); } |
|
|
|
|
|
#takeBuffer(buffer){ |
|
|
const s = buffer.join(''); |
|
|
buffer.length = 0; |
|
|
return s; |
|
|
} |
|
|
|
|
|
takeInputBuffer(){ |
|
|
return this.#takeBuffer(this.#inputBuffer); |
|
|
} |
|
|
takeResultBuffer(){ |
|
|
return this.#takeBuffer(this.#resultBuffer); |
|
|
} |
|
|
|
|
|
nullValue(){ |
|
|
return (0==arguments.length) |
|
|
? this.#nullView |
|
|
: (this.#nullView = ''+arguments[0]); |
|
|
} |
|
|
|
|
|
outputColumnNames(){ |
|
|
return (0==arguments.length) |
|
|
? this.#emitColNames |
|
|
: (this.#emitColNames = !!arguments[0]); |
|
|
} |
|
|
|
|
|
currentDbId(){ |
|
|
return (0==arguments.length) |
|
|
? this.#db.iCurrentDb |
|
|
: (this.#affirmDbId(arguments[0]).#db.iCurrentDb = arguments[0]); |
|
|
} |
|
|
|
|
|
#affirmDbId(id){ |
|
|
if(id<0 || id>=this.#db.list.length){ |
|
|
toss(SQLTesterException, "Database index ",id," is out of range."); |
|
|
} |
|
|
return this; |
|
|
} |
|
|
|
|
|
currentDb(...args){ |
|
|
if( 0!=args.length ){ |
|
|
this.#affirmDbId(id).#db.iCurrentDb = id; |
|
|
} |
|
|
return this.#db.currentDb(); |
|
|
} |
|
|
|
|
|
getDbById(id){ |
|
|
return this.#affirmDbId(id).#db.list[id]; |
|
|
} |
|
|
|
|
|
getCurrentDb(){ return this.#db.list[this.#db.iCurrentDb]; } |
|
|
|
|
|
|
|
|
closeDb(id) { |
|
|
if( 0==arguments.length ){ |
|
|
id = this.#db.iCurrentDb; |
|
|
} |
|
|
const pDb = this.#affirmDbId(id).#db.list[id]; |
|
|
if( pDb ){ |
|
|
sqlite3.capi.sqlite3_close_v2(pDb); |
|
|
this.#db.list[id] = null; |
|
|
} |
|
|
} |
|
|
|
|
|
closeAllDbs(){ |
|
|
for(let i = 0; i<this.#db.list.length; ++i){ |
|
|
if(this.#db.list[i]){ |
|
|
sqlite3.capi.sqlite3_close_v2(this.#db.list[i]); |
|
|
this.#db.list[i] = null; |
|
|
} |
|
|
} |
|
|
this.#db.iCurrentDb = 0; |
|
|
} |
|
|
|
|
|
openDb(name, createIfNeeded){ |
|
|
if( 3===arguments.length ){ |
|
|
const slot = arguments[0]; |
|
|
this.#affirmDbId(slot).#db.iCurrentDb = slot; |
|
|
name = arguments[1]; |
|
|
createIfNeeded = arguments[2]; |
|
|
} |
|
|
this.closeDb(); |
|
|
const capi = sqlite3.capi, wasm = sqlite3.wasm; |
|
|
let pDb = 0; |
|
|
let flags = capi.SQLITE_OPEN_READWRITE; |
|
|
if( createIfNeeded ) flags |= capi.SQLITE_OPEN_CREATE; |
|
|
try{ |
|
|
let rc; |
|
|
wasm.pstack.call(function(){ |
|
|
let ppOut = wasm.pstack.allocPtr(); |
|
|
rc = sqlite3.capi.sqlite3_open_v2(name, ppOut, flags, null); |
|
|
pDb = wasm.peekPtr(ppOut); |
|
|
}); |
|
|
let sql; |
|
|
if( 0==rc && this.#db.initSql.length > 0){ |
|
|
rc = this.#runInitSql(pDb); |
|
|
} |
|
|
if( 0!=rc ){ |
|
|
sqlite3.SQLite3Error.toss( |
|
|
rc, |
|
|
"sqlite3 result code",rc+":", |
|
|
(pDb ? sqlite3.capi.sqlite3_errmsg(pDb) |
|
|
: sqlite3.capi.sqlite3_errstr(rc)) |
|
|
); |
|
|
} |
|
|
return this.#db.list[this.#db.iCurrentDb] = pDb; |
|
|
}catch(e){ |
|
|
sqlite3.capi.sqlite3_close_v2(pDb); |
|
|
throw e; |
|
|
} |
|
|
} |
|
|
|
|
|
addTestScript(ts){ |
|
|
if( 2===arguments.length ){ |
|
|
ts = new TestScript(arguments[0], arguments[1]); |
|
|
}else if(ts instanceof Uint8Array){ |
|
|
ts = new TestScript('<unnamed>', ts); |
|
|
}else if('string' === typeof arguments[1]){ |
|
|
ts = new TestScript('<unnamed>', Util.utf8Encode(arguments[1])); |
|
|
} |
|
|
if( !(ts instanceof TestScript) ){ |
|
|
Util.toss(SQLTesterException, "Invalid argument type for addTestScript()"); |
|
|
} |
|
|
this.#aScripts.push(ts); |
|
|
return this; |
|
|
} |
|
|
|
|
|
runTests(){ |
|
|
this.outln("SQLite version ", sqlite3.capi.sqlite3_libversion()," with ", |
|
|
sqlite3.wasm.ptr.size, "-byte WASM pointers"); |
|
|
const tStart = (new Date()).getTime(); |
|
|
let isVerbose = this.verbosity(); |
|
|
this.metrics.failedScripts.length = 0; |
|
|
this.metrics.nTotalTest = 0; |
|
|
this.metrics.nTestFile = 0; |
|
|
for(const ts of this.#aScripts){ |
|
|
this.reset(); |
|
|
++this.metrics.nTestFile; |
|
|
let threw = false; |
|
|
const timeStart = (new Date()).getTime(); |
|
|
let msgTail = ''; |
|
|
try{ |
|
|
ts.run(this); |
|
|
}catch(e){ |
|
|
if(e instanceof SQLTesterException){ |
|
|
threw = true; |
|
|
this.outln("🔥EXCEPTION: ",e); |
|
|
this.metrics.failedScripts.push({script: ts.filename(), message:e.toString()}); |
|
|
if( this.#keepGoing ){ |
|
|
this.outln("Continuing anyway because of the keep-going option."); |
|
|
}else if( e.isFatal() ){ |
|
|
throw e; |
|
|
} |
|
|
}else{ |
|
|
throw e; |
|
|
} |
|
|
}finally{ |
|
|
const timeEnd = (new Date()).getTime(); |
|
|
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(); |
|
|
Util.unlink(this.#db.initialDbName); |
|
|
this.outln("Took ",(tEnd-tStart),"ms. Test count = ", |
|
|
this.metrics.nTotalTest,", script count = ", |
|
|
this.#aScripts.length,( |
|
|
this.metrics.failedScripts.length |
|
|
? ", failed scripts = "+this.metrics.failedScripts.length |
|
|
: "" |
|
|
) |
|
|
); |
|
|
return this; |
|
|
} |
|
|
|
|
|
#setupInitialDb(){ |
|
|
if( !this.#db.list[0] ){ |
|
|
Util.unlink(this.#db.initialDbName); |
|
|
this.openDb(0, this.#db.initialDbName, true); |
|
|
}else{ |
|
|
this.#outer.outln("WARNING: setupInitialDb() was unexpectedly ", |
|
|
"triggered while it is opened."); |
|
|
} |
|
|
} |
|
|
|
|
|
#escapeSqlValue(v){ |
|
|
if( !v ) return "{}"; |
|
|
if( !Rx.special.test(v) ){ |
|
|
return v ; |
|
|
} |
|
|
if( !Rx.squiggly.test(v) ){ |
|
|
return "{"+v+"}"; |
|
|
} |
|
|
const sb = ["\""]; |
|
|
const n = v.length; |
|
|
for(let i = 0; i < n; ++i){ |
|
|
const ch = v.charAt(i); |
|
|
switch(ch){ |
|
|
case '\\': sb.push("\\\\"); break; |
|
|
case '"': sb.push("\\\""); break; |
|
|
default:{ |
|
|
|
|
|
const ccode = ch.charCodeAt(i); |
|
|
if( ccode < 32 ) sb.push('\\',ccode.toString(8),'o'); |
|
|
else sb.push(ch); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
sb.push("\""); |
|
|
return sb.join(''); |
|
|
} |
|
|
|
|
|
#appendDbErr(pDb, sb, rc){ |
|
|
sb.push(sqlite3.capi.sqlite3_js_rc_str(rc), ' '); |
|
|
const msg = this.#escapeSqlValue(sqlite3.capi.sqlite3_errmsg(pDb)); |
|
|
if( '{' === msg.charAt(0) ){ |
|
|
sb.push(msg); |
|
|
}else{ |
|
|
sb.push('{', msg, '}'); |
|
|
} |
|
|
} |
|
|
|
|
|
#checkDbRc(pDb,rc){ |
|
|
sqlite3.oo1.DB.checkRc(pDb, rc); |
|
|
} |
|
|
|
|
|
execSql(pDb, throwOnError, appendMode, rowMode, sql){ |
|
|
if( !pDb && !this.#db.list[0] ){ |
|
|
this.#setupInitialDb(); |
|
|
} |
|
|
if( !pDb ) pDb = this.#db.currentDb(); |
|
|
const wasm = sqlite3.wasm, capi = sqlite3.capi; |
|
|
sql = (sql instanceof Uint8Array) |
|
|
? sql |
|
|
: Util.utf8Encode(capi.sqlite3_js_sql_to_string(sql)); |
|
|
const self = this; |
|
|
const sb = (ResultBufferMode.NONE===appendMode) ? null : this.#resultBuffer; |
|
|
let rc = 0; |
|
|
wasm.scopedAllocCall(function(){ |
|
|
let sqlByteLen = Number(sql.byteLength); |
|
|
const ppStmt = wasm.scopedAlloc( |
|
|
(2 * wasm.ptr.size) |
|
|
+ (sqlByteLen + 1) |
|
|
); |
|
|
const pzTail = wasm.ptr.add(ppStmt, wasm.ptr.size) ; |
|
|
let pSql = wasm.ptr.add(pzTail, wasm.ptr.size); |
|
|
const pSqlEnd = wasm.ptr.add(pSql, sqlByteLen); |
|
|
wasm.heap8().set(sql, Number(pSql)); |
|
|
wasm.poke8(pSqlEnd, 0); |
|
|
let pos = 0, n = 1, spacing = 0; |
|
|
while( pSql && wasm.peek8(pSql) ){ |
|
|
wasm.pokePtr([ppStmt, pzTail], 0); |
|
|
rc = capi.sqlite3_prepare_v3( |
|
|
pDb, pSql, sqlByteLen, 0, ppStmt, pzTail |
|
|
); |
|
|
if( 0!==rc ){ |
|
|
if(throwOnError){ |
|
|
throw new DbException(self, pDb, rc); |
|
|
}else if( sb ){ |
|
|
self.#appendDbErr(pDb, sb, rc); |
|
|
} |
|
|
break; |
|
|
} |
|
|
const pStmt = wasm.peekPtr(ppStmt); |
|
|
pSql = wasm.peekPtr(pzTail); |
|
|
sqlByteLen = Number(pSqlEnd - pSql); |
|
|
if(!pStmt) continue ; |
|
|
if( sb ){ |
|
|
const nCol = capi.sqlite3_column_count(pStmt); |
|
|
let colName, val; |
|
|
while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) { |
|
|
for( let i=0; i < nCol; ++i ){ |
|
|
if( spacing++ > 0 ) sb.push(' '); |
|
|
if( self.#emitColNames ){ |
|
|
colName = capi.sqlite3_column_name(pStmt, i); |
|
|
switch(appendMode){ |
|
|
case ResultBufferMode.ASIS: sb.push( colName ); break; |
|
|
case ResultBufferMode.ESCAPED: |
|
|
sb.push( self.#escapeSqlValue(colName) ); |
|
|
break; |
|
|
default: |
|
|
self.toss("Unhandled ResultBufferMode."); |
|
|
} |
|
|
sb.push(' '); |
|
|
} |
|
|
val = capi.sqlite3_column_text(pStmt, i); |
|
|
if( null===val ){ |
|
|
sb.push( self.#nullView ); |
|
|
continue; |
|
|
} |
|
|
switch(appendMode){ |
|
|
case ResultBufferMode.ASIS: sb.push( val ); break; |
|
|
case ResultBufferMode.ESCAPED: |
|
|
sb.push( self.#escapeSqlValue(val) ); |
|
|
break; |
|
|
} |
|
|
} |
|
|
if( ResultRowMode.NEWLINE === rowMode ){ |
|
|
spacing = 0; |
|
|
sb.push('\n'); |
|
|
} |
|
|
} |
|
|
}else{ |
|
|
while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {} |
|
|
} |
|
|
capi.sqlite3_finalize(pStmt); |
|
|
if( capi.SQLITE_ROW===rc || capi.SQLITE_DONE===rc) rc = 0; |
|
|
else if( rc!=0 ){ |
|
|
if( sb ){ |
|
|
self.#appendDbErr(pDb, sb, rc); |
|
|
} |
|
|
break; |
|
|
} |
|
|
}; |
|
|
}); |
|
|
return rc; |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
class Command { |
|
|
constructor(){ |
|
|
} |
|
|
|
|
|
process(sqlTester,testScript,argv){ |
|
|
SQLTesterException.toss("process() must be overridden"); |
|
|
} |
|
|
|
|
|
argcCheck(testScript,argv,min,max){ |
|
|
const argc = argv.length-1; |
|
|
if(argc<min || (max>=0 && argc>max)){ |
|
|
if( min==max ){ |
|
|
testScript.toss(argv[0]," requires exactly ",min," argument(s)"); |
|
|
}else if(max>0){ |
|
|
testScript.toss(argv[0]," requires ",min,"-",max," arguments."); |
|
|
}else{ |
|
|
testScript.toss(argv[0]," requires at least ",min," arguments."); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
class Cursor { |
|
|
src; |
|
|
sb = []; |
|
|
pos = 0; |
|
|
|
|
|
|
|
|
lineNo = 0 ; |
|
|
|
|
|
putbackPos = 0; |
|
|
|
|
|
putbackLineNo = 0; |
|
|
|
|
|
peekedPos = 0; |
|
|
|
|
|
peekedLineNo = 0; |
|
|
|
|
|
constructor(){ |
|
|
} |
|
|
|
|
|
|
|
|
rewind(){ |
|
|
this.sb.length = this.pos = this.lineNo |
|
|
= this.putbackPos = this.putbackLineNo |
|
|
= this.peekedPos = this.peekedLineNo = 0; |
|
|
} |
|
|
} |
|
|
|
|
|
class TestScript { |
|
|
#cursor = new Cursor(); |
|
|
#moduleName = null; |
|
|
#filename = null; |
|
|
#testCaseName = null; |
|
|
#outer = new Outer().outputPrefix( ()=>this.getOutputPrefix()+': ' ); |
|
|
|
|
|
constructor(...args){ |
|
|
let content, filename; |
|
|
if( 2 == args.length ){ |
|
|
filename = args[0]; |
|
|
content = args[1]; |
|
|
}else if( 1 == args.length ){ |
|
|
if(args[0] instanceof Object){ |
|
|
const o = args[0]; |
|
|
filename = o.name; |
|
|
content = o.content; |
|
|
}else{ |
|
|
content = args[0]; |
|
|
} |
|
|
} |
|
|
if(!(content instanceof Uint8Array)){ |
|
|
if('string' === typeof content){ |
|
|
content = Util.utf8Encode(content); |
|
|
}else if((content instanceof ArrayBuffer) |
|
|
||(content instanceof Array)){ |
|
|
content = new Uint8Array(content); |
|
|
}else{ |
|
|
toss(Error, "Invalid content type for TestScript constructor."); |
|
|
} |
|
|
} |
|
|
this.#filename = filename; |
|
|
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]); |
|
|
} |
|
|
filename(){ |
|
|
return (0==arguments.length) |
|
|
? this.#filename : (this.#filename = arguments[0]); |
|
|
} |
|
|
|
|
|
getOutputPrefix() { |
|
|
let rc = "["+(this.#moduleName || '<unnamed>')+"]"; |
|
|
if( this.#testCaseName ) rc += "["+this.#testCaseName+"]"; |
|
|
if( this.#filename ) rc += '['+this.#filename+']'; |
|
|
return rc + " line "+ this.#cursor.lineNo; |
|
|
} |
|
|
|
|
|
reset(){ |
|
|
this.#testCaseName = null; |
|
|
this.#cursor.rewind(); |
|
|
return this; |
|
|
} |
|
|
|
|
|
toss(...args){ |
|
|
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; |
|
|
} |
|
|
|
|
|
#checkRequiredProperties(tester, props){ |
|
|
if(true) return false; |
|
|
let nOk = 0; |
|
|
for(const rp of props){ |
|
|
this.verbose2("REQUIRED_PROPERTIES: ",rp); |
|
|
switch(rp){ |
|
|
case "RECURSIVE_TRIGGERS": |
|
|
tester.appendDbInitSql("pragma recursive_triggers=on;"); |
|
|
++nOk; |
|
|
break; |
|
|
case "TEMPSTORE_FILE": |
|
|
|
|
|
|
|
|
tester.appendDbInitSql("pragma temp_store=1;"); |
|
|
++nOk; |
|
|
break; |
|
|
case "TEMPSTORE_MEM": |
|
|
|
|
|
|
|
|
tester.appendDbInitSql("pragma temp_store=0;"); |
|
|
++nOk; |
|
|
break; |
|
|
case "AUTOVACUUM": |
|
|
tester.appendDbInitSql("pragma auto_vacuum=full;"); |
|
|
++nOk; |
|
|
break; |
|
|
case "INCRVACUUM": |
|
|
tester.appendDbInitSql("pragma auto_vacuum=incremental;"); |
|
|
++nOk; |
|
|
default: |
|
|
break; |
|
|
} |
|
|
} |
|
|
return props.length == nOk; |
|
|
} |
|
|
|
|
|
#checkForDirective(tester,line){ |
|
|
if(line.startsWith("#")){ |
|
|
throw new IncompatibleDirective(this, "C-preprocessor input: "+line); |
|
|
}else if(line.startsWith("---")){ |
|
|
throw new IncompatibleDirective(this, "triple-dash: ",line); |
|
|
} |
|
|
let m = Rx.scriptModuleName.exec(line); |
|
|
if( m ){ |
|
|
this.#moduleName = m[1]; |
|
|
return; |
|
|
} |
|
|
m = Rx.requiredProperties.exec(line); |
|
|
if( m ){ |
|
|
const rp = m[1]; |
|
|
if( !this.#checkRequiredProperties( tester, rp.split(/\s+/).filter(v=>!!v) ) ){ |
|
|
throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); |
|
|
} |
|
|
} |
|
|
|
|
|
m = Rx.mixedModuleName.exec(line); |
|
|
if( m ){ |
|
|
throw new IncompatibleDirective(this, m[1]+": "+m[3]); |
|
|
} |
|
|
if( line.indexOf("\n|")>=0 ){ |
|
|
throw new IncompatibleDirective(this, "newline-pipe combination."); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
#getCommandArgv(line){ |
|
|
const m = Rx.command.exec(line); |
|
|
return m ? m[1].trim().split(/\s+/) : null; |
|
|
} |
|
|
|
|
|
|
|
|
#isCommandLine(line, checkForImpl){ |
|
|
let m = Rx.command.exec(line); |
|
|
if( m && checkForImpl ){ |
|
|
m = !!CommandDispatcher.getCommandByName(m[2]); |
|
|
} |
|
|
return !!m; |
|
|
} |
|
|
|
|
|
fetchCommandBody(tester){ |
|
|
const sb = []; |
|
|
let line; |
|
|
while( (null !== (line = this.peekLine())) ){ |
|
|
this.#checkForDirective(tester, line); |
|
|
if( this.#isCommandLine(line, true) ) break; |
|
|
sb.push(line,"\n"); |
|
|
this.consumePeeked(); |
|
|
} |
|
|
line = sb.join(''); |
|
|
return !!line.trim() ? line : null; |
|
|
} |
|
|
|
|
|
run(tester){ |
|
|
this.reset(); |
|
|
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); |
|
|
this.#checkForDirective(tester, line); |
|
|
argv = this.#getCommandArgv(line); |
|
|
if( argv ){ |
|
|
this.#processCommand(tester, argv); |
|
|
continue; |
|
|
} |
|
|
tester.appendInput(line,true); |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
#processCommand(tester, argv){ |
|
|
this.verbose2("processCommand(): ",argv[0], " ", Util.argvToString(argv)); |
|
|
if(this.#outer.verbosity()>1){ |
|
|
const input = tester.getInputText(); |
|
|
this.verbose3("processCommand() input buffer = ",input); |
|
|
} |
|
|
CommandDispatcher.dispatch(tester, this, argv); |
|
|
} |
|
|
|
|
|
getLine(){ |
|
|
const cur = this.#cursor; |
|
|
if( cur.pos==cur.src.byteLength ){ |
|
|
return null; |
|
|
} |
|
|
cur.putbackPos = cur.pos; |
|
|
cur.putbackLineNo = cur.lineNo; |
|
|
cur.sb.length = 0; |
|
|
let b = 0, prevB = 0, i = cur.pos; |
|
|
let doBreak = false; |
|
|
let nChar = 0 ; |
|
|
const end = cur.src.byteLength; |
|
|
for(; i < end && !doBreak; ++i){ |
|
|
b = cur.src[i]; |
|
|
switch( b ){ |
|
|
case 13: continue; |
|
|
case 10: |
|
|
++cur.lineNo; |
|
|
if(cur.sb.length>0) doBreak = true; |
|
|
|
|
|
break; |
|
|
default:{ |
|
|
|
|
|
|
|
|
nChar = 1; |
|
|
switch( b & 0xF0 ){ |
|
|
case 0xC0: nChar = 2; break; |
|
|
case 0xE0: nChar = 3; break; |
|
|
case 0xF0: nChar = 4; break; |
|
|
default: |
|
|
if( b > 127 ) this.toss("Invalid character (#"+b+")."); |
|
|
break; |
|
|
} |
|
|
if( 1==nChar ){ |
|
|
cur.sb.push(String.fromCharCode(b)); |
|
|
}else{ |
|
|
const aChar = [] ; |
|
|
for(let x = 0; (x < nChar) && (i+x < end); ++x) aChar[x] = cur.src[i+x]; |
|
|
cur.sb.push( |
|
|
Util.utf8Decode( new Uint8Array(aChar) ) |
|
|
); |
|
|
i += nChar-1; |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
cur.pos = i; |
|
|
const rv = cur.sb.join(''); |
|
|
if( i==cur.src.byteLength && 0==rv.length ){ |
|
|
return null ; |
|
|
} |
|
|
return rv; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
peekLine(){ |
|
|
const cur = this.#cursor; |
|
|
const oldPos = cur.pos; |
|
|
const oldPB = cur.putbackPos; |
|
|
const oldPBL = cur.putbackLineNo; |
|
|
const oldLine = cur.lineNo; |
|
|
try { |
|
|
return this.getLine(); |
|
|
}finally{ |
|
|
cur.peekedPos = cur.pos; |
|
|
cur.peekedLineNo = cur.lineNo; |
|
|
cur.pos = oldPos; |
|
|
cur.lineNo = oldLine; |
|
|
cur.putbackPos = oldPB; |
|
|
cur.putbackLineNo = oldPBL; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
consumePeeked(){ |
|
|
const cur = this.#cursor; |
|
|
cur.pos = cur.peekedPos; |
|
|
cur.lineNo = cur.peekedLineNo; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
putbackLine(){ |
|
|
const cur = this.#cursor; |
|
|
cur.pos = cur.putbackPos; |
|
|
cur.lineNo = cur.putbackLineNo; |
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
class CloseDbCommand extends Command { |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,0,1); |
|
|
let id; |
|
|
if(argv.length>1){ |
|
|
const arg = argv[1]; |
|
|
if( "all" === arg ){ |
|
|
t.closeAllDbs(); |
|
|
return; |
|
|
} |
|
|
else{ |
|
|
id = parseInt(arg); |
|
|
} |
|
|
}else{ |
|
|
id = t.currentDbId(); |
|
|
} |
|
|
t.closeDb(id); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class ColumnNamesCommand extends Command { |
|
|
process( st, ts, argv ){ |
|
|
this.argcCheck(ts,argv,1); |
|
|
st.outputColumnNames( !!parseInt(argv[1]) ); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class DbCommand extends Command { |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,1); |
|
|
t.currentDbId( parseInt(argv[1]) ); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class GlobCommand extends Command { |
|
|
#negate = false; |
|
|
constructor(negate=false){ |
|
|
super(); |
|
|
this.#negate = negate; |
|
|
} |
|
|
|
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,1,-1); |
|
|
t.incrementTestCounter(); |
|
|
const sql = t.takeInputBuffer(); |
|
|
let rc = t.execSql(null, true, ResultBufferMode.ESCAPED, |
|
|
ResultRowMode.ONELINE, sql); |
|
|
const result = t.getResultText(); |
|
|
const sArgs = Util.argvToString(argv); |
|
|
|
|
|
const glob = Util.argvToString(argv); |
|
|
rc = Util.strglob(glob, result); |
|
|
if( (this.#negate && 0===rc) || (!this.#negate && 0!==rc) ){ |
|
|
ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class NotGlobCommand extends GlobCommand { |
|
|
constructor(){super(true);} |
|
|
} |
|
|
|
|
|
|
|
|
class OpenDbCommand extends Command { |
|
|
#createIfNeeded = false; |
|
|
constructor(createIfNeeded=false){ |
|
|
super(); |
|
|
this.#createIfNeeded = createIfNeeded; |
|
|
} |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,1); |
|
|
t.openDb(argv[1], this.#createIfNeeded); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class NewDbCommand extends OpenDbCommand { |
|
|
constructor(){ super(true); } |
|
|
} |
|
|
|
|
|
|
|
|
class NoopCommand extends Command { |
|
|
process(t, ts, argv){} |
|
|
} |
|
|
|
|
|
|
|
|
class NullCommand extends Command { |
|
|
process(st, ts, argv){ |
|
|
this.argcCheck(ts,argv,1); |
|
|
st.nullValue( argv[1] ); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class PrintCommand extends Command { |
|
|
process(st, ts, argv){ |
|
|
st.out(ts.getOutputPrefix(),': '); |
|
|
if( 1==argv.length ){ |
|
|
st.out( st.getInputText() ); |
|
|
}else{ |
|
|
st.outln( Util.argvToString(argv) ); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class ResultCommand extends Command { |
|
|
#bufferMode; |
|
|
constructor(resultBufferMode = ResultBufferMode.ESCAPED){ |
|
|
super(); |
|
|
this.#bufferMode = resultBufferMode; |
|
|
} |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,0,-1); |
|
|
t.incrementTestCounter(); |
|
|
const sql = t.takeInputBuffer(); |
|
|
|
|
|
t.execSql(null, false, this.#bufferMode, ResultRowMode.ONELINE, sql); |
|
|
const result = t.getResultText().trim(); |
|
|
const sArgs = argv.length>1 ? Util.argvToString(argv) : ""; |
|
|
if( result !== sArgs ){ |
|
|
t.outln(argv[0]," FAILED comparison. Result buffer:\n", |
|
|
result,"\nExpected result:\n",sArgs); |
|
|
ts.toss(argv[0]+" comparison failed."); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class JsonCommand extends ResultCommand { |
|
|
constructor(){ super(ResultBufferMode.ASIS); } |
|
|
} |
|
|
|
|
|
|
|
|
class RunCommand extends Command { |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,0,1); |
|
|
const pDb = (1==argv.length) |
|
|
? t.currentDb() : t.getDbById( parseInt(argv[1]) ); |
|
|
const sql = t.takeInputBuffer(); |
|
|
const rc = t.execSql(pDb, false, ResultBufferMode.NONE, |
|
|
ResultRowMode.ONELINE, sql); |
|
|
if( 0!==rc && t.verbosity()>0 ){ |
|
|
const msg = sqlite3.capi.sqlite3_errmsg(pDb); |
|
|
ts.verbose2(argv[0]," non-fatal command error #",rc,": ", |
|
|
msg,"\nfor SQL:\n",sql); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class TableResultCommand extends Command { |
|
|
#jsonMode; |
|
|
constructor(jsonMode=false){ |
|
|
super(); |
|
|
this.#jsonMode = jsonMode; |
|
|
} |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,0); |
|
|
t.incrementTestCounter(); |
|
|
let body = ts.fetchCommandBody(t); |
|
|
if( null===body ) ts.toss("Missing ",argv[0]," body."); |
|
|
body = body.trim(); |
|
|
if( !body.endsWith("\n--end") ){ |
|
|
ts.toss(argv[0], " must be terminated with --end\\n"); |
|
|
}else{ |
|
|
body = body.substring(0, body.length-6); |
|
|
} |
|
|
const globs = body.split(/\s*\n\s*/); |
|
|
if( globs.length < 1 ){ |
|
|
ts.toss(argv[0], " requires 1 or more ", |
|
|
(this.#jsonMode ? "json snippets" : "globs"),"."); |
|
|
} |
|
|
const sql = t.takeInputBuffer(); |
|
|
t.execSql(null, true, |
|
|
this.#jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, |
|
|
ResultRowMode.NEWLINE, sql); |
|
|
const rbuf = t.getResultText().trim(); |
|
|
const res = rbuf.split(/\r?\n/); |
|
|
if( res.length !== globs.length ){ |
|
|
ts.toss(argv[0], " failure: input has ", res.length, |
|
|
" row(s) but expecting ",globs.length); |
|
|
} |
|
|
for(let i = 0; i < res.length; ++i){ |
|
|
const glob = globs[i].replaceAll(/\s+/g," ").trim(); |
|
|
|
|
|
if( this.#jsonMode ){ |
|
|
if( glob!==res[i] ){ |
|
|
ts.toss(argv[0], " json <<",glob, ">> does not match: <<", |
|
|
res[i],">>"); |
|
|
} |
|
|
}else if( 0!=Util.strglob(glob, res[i]) ){ |
|
|
ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class JsonBlockCommand extends TableResultCommand { |
|
|
constructor(){ super(true); } |
|
|
} |
|
|
|
|
|
|
|
|
class TestCaseCommand extends Command { |
|
|
process(tester, script, argv){ |
|
|
this.argcCheck(script, argv,1); |
|
|
script.testCaseName(argv[1]); |
|
|
tester.clearResultBuffer(); |
|
|
tester.clearInputBuffer(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class VerbosityCommand extends Command { |
|
|
process(t, ts, argv){ |
|
|
this.argcCheck(ts,argv,1); |
|
|
ts.verbosity( parseInt(argv[1]) ); |
|
|
} |
|
|
} |
|
|
|
|
|
class CommandDispatcher { |
|
|
static map = newObj(); |
|
|
|
|
|
static getCommandByName(name){ |
|
|
let rv = CommandDispatcher.map[name]; |
|
|
if( rv ) return rv; |
|
|
switch(name){ |
|
|
case "close": rv = new CloseDbCommand(); break; |
|
|
case "column-names": rv = new ColumnNamesCommand(); break; |
|
|
case "db": rv = new DbCommand(); break; |
|
|
case "glob": rv = new GlobCommand(); break; |
|
|
case "json": rv = new JsonCommand(); break; |
|
|
case "json-block": rv = new JsonBlockCommand(); break; |
|
|
case "new": rv = new NewDbCommand(); break; |
|
|
case "notglob": rv = new NotGlobCommand(); break; |
|
|
case "null": rv = new NullCommand(); break; |
|
|
case "oom": rv = new NoopCommand(); break; |
|
|
case "open": rv = new OpenDbCommand(); break; |
|
|
case "print": rv = new PrintCommand(); break; |
|
|
case "result": rv = new ResultCommand(); break; |
|
|
case "run": rv = new RunCommand(); break; |
|
|
case "tableresult": rv = new TableResultCommand(); break; |
|
|
case "testcase": rv = new TestCaseCommand(); break; |
|
|
case "verbosity": rv = new VerbosityCommand(); break; |
|
|
} |
|
|
if( rv ){ |
|
|
CommandDispatcher.map[name] = rv; |
|
|
} |
|
|
return rv; |
|
|
} |
|
|
|
|
|
static dispatch(tester, testScript, argv){ |
|
|
const cmd = CommandDispatcher.getCommandByName(argv[0]); |
|
|
if( !cmd ){ |
|
|
toss(UnknownCommand,testScript,argv[0]); |
|
|
} |
|
|
cmd.process(tester, testScript, argv); |
|
|
} |
|
|
} |
|
|
|
|
|
const namespace = newObj({ |
|
|
Command, |
|
|
DbException, |
|
|
IncompatibleDirective, |
|
|
Outer, |
|
|
SQLTester, |
|
|
SQLTesterException, |
|
|
TestScript, |
|
|
TestScriptFailed, |
|
|
UnknownCommand, |
|
|
Util, |
|
|
sqlite3 |
|
|
}); |
|
|
|
|
|
export {namespace as default}; |
|
|
|