]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
JS SQLTestRunner can now run the Java impl's core-most sanity tests, missing only...
authorstephan <stephan@noemail.net>
Tue, 29 Aug 2023 20:01:01 +0000 (20:01 +0000)
committerstephan <stephan@noemail.net>
Tue, 29 Aug 2023 20:01:01 +0000 (20:01 +0000)
FossilOrigin-Name: 5e798369375ce1b0c9cdf831f835d931fbd562ff7b4db09a06d1bdca2ac1b975

ext/jni/src/org/sqlite/jni/tester/SQLTester.java
ext/wasm/SQLTester/SQLTester.mjs
ext/wasm/SQLTester/SQLTester.run.mjs
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/api/sqlite3-wasm.c
manifest
manifest.uuid

index f7550c8e46b337aa0baf6c4dcf221894ec2049e6..b49c61957fefc69fd53121becb3e1b1856961bd4 100644 (file)
@@ -181,7 +181,7 @@ public class SQLTester {
   private int nTestFile = 0;
   //! Number of scripts which were aborted.
   private int nAbortedScript = 0;
-  //! Per-script test counter.
+  //! Incremented by test case handlers
   private int nTest = 0;
   //! True to enable column name output from execSql()
   private boolean emitColNames;
@@ -582,6 +582,10 @@ public class SQLTester {
         }
       }
     }finally{
+      sqlite3_reset(stmt
+        /* In order to trigger an exception in the
+           INSERT...RETURNING locking scenario:
+           https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
       sqlite3_finalize(stmt);
     }
     if( 0!=rc && throwOnError ){
@@ -926,8 +930,8 @@ class RunCommand extends Command {
     final sqlite3 db = (1==argv.length)
       ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
     final String sql = t.takeInputBuffer();
-    int rc = t.execSql(db, false, ResultBufferMode.NONE,
-                       ResultRowMode.ONELINE, sql);
+    final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+                             ResultRowMode.ONELINE, sql);
     if( 0!=rc && t.isVerbose() ){
       String msg = sqlite3_errmsg(db);
       ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
@@ -950,8 +954,7 @@ class TableResultCommand extends Command {
     if( !body.endsWith("\n--end") ){
       ts.toss(argv[0], " must be terminated with --end.");
     }else{
-      int n = body.length();
-      body = body.substring(0, n-6);
+      body = body.substring(0, body.length()-6);
     }
     final String[] globs = body.split("\\s*\\n\\s*");
     if( globs.length < 1 ){
@@ -1240,14 +1243,15 @@ class TestScript {
     final int oldPB = cur.putbackPos;
     final int oldPBL = cur.putbackLineNo;
     final int oldLine = cur.lineNo;
-    final String rc = getLine();
-    cur.peekedPos = cur.pos;
-    cur.peekedLineNo = cur.lineNo;
-    cur.pos = oldPos;
-    cur.lineNo = oldLine;
-    cur.putbackPos = oldPB;
-    cur.putbackLineNo = oldPBL;
-    return rc;
+    try{ return getLine(); }
+    finally{
+      cur.peekedPos = cur.pos;
+      cur.peekedLineNo = cur.lineNo;
+      cur.pos = oldPos;
+      cur.lineNo = oldLine;
+      cur.putbackPos = oldPB;
+      cur.putbackLineNo = oldPBL;
+    }
   }
 
   /**
@@ -1374,11 +1378,10 @@ class TestScript {
     String line;
     while( (null != (line = peekLine())) ){
       checkForDirective(tester, line);
-      if( !isCommandLine(line, true) ){
+      if( isCommandLine(line, true) ) break;
+      else {
         sb.append(line).append("\n");
         consumePeeked();
-      }else{
-        break;
       }
     }
     line = sb.toString();
index 0461beb86fdde4587dad397c284a65d0a6247988..c7059ad1b7ea3131f302b6d5d1eb676666591ede 100644 (file)
@@ -73,7 +73,7 @@ class DbException extends SQLTesterException {
 
 class TestScriptFailed extends SQLTesterException {
   constructor(testScript, ...args){
-    super(testScript.getPutputPrefix(),': ',...args);
+    super(testScript.getOutputPrefix(),': ',...args);
   }
   isFatal() { return true; }
 }
@@ -103,6 +103,18 @@ 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" characters - we have to escape output if it contains any.
+  special: /[\x00-\x20\x22\x5c\x7b\x7d]/,
+  //! Either of '{' or '}'.
+  squiggly: /[{}]/
+});
+
 const Util = newObj({
   toss,
 
@@ -110,7 +122,11 @@ const Util = newObj({
     return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn);
   },
 
-  argvToString: (list)=>list.join(" "),
+  argvToString: (list)=>{
+    const m = [...list];
+    m.shift();
+    return m.join(" ")
+  },
 
   utf8Decode: function(arrayBuffer, begin, end){
     return __utf8Decoder.decode(
@@ -120,7 +136,10 @@ const Util = newObj({
     );
   },
 
-  utf8Encode: (str)=>__utf8Encoder.encode(str)
+  utf8Encode: (str)=>__utf8Encoder.encode(str),
+
+  strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int',
+                              ['string','string'])
 })/*Util*/;
 
 class Outer {
@@ -182,21 +201,39 @@ class Outer {
 
 class SQLTester {
 
+  //! Console output utility.
   #outer = new Outer().outputPrefix( ()=>'SQLTester: ' );
+  //! List of input script files.
   #aFiles = [];
+  //! Test input buffer.
   #inputBuffer = [];
+  //! Test result buffer.
   #resultBuffer = [];
+  //! Output representation of SQL NULL.
   #nullView = "nil";
-  #metrics = newObj({
-    nTotalTest: 0, nTestFile: 0, nAbortedScript: 0
+  metrics = newObj({
+    //! Total tests run
+    nTotalTest: 0,
+    //! Total test script files run
+    nTestFile: 0,
+    //! Number of scripts which were aborted
+    nAbortedScript: 0,
+    //! Incremented by test case handlers
+    nTest: 0
   });
   #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];
     }
@@ -208,12 +245,17 @@ class SQLTester {
   outln(...args){ return this.#outer.outln(...args); }
   out(...args){ return this.#outer.out(...args); }
 
+  incrementTestCounter(){
+    ++this.metrics.nTotalTest;
+    ++this.metrics.nTest;
+  }
+
   reset(){
     this.clearInputBuffer();
     this.clearResultBuffer();
     this.#clearBuffer(this.#db.initSql);
     this.closeAllDbs();
-    this.nTest = 0;
+    this.metrics.nTest = 0;
     this.nullView = "nil";
     this.emitColNames = false;
     this.#db.iCurrentDb = 0;
@@ -365,7 +407,7 @@ class SQLTester {
       Util.unlink(this.#db.initialDbName);
       this.openDb(0, this.#db.initialDbName, true);
     }else{
-      this.#outer.outln("WARNING: setupInitialDb() unexpectedly ",
+      this.#outer.outln("WARNING: setupInitialDb() was unexpectedly ",
                         "triggered while it is opened.");
     }
   }
@@ -405,17 +447,107 @@ class SQLTester {
   #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) ){
+    if( '{' === msg.charAt(0) ){
       sb.push(msg);
     }else{
       sb.push('{', msg, '}');
     }
   }
 
+  #checkDbRc(pDb,rc){
+    sqlite3.oo1.DB.checkRc(pDb, rc);
+  }
+
   execSql(pDb, throwOnError, appendMode, lineMode, sql){
-    sql = sqlite3.capi.sqlite3_js_sql_to_string(sql);
-    this.#outer.outln("execSql() is TODO. ",sql);
-    return 0;
+    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
+      : new TextEncoder("utf-8").encode(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(pDb, rc);
+          }else if( sb ){
+            self.#appendDbErr(db, 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 === lineMode ){
+            spacing = 0;
+            sb.push('\n');
+          }
+        }else{ // no output but possibly other side effects
+          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(db, sb, rc);
+          }
+          break;
+        }
+      }/* SQL script loop */;
+    })/*scopedAllocCall()*/;
+    return rc;
   }
 
 }/*SQLTester*/
@@ -469,17 +601,6 @@ class Cursor {
   }
 }
 
