]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
More for the SQLTester rework. Can read input and dispatch commands, but only --print...
authorstephan <stephan@noemail.net>
Wed, 9 Aug 2023 22:18:22 +0000 (22:18 +0000)
committerstephan <stephan@noemail.net>
Wed, 9 Aug 2023 22:18:22 +0000 (22:18 +0000)
FossilOrigin-Name: 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314

ext/jni/src/org/sqlite/jni/tester/SQLTester.java
ext/jni/src/org/sqlite/jni/tester/TestScript.java
ext/jni/src/org/sqlite/jni/tester/TestScript2.java
ext/jni/src/tests/000-000-sanity.test2
manifest
manifest.uuid

index e93fd10a70911ff1fa46d21a70b4cb4dab9b048a..39047fa98efc1187af7b6c571ffb9243ab35c08e 100644 (file)
@@ -80,7 +80,6 @@ public class SQLTester {
   private int iCurrentDb = 0;
   private final String initialDbName = "test.db";
   private TestScript currentScript;
-  private TestScript2 currentScript2;
 
   public SQLTester(){
     reset();
@@ -159,23 +158,23 @@ public class SQLTester {
 
   //! Not yet funcional
   private void runTests2() throws Exception {
-    try {
-      for(String f : listInFiles){
-        reset();
-        setupInitialDb();
-        ++nTestFile;
-        final TestScript2 ts = new TestScript2(f);
-        currentScript2 = ts;
-        try{
-          ts.run(this);
-        }catch(SkipTestRemainder e){
-          /* not an error */
-          ++nAbortedScript;
-        }
-        outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename());
+    for(String f : listInFiles){
+      reset();
+      setupInitialDb();
+      ++nTestFile;
+      final TestScript2 ts = new TestScript2(f);
+      try{
+        ts.run(this);
+      }catch(SkipTestRemainder2 e){
+        /* not fatal */
+        outln(e);
+        ++nAbortedScript;
+      }catch(IncompatibleDirective e){
+        /* not fatal */
+        outln(e);
+        ++nAbortedScript;
       }
-    }finally{
-      currentScript2 = null;
+      outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename());
     }
     Util.unlink(initialDbName);
   }
@@ -195,6 +194,16 @@ public class SQLTester {
 
   StringBuilder getInputBuffer(){ return inputBuffer; }
 
+  void appendInput(String n, boolean addNL){
+    inputBuffer.append(n);
+    if(addNL) inputBuffer.append('\n');
+  }
+
+  void appendResult(String n, boolean addNL){
+    resultBuffer.append(n);
+    if(addNL) resultBuffer.append('\n');
+  }
+
   String getInputText(){ return inputBuffer.toString(); }
 
   String getResultText(){ return resultBuffer.toString(); }
index d1cdf1e238f79461aeefcf55618f739aa7c87f5c..742cf4fa52d1c595ced1ef2227c67be15364aafc 100644 (file)
@@ -90,7 +90,7 @@ class TestScript {
   }
 
   @SuppressWarnings("unchecked")
-  private <T> TestScript verbose(T... vals){
+  private TestScript verbose(Object... vals){
     outer.verbose(vals);
     return this;
   }
index 4c9c5bdb3bfe4ff93434b7f4d141cfad86f3f2d7..013c46cd12e45554f904038865a0d0274d421649 100644 (file)
 ** This file contains the TestScript2 part of the SQLTester framework.
 */
 package org.sqlite.jni.tester;
-//import java.util.regex.*;
 import java.util.Arrays;
 import java.nio.charset.StandardCharsets;
+import java.util.regex.*;
+
+class SQLTestException extends RuntimeException {
+  public SQLTestException(String msg){
+    super(msg);
+  }
+}
+
+class SkipTestRemainder2 extends SQLTestException {
+  public SkipTestRemainder2(TestScript2 ts){
+    super(ts.getOutputPrefix()+": skipping remainder");
+  }
+}
+
+class IncompatibleDirective extends SQLTestException {
+  public IncompatibleDirective(TestScript2 ts, String line){
+    super(ts.getOutputPrefix()+": incompatible directive: "+line);
+  }
+}
+
+class UnknownCommand extends SQLTestException {
+  public UnknownCommand(TestScript2 ts, String line){
+    super(ts.getOutputPrefix()+": unknown command: "+line);
+  }
+}
+
+abstract class Command2 {
+  protected Command2(){}
+
+  public abstract void process(
+    SQLTester st, TestScript2 ts, String[] argv
+  ) throws Exception;
+
+  /**
+     If argv.length-1 (-1 because the command's name is in argv[0]) does not
+     fall in the inclusive range (min,max) then this function throws. Use
+     a max value of -1 to mean unlimited.
+  */
+  protected final void argcCheck(String[] argv, int min, int max) throws Exception{
+    int argc = argv.length-1;
+    if(argc<min || (max>=0 && argc>max)){
+      if( min==max ){
+        Util.badArg(argv[0]," requires exactly ",min," argument(s)");
+      }else if(max>0){
+        Util.badArg(argv[0]," requires ",min,"-",max," arguments.");
+      }else{
+        Util.badArg(argv[0]," requires at least ",min," arguments.");
+      }
+    }
+  }
+
+  /**
+     Equivalent to argcCheck(argv,argc,argc).
+  */
+  protected final void argcCheck(String[] argv, int argc) throws Exception{
+    argcCheck(argv, argc, argc);
+  }
+}
+
+class PrintCommand2 extends Command2 {
+  public void process(
+    SQLTester st, TestScript2 ts, String[] argv
+  ) throws Exception{
+    st.out(ts.getOutputPrefix(),": ");
+    if( 1==argv.length ){
+      st.outln( st.getInputText() );
+    }else{
+      st.outln( Util.argvToString(argv) );
+    }
+    final String body = ts.fetchCommandBody();
+    if( null!=body ){
+      st.out(body,"\n");
+    }
+  }
+}
+
+class CommandDispatcher2 {
+
+  private static java.util.Map<String,Command2> commandMap =
+    new java.util.HashMap<>();
+
+  /**
+     Returns a (cached) instance mapped to name, or null if no match
+     is found.
+  */
+  static Command2 getCommandByName(String name){
+    Command2 rv = commandMap.get(name);
+    if( null!=rv ) return rv;
+    switch(name){
+      case "print":       rv = new PrintCommand2(); break;
+      default: rv = null; break;
+    }
+    if( null!=rv ) commandMap.put(name, rv);
+    return rv;
+  }
+
+  /**
+     Treats argv[0] as a command name, looks it up with
+     getCommandByName(), and calls process() on that instance, passing
+     it arguments given to this function.
+  */
+  static void dispatch(SQLTester tester, TestScript2 ts, String[] argv) throws Exception{
+    final Command2 cmd = getCommandByName(argv[0]);
+    if(null == cmd){
+      if( tester.skipUnknownCommands() ){
+        ts.warn("skipping remainder because of unknown command '",argv[0],"'.");
+        throw new SkipTestRemainder2(ts);
+      }
+      Util.toss(IllegalArgumentException.class,
+                ts.getOutputPrefix()+": no command handler found for '"+argv[0]+"'.");
+    }
+    cmd.process(tester, ts, argv);
+  }
+}
+
 
 /**
    This class represents a single test script. It handles (or
@@ -23,18 +137,23 @@ import java.nio.charset.StandardCharsets;
 */
 class TestScript2 {
   private String filename = null;
-  private final Cursor curs = new Cursor();
+  private String moduleName = null;
+  private final Cursor cur = new Cursor();
   private final Outer outer = new Outer();
 
   private static final class Cursor {
     private final StringBuilder sb = new StringBuilder();
     byte[] src = null;
     int pos = 0;
-    int lineNo = 1;
+    int putbackPos = 0;
+    int putbackLineNo = 0;
+    int lineNo = 0 /* yes, zero */;
+    int peekedPos = 0;
+    int peekedLineNo = 0;
     boolean inComment = false;
 
     void reset(){
-      sb.setLength(0); pos = 0; lineNo = 1; inComment = false;
+      sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false;
     }
   }
 
@@ -49,76 +168,102 @@ class TestScript2 {
   public TestScript2(String filename) throws Exception{
     this.filename = filename;
     setVerbosity(2);
-    curs.src = readFile(filename);
+    cur.src = readFile(filename);
   }
 
   public String getFilename(){
     return filename;
   }
 
+  public String getModuleName(){
+    return moduleName;
+  }
+
   public void setVerbosity(int level){
     outer.setVerbosity(level);
   }
 
+  public String getOutputPrefix(){
+    return "["+(moduleName==null ? filename : moduleName)+"] line "+
+      cur.lineNo;
+  }
+
   @SuppressWarnings("unchecked")
-  private <T> TestScript2 verbose(T... vals){
-    outer.verbose(vals);
+  private TestScript2 verbose(Object... vals){
+    final int verbosity = outer.getVerbosity();
+    if(verbosity>0){
+      outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),
+                getOutputPrefix(),": ");
+      outer.outln(vals);
+    }
+    return this;
+  }
+
+  @SuppressWarnings("unchecked")
+  public TestScript2 warn(Object... vals){
+    outer.out("WARNING ", getOutputPrefix(),": ");
+    outer.outln(vals);
     return this;
   }
 
   @SuppressWarnings("unchecked")
   private void tossSyntax(Object... msg){
     StringBuilder sb = new StringBuilder();
-    sb.append(this.filename).append(":").append(curs.lineNo).
+    sb.append(this.filename).append(":").append(cur.lineNo).
       append(": ");
     for(Object o : msg) sb.append(o);
     throw new RuntimeException(sb.toString());
   }
 
   private void reset(){
-    curs.reset();
+    cur.reset();
   }
 
+
   /**
      Returns the next line from the buffer, minus the trailing EOL.
-     If skipLeadingWs is true then all leading whitespace (including
-     blank links) is skipped over and will not appear in the resulting
-     string.
 
      Returns null when all input is consumed. Throws if it reads
      illegally-encoded input, e.g. (non-)characters in the range
      128-256.
   */
-  String getLine(boolean skipLeadingWs){
-    curs.sb.setLength(0);
+  String getLine(){
+    if( cur.pos==cur.src.length ){
+      return null /* EOF */;
+    }
+    cur.putbackPos = cur.pos;
+    cur.putbackLineNo = cur.lineNo;
+    cur.sb.setLength(0);
+    final boolean skipLeadingWs = false;
     byte b = 0, prevB = 0;
-    int i = curs.pos;
+    int i = cur.pos;
     if(skipLeadingWs) {
       /* Skip any leading spaces, including newlines. This will eliminate
          blank lines. */
-      for(; i < curs.src.length; ++i, prevB=b){
-        b = curs.src[i];
+      for(; i < cur.src.length; ++i, prevB=b){
+        b = cur.src[i];
         switch((int)b){
           case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue;
-          case 10/*NL*/: ++curs.lineNo; continue;
+          case 10/*NL*/: ++cur.lineNo; continue;
           default: break;
         }
         break;
       }
-    }
-    if( i==curs.src.length ){
-      return null /* EOF */;
+      if( i==cur.src.length ){
+        return null /* EOF */;
+      }
     }
     boolean doBreak = false;
     final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */;
     int nChar = 0 /* number of bytes in the char */;
-    for(; i < curs.src.length && !doBreak; ++i){
-      b = curs.src[i];
+    for(; i < cur.src.length && !doBreak; ++i){
+      b = cur.src[i];
       switch( (int)b ){
         case 13/*CR*/: continue;
         case 10/*NL*/:
-          ++curs.lineNo;
-          if(curs.sb.length()>0) doBreak = true;
+          ++cur.lineNo;
+          if(cur.sb.length()>0) doBreak = true;
+          // Else it's an empty string
           break;
         default:
           /* Multi-byte chars need to be gathered up and appended at
@@ -134,33 +279,161 @@ class TestScript2 {
               break;
           }
           if( 1==nChar ){
-            curs.sb.append((char)b);
+            cur.sb.append((char)b);
           }else{
-            for(int x = 0; x < nChar; ++x) aChar[x] = curs.src[i+x];
-            curs.sb.append(new String(Arrays.copyOf(aChar, nChar),
+            for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x];
+            cur.sb.append(new String(Arrays.copyOf(aChar, nChar),
                                       StandardCharsets.UTF_8));
             i += nChar-1;
           }
           break;
       }
     }
-    curs.pos = i;
-    if( 0==curs.sb.length() && i==curs.src.length ){
+    cur.pos = i;
+    final String rv = cur.sb.toString();
+    if( i==cur.src.length && 0==rv.length() ){
       return null /* EOF */;
     }
-    return curs.sb.toString();
+    return rv;
   }/*getLine()*/
 
+  /**
+     Fetches the next line then resets the cursor to its pre-call
+     state. consumePeeked() can be used to consume this peeked line
+     without having to re-parse it.
+  */
+  public String peekLine(){
+    final int oldPos = cur.pos;
+    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;
+  }
+
+  /**
+     Only valid after calling peekLine() and before calling getLine().
+     This places the cursor to the position it would have been at had
+     the peekLine() had been fetched with getLine().
+  */
+  public void consumePeeked(){
+    cur.pos = cur.peekedPos;
+    cur.lineNo = cur.peekedLineNo;
+  }
+
+  /**
+     Restores the cursor to the position it had before the previous
+     call to getLine().
+  */
+  public void putbackLine(){
+    cur.pos = cur.putbackPos;
+    cur.lineNo = cur.putbackLineNo;
+  }
+
+  private static final Pattern patternRequiredProperties =
+    Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(.*+)\\s*$");
+  private static final Pattern patternScriptModuleName =
+    Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
+  private static final Pattern patternMixedModuleName =
+    Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
+  private static final Pattern patternCommand =
+    Pattern.compile("^--(([a-z-]+)( .*)?)$");
+
+  /**
+     Looks for "directives." If a compatible one is found, it is
+     processed and this function returns. If an incompatible one is found,
+     a description of it is returned and processing of the test must
+     end immediately.
+  */
+  private void checkForDirective(String line) throws IncompatibleDirective {
+    if(line.startsWith("#")){
+      throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+    }else if(line.startsWith("---")){
+      new IncompatibleDirective(this, "Triple-dash: "+line);
+    }
+    Matcher m = patternScriptModuleName.matcher(line);
+    if( m.find() ){
+      moduleName = m.group(1);
+      return;
+    }
+    m = patternRequiredProperties.matcher(line);
+    if( m.find() ){
+      throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1));
+    }
+    m = patternMixedModuleName.matcher(line);
+    if( m.find() ){
+      throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3));
+    }
+    return;
+  }
+
+  public boolean isCommandLine(String line){
+    final Matcher m = patternCommand.matcher(line);
+    return m.find();
+  }
+
+  /**
+     If line looks like a command, returns an argv for that command
+     invocation, else returns null.
+  */
+  public String[] getCommandArgv(String line){
+    final Matcher m = patternCommand.matcher(line);
+    return m.find() ? m.group(1).trim().split("\\s+") : null;
+  }
+
+  /**
+     Fetches lines until the next command. Throws if
+     checkForDirective() does.  Returns null if there is no input or
+     it's only whitespace. The returned string is trim()'d of
+     leading/trailing whitespace.
+  */
+  public String fetchCommandBody(){
+    final StringBuilder sb = new StringBuilder();
+    String line;
+    while( (null != (line = peekLine())) ){
+      checkForDirective(line);
+      if( !isCommandLine(line) ){
+        sb.append(line).append("\n");
+        consumePeeked();
+      }else{
+        break;
+      }
+    }
+    line = sb.toString().trim();
+    return line.isEmpty() ? null : line;
+  }
+
+  public void processCommand(SQLTester t, String[] argv) throws Exception{
+    //verbose("got argv: ",argv[0], " ", Util.argvToString(argv));
+    //verbose("Input buffer = ",t.getInputBuffer());
+    CommandDispatcher2.dispatch(t, this, argv);
+  }
+
   /**
      Runs this test script in the context of the given tester object.
   */
   @SuppressWarnings("unchecked")
-  public void run(SQLTester tester) throws Exception {
+  public boolean run(SQLTester tester) throws Exception {
     reset();
     setVerbosity(tester.getVerbosity());
-    String line;
-    while( null != (line = getLine(false)) ){
-      verbose("LINE #",curs.lineNo-1,": ",line);
+    String line, directive;
+    String[] argv;
+    while( null != (line = getLine()) ){
+      //verbose(line);
+      checkForDirective(line);
+      argv = getCommandArgv(line);
+      if( null!=argv ){
+        processCommand(tester, argv);
+        continue;
+      }
+      tester.appendInput(line,true);
     }
+    return true;
   }
 }
index 24e1b68a14187ea1514ae8e6c979c75d698adde2..241305c0f893e3d2e7febae1a307316b59df3ffd 100644 (file)
@@ -1,10 +1,20 @@
   /*
 ** This is a comment. There are many like it but this one is mine.
 **
-  */
---command 1
+** SCRIPT_MODULE_NAME:      sanity-check
+** xMIXED_MODULE_NAME:       mixed-module
+** xMODULE_NAME:             module-name
+** xREQUIRED_PROPERTIES:      small fast reliable
+**
+*/
 
+/*#if foo*/
+/*---foo*/
+/* --print without flags dumps current input buffer */
+--print
 
---command ðŸ¤©ðŸ˜ƒ
+non-command/non-directive text after --print is also emitted.
+--print ðŸ¤©ðŸ˜ƒ
   SELECT 1;
   SELECT 2;
+--print the end
index e48221f32ffb99eaa125357c2db34b48e1bedc6c..93a495d6f2741fe36481a2ca42982dd8e2ee34f9 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Initial\ssketches\sof\sa\sline-by-line\sparser\sfor\sSQLTester\sto\sovercome\sits\scompatibility\sshortcomings.\sFar\sfrom\scomplete.
-D 2023-08-09T19:51:39.077
+C More\sfor\sthe\sSQLTester\srework.\sCan\sread\sinput\sand\sdispatch\scommands,\sbut\sonly\s--print\sis\scurrently\simplemented.
+D 2023-08-09T22:18:22.852
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -266,11 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859
 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/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b
-F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 35ea65f416b41b1e21119f58109e35518a39cbc1edbdd8883cb581772665c18d
-F ext/jni/src/org/sqlite/jni/tester/TestScript.java 18f55e1e3001c4ccfc359d57448729227c3eaf4a7c774964fe6418e07aefd541
-F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 1c8426039f2050cf623dab17e5d05a976ee061429c7be1eb5a3e73f7b00daf65
+F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76
+F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28
+F ext/jni/src/org/sqlite/jni/tester/TestScript2.java f01faf9facbc029f5baf731edf5de37cdd2921ec55ca4d8bd1e764c13211828f
 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba
-F ext/jni/src/tests/000-000-sanity.test2 0b4bb90e12cfa96e24999f5890ce46305b6a83efcf9ef0ff4df16521f636a9d5
+F ext/jni/src/tests/000-000-sanity.test2 8cb312a3dde4d09ead6a33a056ef685a99225b18635a07d03fa21d83879fb1c0
 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd
 F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
@@ -2092,8 +2092,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 f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c
-R e3dd73f0afa45739c2d0daea22589f05
+P 43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9
+R 02b26ab163237a92c9aeaab8dbe6493d
 U stephan
-Z 24fe8a8f37b2f666f1b6b1c10180bf33
+Z 4fb10f0d619801f5f787d45bc947ea2c
 # Remove this line to create a well-formed Fossil manifest.
index eaf5ea6314e185b3f8c0518c777caa9ca9d8b1c4..c6f2dd21f8ec8d6ca675faa8a4cc9575e285cc81 100644 (file)
@@ -1 +1 @@
-43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9
\ No newline at end of file
+4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314
\ No newline at end of file