/*** 2023年08月29日**** The author disclaims copyright to this source code. In place of** a legal notice, here is a blessing:**** May you do good and not evil.** May you find forgiveness for yourself and forgive others.** May you share freely, never taking more than you give.***************************************************************************** This file contains the main application entry pointer for the JS** 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';const sqlite3 = await sqlite3ApiInit();const log = (...args)=>{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 ifvfs registration as the default VFS fails but has no side effectsif vfsName is not currently registered.*/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 ){// Try OPFS storage, if available...if( 0 && sqlite3.oo1.OpfsDb ){/* Really slow with these tests */tryInstallVfs("opfs");}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 wPost = (function(){return (('undefined'===typeof WorkerGlobalScope)? ()=>{}: (type, payload)=>{postMessage({type, payload});});})();//log("WorkerGlobalScope",globalThis.WorkerGlobalScope);// Return a new enum entry valueconst newE = ()=>Object.create(null);const newObj = (props)=>Object.assign(newE(), props);/**Modes for how to escape (or not) column values and names fromSQLTester.execSql() to the result buffer output.*/const ResultBufferMode = Object.assign(Object.create(null),{//! Do not append to result bufferNONE: newE(),//! Append output escaped.ESCAPED: newE(),//! Append output as-isASIS: newE()});/**Modes to specify how to emit multi-row output fromSQLTester.execSql() to the result buffer.*/const ResultRowMode = newObj({//! Keep all result rows on one line, space-separated.ONLINE: newE(),//! Add a newline between each result row.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';}}//! 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;/* Frequently-reused regexes. */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" characters - we have to escape output if it contains any.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() /* strip command name */;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'])})/*Util*/;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;}}/*Outer*/class SQLTester {//! Console output utility.#outer = new Outer().outputPrefix( ()=>'SQLTester: ' );//! List of input scripts.#aScripts = [];//! Test input buffer.#inputBuffer = [];//! Test result buffer.#resultBuffer = [];//! Output representation of SQL NULL.#nullView;metrics = newObj({//! Total tests runnTotalTest: 0,//! Total test script files runnTestFile: 0,//! Test-case count for to the current TestScriptnTest: 0,//! Names of scripts which were aborted.failedScripts: []});#emitColNames = false;//! True to keep going regardless of how a test fails.#keepGoing = false;#db = newObj({//! The list of available db handles.list: new Array(7),//! Index into this.list of the current db.iCurrentDb: 0,//! Name of the default db, re-created for each script.initialDbName: "test.db",//! Buffer for REQUIRED_PROPERTIES pragmas.initSql: ['select 1;'],//! (sqlite3*) to the current db.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;//this.#db.initSql.push("SELECT 1;");}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(){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 /* no escaping needed */;}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:{//verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));const ccode = ch.charCodeAt(i);if( ccode < 32 ) sb.push('\\',ccode.toString(8),'o');else sb.push(ch);break;}}}sb.append("\"");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 = sql.byteLength;const ppStmt = wasm.scopedAlloc(/* output (sqlite3_stmt**) arg and pzTail */(2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */));const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;let pSql = pzTail + wasm.ptrSizeof;const pSqlEnd = pSql + sqlByteLen;wasm.heap8().set(sql, pSql);wasm.poke8(pSql + sqlByteLen, 0/*NUL terminator*/);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 = pSqlEnd - pSql;if(!pStmt) continue /* only whitespace or comments */;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;}}/* column loop */}/* row loop */if( ResultRowMode.NEWLINE === rowMode ){spacing = 0;sb.push('\n');}}else{ // no output but possibly other side effectswhile( 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(db, sb, rc);}break;}}/* SQL script loop */;})/*scopedAllocCall()*/;return rc;}}/*SQLTester*/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;//! Current line number. Starts at 0 for internal reasons and will// line up with 1-based reality once parsing starts.lineNo = 0 /* yes, zero */;//! Putback value for this.pos.putbackPos = 0;//! Putback line numberputbackLineNo = 0;//! Peeked-to pos, used by peekLine() and consumePeeked().peekedPos = 0;//! Peeked-to line number.peekedLineNo = 0;constructor(){}//! Restore parsing state to the start of the stream.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":/* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,which we just happen to know is the case */tester.appendDbInitSql("pragma temp_store=1;");++nOk;break;case "TEMPSTORE_MEM":/* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,which we just happen to know is the case */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/*EOF*/;}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 /* number of bytes in the aChar char */;const end = cur.src.byteLength;for(; i < end && !doBreak; ++i){b = cur.src[i];switch( b ){case 13/*CR*/: continue;case 10/*NL*/:++cur.lineNo;if(cur.sb.length>0) doBreak = true;// Else it's an empty stringbreak;default:{/* Multi-byte chars need to be gathered up and appended atone time so that we can get them as string objects. */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 = [] /* multi-byte char buffer */;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 /* EOF */;}return rv;}/*getLine()*//**Fetches the next line then resets the cursor to its pre-callstate. consumePeeked() can be used to consume this peeked linewithout having to re-parse it.*/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;}}/**Only valid after calling peekLine() and before calling getLine().This places the cursor to the position it would have been at hadthe peekLine() had been fetched with getLine().*/consumePeeked(){const cur = this.#cursor;cur.pos = cur.peekedPos;cur.lineNo = cur.peekedLineNo;}/**Restores the cursor to the position it had before the previouscall to getLine().*/putbackLine(){const cur = this.#cursor;cur.pos = cur.putbackPos;cur.lineNo = cur.putbackLineNo;}}/*TestScript*/;//! --close commandclass 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);}}//! --column-names commandclass ColumnNamesCommand extends Command {process( st, ts, argv ){this.argcCheck(ts,argv,1);st.outputColumnNames( !!parseInt(argv[1]) );}}//! --db commandclass DbCommand extends Command {process(t, ts, argv){this.argcCheck(ts,argv,1);t.currentDbId( parseInt(argv[1]) );}}//! --glob commandclass 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);//t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);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);}}}//! --notglob commandclass NotGlobCommand extends GlobCommand {constructor(){super(true);}}//! --open commandclass 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);}}//! --new commandclass NewDbCommand extends OpenDbCommand {constructor(){ super(true); }}//! Placeholder dummy/no-op commandsclass NoopCommand extends Command {process(t, ts, argv){}}//! --null commandclass NullCommand extends Command {process(st, ts, argv){this.argcCheck(ts,argv,1);st.nullValue( argv[1] );}}//! --print commandclass 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) );}}}//! --result commandclass 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();//ts.verbose2(argv[0]," SQL =\n",sql);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.");}}}//! --json commandclass JsonCommand extends ResultCommand {constructor(){ super(ResultBufferMode.ASIS); }}//! --run commandclass 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);}}}//! --tableresult commandclass 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();//ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");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],">>");}}}}//! --json-block commandclass JsonBlockCommand extends TableResultCommand {constructor(){ super(true); }}//! --testcase commandclass TestCaseCommand extends Command {process(tester, script, argv){this.argcCheck(script, argv,1);script.testCaseName(argv[1]);tester.clearResultBuffer();tester.clearInputBuffer();}}//! --verbosity commandclass 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);}}/*CommandDispatcher*/const namespace = newObj({Command,DbException,IncompatibleDirective,Outer,SQLTester,SQLTesterException,TestScript,TestScriptFailed,UnknownCommand,Util,sqlite3});export {namespace as default};
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。