-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]/,
-  //! Either of '{' or '}'.
-  squiggly: /[{}]/
-});
-
 class TestScript {
   #cursor = new Cursor();
   #moduleName = null;
@@ -529,6 +650,28 @@ class TestScript {
     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());
@@ -621,14 +764,16 @@ class TestScript {
     const oldPB = cur.putbackPos;
     const oldPBL = cur.putbackLineNo;
     const oldLine = cur.lineNo;
-    const rc = this.getLine();
-    cur.peekedPos = cur.pos;
-    cur.peekedLineNo = cur.lineNo;
-    cur.pos = oldPos;
-    cur.lineNo = oldLine;
-    cur.putbackPos = oldPB;
-    cur.putbackLineNo = oldPBL;
-    return rc;
+    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;
+    }
   }
 
 
@@ -667,7 +812,7 @@ class CloseDbCommand extends Command {
     let id;
     if(argv.length>1){
       const arg = argv[1];
-      if("all".equals(arg)){
+      if( "all" === arg ){
         t.closeAllDbs();
         return;
       }
@@ -697,6 +842,36 @@ class DbCommand extends Command {
   }
 }
 
+//! --glob command
+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);
+    //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 command
+class NotGlobCommand extends GlobCommand {
+  constructor(){super(true);}
+}
+
 //! --open command
 class OpenDbCommand extends Command {
   #createIfNeeded = false;
@@ -740,6 +915,107 @@ class PrintCommand extends Command {
   }
 }
 
+//! --result command
+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();
+    //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 command
+class JsonCommand extends ResultCommand {
+  constructor(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --run command
+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.verbose1(argv[0]," non-fatal command error #",rc,": ",
+                  msg,"\nfor SQL:\n",sql);
+    }
+  }
+}
+
+//! --tableresult command
+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);
+    log("TRC fetchCommandBody: ",body);
+    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);
+      log("TRC fetchCommandBody reshaped:",body);
+    }
+    const globs = body.split(/\s*\n\s*/);
+    if( globs.length < 1 ){
+      ts.toss(argv[0], " requires 1 or more ",
+              (this.#jsonMode ? "json snippets" : "globs"),".");
+    }
+    log("TRC fetchCommandBody globs:",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/);
+    log("TRC fetchCommandBody rbuf, res:",rbuf, res);
+    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 command
+class JsonBlockCommand extends TableResultCommand {
+  constructor(){ super(true); }
+}
 
 //! --testcase command
 class TestCaseCommand extends Command {
@@ -770,18 +1046,18 @@ class CommandDispatcher {
       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 "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 "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 "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;
     }
index dc8eaa0c171ce5476c2a93fc6e76dbfd12e2ab38..36d1ab5dcd878cc3a0e51d758d8ee7f20b83601e 100644 (file)
@@ -25,14 +25,46 @@ log("ns =",ns);
 out("Hi there. ").outln("SQLTester is ostensibly ready.");
 
 let ts = new ns.TestScript('/foo.test', ns.Util.utf8Encode(
-`# comment line
---print Starting up...
---null NIL
---new :memory:
---testcase 0.0.1
-select '0.0.1';
-#--result 0.0.1
---print done
+`
+--close all
+--oom
+--db 0
+--new my.db
+--null zilch
+--testcase 1.0
+SELECT 1, null;
+--result 1 zilch
+--glob *zil*
+--notglob *ZIL*
+SELECT 1, 2;
+intentional error;
+--run
+--testcase json-1
+SELECT json_array(1,2,3)
+--json [1,2,3]
+--testcase tableresult-1
+  select 1, 'a';
+  select 2, 'b';
+--tableresult
+  # [a-z]
+  2 b
+--end
+--testcase json-block-1
+  select json_array(1,2,3);
+  select json_object('a',1,'b',2);
+--json-block
+  [1,2,3]
+  {"a":1,"b":2}
+--end
+--testcase col-names-on
+--column-names 1
+  select 1 as 'a', 2 as 'b';
+--result a 1 b 2
+--testcase col-names-off
+--column-names 0
+  select 1 as 'a', 2 as 'b';
+--result 1 2
+--close
 `));
 
 const sqt = new ns.SQLTester();
@@ -41,11 +73,12 @@ try{
   sqt.openDb('/foo.db', true);
   log( 'sqt.getCurrentDb()', sqt.getCurrentDb() );
   sqt.verbosity(0);
-  affirm( 'NIL' !== sqt.nullValue() );
+  affirm( 'zilch' !== sqt.nullValue() );
   ts.run(sqt);
-  affirm( 'NIL' === sqt.nullValue() );
+  affirm( 'zilch' === sqt.nullValue() );
 }finally{
   sqt.reset();
 }
 log( 'sqt.getCurrentDb()', sqt.getCurrentDb() );
+log( "Metrics:", sqt.metrics );
 
index 35c856269d336edef9f4a0c519b6e3469bd976b2..996f23298f97c98810ddbd7403c1bc1b6900cc3d 100644 (file)
@@ -1139,15 +1139,15 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
 
     /**
        Records the current pstack position, calls the given function,
-       and restores the pstack regardless of whether the function
-       throws. Returns the result of the call or propagates an
-       exception on error.
+       passing it the sqlite3 object, then restores the pstack
+       regardless of whether the function throws. Returns the result
+       of the call or propagates an exception on error.
 
        Added in 3.44.
     */
     call: function(f){
       const stackPos = wasm.pstack.pointer;
-      try{ return f() }finally{
+      try{ return f(sqlite3) } finally{
         wasm.pstack.restore(stackPos);
       }
     }
index ff15e3b4fb163016d5e0128b659c5e42097e977d..db77010d95706652776c237a7867e28cc6c4c9fd 100644 (file)
@@ -1801,6 +1801,118 @@ char * sqlite3_wasm_test_str_hello(int fail){
   }
   return s;
 }
+
+/*
+** For testing using SQLTester scripts.
+**
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+**         zero == no match
+**     non-zero == match
+**
+** Globbing rules:
+**
+**      '*'       Matches any sequence of zero or more characters.
+**
+**      '?'       Matches exactly one character.
+**
+**     [...]      Matches one character from the enclosed list of
+**                characters.
+**
+**     [^...]     Matches one character not in the enclosed list.
+**
+**      '#'       Matches any sequence of one or more digits with an
+**                optional + or - sign in front, or a hexadecimal
+**                literal of the form 0x...
+*/
+static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
+  int c, c2;
+  int invert;
+  int seen;
+  typedef int (*recurse_f)(const char *,const char *);
+  static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob;
+
+  while( (c = (*(zGlob++)))!=0 ){
+    if( c=='*' ){
+      while( (c=(*(zGlob++))) == '*' || c=='?' ){
+        if( c=='?' && (*(z++))==0 ) return 0;
+      }
+      if( c==0 ){
+        return 1;
+      }else if( c=='[' ){
+        while( *z && recurse(zGlob-1,z)==0 ){
+          z++;
+        }
+        return (*z)!=0;
+      }
+      while( (c2 = (*(z++)))!=0 ){
+        while( c2!=c ){
+          c2 = *(z++);
+          if( c2==0 ) return 0;
+        }
+        if( recurse(zGlob,z) ) return 1;
+      }
+      return 0;
+    }else if( c=='?' ){
+      if( (*(z++))==0 ) return 0;
+    }else if( c=='[' ){
+      int prior_c = 0;
+      seen = 0;
+      invert = 0;
+      c = *(z++);
+      if( c==0 ) return 0;
+      c2 = *(zGlob++);
+      if( c2=='^' ){
+        invert = 1;
+        c2 = *(zGlob++);
+      }
+      if( c2==']' ){
+        if( c==']' ) seen = 1;
+        c2 = *(zGlob++);
+      }
+      while( c2 && c2!=']' ){
+        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+          c2 = *(zGlob++);
+          if( c>=prior_c && c<=c2 ) seen = 1;
+          prior_c = 0;
+        }else{
+          if( c==c2 ){
+            seen = 1;
+          }
+          prior_c = c2;
+        }
+        c2 = *(zGlob++);
+      }
+      if( c2==0 || (seen ^ invert)==0 ) return 0;
+    }else if( c=='#' ){
+      if( z[0]=='0'
+       && (z[1]=='x' || z[1]=='X')
+       && sqlite3Isxdigit(z[2])
+      ){
+        z += 3;
+        while( sqlite3Isxdigit(z[0]) ){ z++; }
+      }else{
+        if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+        if( !sqlite3Isdigit(z[0]) ) return 0;
+        z++;
+        while( sqlite3Isdigit(z[0]) ){ z++; }
+      }
+    }else{
+      if( c!=(*(z++)) ) return 0;
+    }
+  }
+  return *z==0;
+}
+
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
+ return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
+}
+
+
 #endif /* SQLITE_WASM_TESTS */
 
 #undef SQLITE_WASM_EXPORT
index 94540f2c28c4e7d1776cf386c406eea9cf1c1f1f..40829f5f94528cb020b2ce89c60b136d0d2c954a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Get\sthe\sJS\sSQLTester\scommand\shandlers\sin\splace\ssans\sthose\swhich\shave\sto\srun\sSQL.
-D 2023-08-29T15:39:57.155
+C JS\sSQLTestRunner\scan\snow\srun\sthe\sJava\simpl's\score-most\ssanity\stests,\smissing\sonly\ssupport\sfor\sdirectives.
+D 2023-08-29T20:01:01.586
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -283,7 +283,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38
 F ext/jni/src/org/sqlite/jni/sqlite3_context.java 66ca95ce904044263a4aff684abe262d56f73e6b06bca6cf650761d79d7779ad
 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
 F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a
-F ext/jni/src/org/sqlite/jni/tester/SQLTester.java c8a9f20694e66f4d7ed677cd6d1f0d829f802c347a1f413ac2446c62e4cba23d
+F ext/jni/src/org/sqlite/jni/tester/SQLTester.java a9f4b9e12109645b21fef15807973706dd958aad9fe1c835693fcb8e95abe949
 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
 F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d
 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
@@ -548,8 +548,8 @@ F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34ce
 F ext/wasm/GNUmakefile 0e362f3fc04eab6628cbe4f1e35f4ab4a200881f6b5f753b27fb45eabeddd9d2
 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576
 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193
-F ext/wasm/SQLTester/SQLTester.mjs 90fc3d2eb831afed237c18b78c22b8871d8f855a742715ebee571a60b9fcd98e
-F ext/wasm/SQLTester/SQLTester.run.mjs 30a459ec400495cc52f1d693703f1629e141947a19eaf868a8e4c1fd3ef2a114
+F ext/wasm/SQLTester/SQLTester.mjs 345736d970dc56e2c1041f8583fc602eedd8a64d455864f312db7d3208e640ea
+F ext/wasm/SQLTester/SQLTester.run.mjs 2dfa1407f5f188dadafe6f21f7a6740b4f07d59c594781a01eedadec16b2ddfe
 F ext/wasm/SQLTester/index.html 88d87e3ccbc33e7ab3773a8e48c1172e876951c4be31d1307c3700671262cddf
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab
 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b
@@ -563,14 +563,14 @@ F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057af
 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e
 F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3
 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8
-F ext/wasm/api/sqlite3-api-prologue.js 7fe51f06cd855634cb3765f830393f544fb532ead1cf95b5de3dd0befc81b92d
+F ext/wasm/api/sqlite3-api-prologue.js 723908946bd624d367e4df7093e9a6c9725606dc526953ea601cad8d7ce88538
 F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec
 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379
 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25
 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 561463ac5380e4ccf1839a1922e6d7a5585660f32e3b9701a270b78cd35566cf
 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js d9e62d42b86f7bb3143eb071628b24e2ba7dcc749e41a0e9d3e2451bfea1a6b6
-F ext/wasm/api/sqlite3-wasm.c 6773e949034369ddd2a1efdedc39b2808a10b7274b0769188905432e561feebe
+F ext/wasm/api/sqlite3-wasm.c 65d60439671e24d50d9119ca805ac1c68fb36129e164377eb46f8d037bd88b07
 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f
 F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75
 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
@@ -2111,8 +2111,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 8fcc2a553c1e26734902bbdee0c38183ee22b7b5c75f07405529bb79db34145a
-R 8ed29d2cdb1f79e88ba5ccad29a3151d
+P d21b1217964a53f33b7ba3958b34aa8560dff8ede33e66f54aa0afbab7099ec3
+R 7266768b4057594984eb0965145c2068
 U stephan
-Z 988ed72426998a455381761a89d498de
+Z 703bcb5450951150eb9347fe40faa521
 # Remove this line to create a well-formed Fossil manifest.
index 60048b2641b3f0a702f9c8590d3a2b29a0e23477..47297d57d235deca9000543900aa91514a2f9179 100644 (file)
@@ -1 +1 @@
-d21b1217964a53f33b7ba3958b34aa8560dff8ede33e66f54aa0afbab7099ec3
\ No newline at end of file
+5e798369375ce1b0c9cdf831f835d931fbd562ff7b4db09a06d1bdca2ac1b975
\ No newline at end of file