]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Rework how SQLTester's Command objects are dispatched and how TestScript stores its...
authorstephan <stephan@noemail.net>
Wed, 9 Aug 2023 11:05:43 +0000 (11:05 +0000)
committerstephan <stephan@noemail.net>
Wed, 9 Aug 2023 11:05:43 +0000 (11:05 +0000)
FossilOrigin-Name: f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de

ext/jni/src/org/sqlite/jni/tester/SQLTester.java
ext/jni/src/org/sqlite/jni/tester/TestScript.java
ext/jni/src/tests/000_first.test
manifest
manifest.uuid

index 277065a5c669610480aad0d978856954cd5445d8..e38a9877b40cf6cff62a948aec0e094104fb0deb 100644 (file)
@@ -124,11 +124,17 @@ public class SQLTester {
         final TestScript ts = new TestScript(f);
         currentScript = ts;
         outln("----->>>>> Test [",ts.getName(),"]");
-        try{
-          ts.run(this);
-        }catch(SkipTestRemainder e){
-          /* not an error */
-          ++nAbortedScript;
+        if( ts.isIgnored() ){
+          outln("WARNING: skipping [",ts.getName(),"] because it contains ",
+                "content which requires that it be skipped.");
+          continue;
+        }else{
+          try{
+            ts.run(this);
+          }catch(SkipTestRemainder e){
+            /* not an error */
+            ++nAbortedScript;
+          }
         }
         outln("<<<<<----- ",nTest," test(s) in [",f,"]");
       }
@@ -385,37 +391,55 @@ public class SQLTester {
    Base class for test script commands. It provides a set of utility
    APIs for concrete command implementations.
 
-   Each subclass must have a ctor with this signature:
+   Each subclass must have a public no-arg ctor and must implement
+   the process() method which is abstract in this class.
 
-   (SQLTester testContext, String[] argv, String content) throws Exception
+   Commands are intended to be stateless, except perhaps for counters
+   and similar internals. No state which changes the behavior between
+   any two invocations of process() should be retained.
+*/
+abstract class Command {
+  protected Command(){}
 
-   argv is a list with the command name followed by any
-   arguments to that command. The argcCheck() method provides
-   very basic argc validation.
+  /**
+     Must process one command-unit of work and either return
+     (on success) or throw (on error).
 
-   The content is any text content which was specified after the
-   command, or null if there is null. Any command which does not
-   permit content must pass that argument to affirmNoContent() in
-   their constructor. Similary, those which require content should
-   pass it to affirmHasContent().
+     The first argument is the context of the test.
 
-   For simplicity, instantiating the test is intended to execute it,
-   as opposed to delaying execution until a method devoted to that.
+     argv is a list with the command name followed by any arguments to
+     that command. The argcCheck() method from this class provides
+     very basic argc validation.
 
-   Tests must throw on error.
-*/
-class Command {
-  protected Command(){}
+     The content is any text content which was specified after the
+     command, or null if there is null. Any command which does not
+     permit content must pass that argument to affirmNoContent() in
+     their constructor (or perform an equivalent check). Similary,
+     those which require content must pass it to affirmHasContent()
+     (or equivalent).
+  */
+  public abstract void process(SQLTester tester, String[] argv, String content) 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(max<0) max = 99999999;
-    if(argc<min || argc>max){
+    if(argc<min || (max>=0 && argc>max)){
       if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)");
-      else Util.badArg(argv[0]," requires ",min,"-",max," arguments.");
+      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);
   }
@@ -436,7 +460,8 @@ class Command {
 }
 
 class CloseDbCommand extends Command {
-  public CloseDbCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public CloseDbCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,0,1);
     affirmNoContent(content);
     Integer id;
@@ -458,8 +483,10 @@ class CloseDbCommand extends Command {
   }
 }
 
+//! --db command
 class DbCommand extends Command {
-  public DbCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public DbCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmNoContent(content);
     final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) );
@@ -467,9 +494,13 @@ class DbCommand extends Command {
   }
 }
 
+//! --glob command
 class GlobCommand extends Command {
-  protected GlobCommand(boolean negate, SQLTester t,
-                        String[] argv, String content) throws Exception{
+  private boolean negate = false;
+  public GlobCommand(){}
+  protected GlobCommand(boolean negate){ this.negate = negate; }
+
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmNoContent(content);
 
@@ -487,13 +518,12 @@ class GlobCommand extends Command {
                 " glob mismatch: ",glob," vs input: ",result);
     }
   }
-  public GlobCommand(SQLTester t, String[] argv, String content) throws Exception{
-    this(false, t, argv, content);
-  }
 }
 
+//! --new command
 class NewDbCommand extends Command {
-  public NewDbCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public NewDbCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmNoContent(content);
     String fname = argv[1];
@@ -503,19 +533,24 @@ class NewDbCommand extends Command {
   }
 }
 
+//! Placeholder dummy/no-op command
 class NoopCommand extends Command {
-  public NoopCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public NoopCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
   }
 }
 
+//! --notglob command
 class NotGlobCommand extends GlobCommand {
-  public NotGlobCommand(SQLTester t, String[] argv, String content) throws Exception{
-    super(true, t, argv, content);
+  public NotGlobCommand(){
+    super(true);
   }
 }
 
+//! --null command
 class NullCommand extends Command {
-  public NullCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public NullCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmNoContent(content);
     t.setNullValue(argv[1]);
@@ -523,8 +558,10 @@ class NullCommand extends Command {
   }
 }
 
+//! --open command
 class OpenDbCommand extends Command {
-  public OpenDbCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public OpenDbCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmNoContent(content);
     String fname = argv[1];
@@ -533,16 +570,18 @@ class OpenDbCommand extends Command {
   }
 }
 
-
+//! --print command
 class PrintCommand extends Command {
-  public PrintCommand(SQLTester t, String[] argv, String content) throws Exception{
-    argcCheck(argv,0);
-    t.outln(content);
+  public PrintCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
+    if( argv.length > 1 ) t.outln("\t",Util.argvToString(argv));
+    if( null!=content ) t.outln(content.replaceAll("(?m)^", "\t"));
   }
 }
 
 class ResultCommand extends Command {
-  public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public ResultCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,0,-1);
     affirmNoContent(content);
     t.incrementTestCounter();
@@ -559,7 +598,8 @@ class ResultCommand extends Command {
 }
 
 class RunCommand extends Command {
-  public RunCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public RunCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,0,1);
     affirmHasContent(content);
     final sqlite3 db = (1==argv.length)
@@ -574,7 +614,8 @@ class RunCommand extends Command {
 }
 
 class TestCaseCommand extends Command {
-  public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{
+  public TestCaseCommand(){}
+  public void process(SQLTester t, String[] argv, String content) throws Exception{
     argcCheck(argv,1);
     affirmHasContent(content);
     // TODO: do something with the test name
@@ -586,28 +627,37 @@ class TestCaseCommand extends Command {
 
 class CommandDispatcher {
 
-  static Class getCommandByName(String name){
+  private static java.util.Map<String,Command> commandMap =
+    new java.util.HashMap<>();
+  static Command getCommandByName(String name){
+    // TODO? Do this dispatching using a custom annotation on
+    // Command impls. That requires a surprisingly huge amount
+    // of code, though.
+    Command rv = commandMap.get(name);
+    if( null!=rv ) return rv;
     switch(name){
-      case "close":    return CloseDbCommand.class;
-      case "db":       return DbCommand.class;
-      case "glob":     return GlobCommand.class;
-      case "new":      return NewDbCommand.class;
-      case "notglob":  return NotGlobCommand.class;
-      case "null":     return NullCommand.class;
-      case "oom":      return NoopCommand.class;
-      case "open":     return OpenDbCommand.class;
-      case "print":    return PrintCommand.class;
-      case "result":   return ResultCommand.class;
-      case "run":      return RunCommand.class;
-      case "testcase": return TestCaseCommand.class;
-      default: return null;
+      case "close":    rv = new CloseDbCommand(); break;
+      case "db":       rv = new DbCommand(); break;
+      case "glob":     rv = new GlobCommand(); 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 "testcase": rv = new TestCaseCommand(); break;
+      default: rv = null; break;
     }
+    if( null!=rv ) commandMap.put(name, rv);
+    return rv;
   }
 
   @SuppressWarnings("unchecked")
   static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{
-    final Class cmdClass = getCommandByName(argv[0]);
-    if(null == cmdClass){
+    final Command cmd = getCommandByName(argv[0]);
+    if(null == cmd){
       final TestScript ts = tester.getCurrentScript();
       if( tester.skipUnknownCommands() ){
         tester.outln("WARNING: skipping remainder of ",ts.getName(),
@@ -618,15 +668,8 @@ class CommandDispatcher {
                 "No command handler found for '"+argv[0]+"' in ",
                 ts.getName());
     }
-    final java.lang.reflect.Constructor<Command> ctor =
-      cmdClass.getConstructor(SQLTester.class, String[].class, String.class);
-    try{
-      //tester.verbose("Running ",argv[0]," with:\n", content);
-      ctor.newInstance(tester, argv, content);
-    }catch(java.lang.reflect.InvocationTargetException e){
-      Throwable t = e.getCause();
-      throw (t!=null && t instanceof Exception) ? (Exception)t : e;
-    }
+    //tester.verbose("Running ",argv[0]," with:\n", content);
+    cmd.process(tester, argv, content);
   }
 }
 
@@ -664,4 +707,5 @@ final class Util {
     }
     return sb.toString();
   }
+
 }
index f5a0134cc9bdd3b6f3fb1d860691c1456da681cd..96b372e8aeba192feda7d70e8cf1205e68c2df39 100644 (file)
@@ -18,26 +18,30 @@ import java.io.*;
 import java.util.regex.*;
 
 /**
-   This class represents a single test script. It handles (or delegates)
-   its input and parsing. Iteration and evalution are deferred to other,
-   as-yet-non-existent, classes.
-
+   This class represents a single test script. It handles (or
+   delegates) its the reading-in and parsing, but the details of
+   evaluation are delegated elsewhere.
 */
 class TestScript {
   private String name;
-  private String content;
-  private List<String> chunks = null;
+  private List<CommandChunk> chunks = null;
   private final Outer outer = new Outer();
   private boolean ignored = false;
 
+  /* One "chunk" of input, representing a single command and
+     its optional body content. */
+  private static final class CommandChunk {
+    public String[] argv = null;
+    public String content = null;
+  }
+
   private byte[] readFile(String filename) throws Exception {
     return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename));
   }
 
   private void setContent(String c){
-    content = c;
     ignored = shouldBeIgnored(c);
-    chunks = chunkContent();
+    if( !ignored ) chunks = chunkContent(c);
   }
   /**
      Initializes the script with the content of the given file.
@@ -104,7 +108,7 @@ class TestScript {
 
      If/when that becomes a problem, it can be refactored.
   */
-  private List<String> chunkContent(){
+  private List<CommandChunk> chunkContent(String content){
     if( ignored ) return null;
     // First, strip out any content which we know we can ignore...
     final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]";
@@ -117,53 +121,63 @@ class TestScript {
     lPats.add(sTclComment);
     lPats.add(sEmptyLine);
     //verbose("Content:").verbose(content).verbose("<EOF>");
-    String tmp = content;
     for( String s : lPats ){
       final Pattern p = Pattern.compile(
         s, Pattern.MULTILINE
       );
-      final Matcher m = p.matcher(tmp);
+      final Matcher m = p.matcher(content);
       /*verbose("Pattern {{{ ",p.pattern()," }}} with flags ",
               p.flags()," matches:"
               );*/
       int n = 0;
       //while( m.find() ) verbose("#",(++n),"\t",m.group(0).trim());
-      tmp = m.replaceAll("");
+      content = m.replaceAll("");
     }
     // Chunk the newly-cleaned text into individual commands and their input...
-    final List<String> rc = new ArrayList<>();
+    // First split up the input into command-size blocks...
+    final List<String> blocks = new ArrayList<>();
     final Pattern p = Pattern.compile(
       "^--(?!end)[a-z]+", Pattern.MULTILINE
       // --end is a marker used by --tableresult and --(not)glob.
     );
-    final Matcher m = p.matcher(tmp);
+    final Matcher m = p.matcher(content);
     int ndxPrev = 0, pos = 0, i = 0;
-    String chunk;
-    //verbose("Trimmed content:").verbose(tmp).verbose("<EOF>");
+    //verbose("Trimmed content:").verbose(content).verbose("<EOF>");
     while( m.find() ){
       pos = m.start();
-      chunk = tmp.substring(ndxPrev, pos).trim();
+      final String block = content.substring(ndxPrev, pos).trim();
       if( 0==ndxPrev && pos>ndxPrev ){
-        /* Initial chunk of non-command state. Skip it. */
+        /* Initial block of non-command state. Skip it. */
         ndxPrev = pos + 2;
         continue;
       }
-      if( !chunk.isEmpty() ){
+      if( !block.isEmpty() ){
         ++i;
-        //verbose("CHUNK #",i," ",+ndxPrev,"..",pos,chunk);
-        rc.add( chunk );
+        //verbose("BLOCK #",i," ",+ndxPrev,"..",pos,block);
+        blocks.add( block );
       }
       ndxPrev = pos + 2;
     }
-    if( ndxPrev < tmp.length() ){
+    if( ndxPrev < content.length() ){
       // This all belongs to the final command
-      chunk = tmp.substring(ndxPrev, tmp.length()).trim();
-      if( !chunk.isEmpty() ){
+      final String block = content.substring(ndxPrev, content.length()).trim();
+      if( !block.isEmpty() ){
         ++i;
-        //verbose("CHUNK #",(++i)," ",chunk);
-        rc.add( chunk );
+        //verbose("BLOCK #",(++i)," ",block);
+        blocks.add( block );
       }
     }
+    // Next, convert those blocks into higher-level CommandChunks...
+    final List<CommandChunk> rc = new ArrayList<>();
+    for( String block : blocks ){
+      final CommandChunk chunk = new CommandChunk();
+      final String[] parts = block.split("\\n", 2);
+      chunk.argv = parts[0].split("\\s+");
+      if( parts.length>1 && parts[1].length()>0 ){
+        chunk.content = parts[1];
+      }
+      rc.add( chunk );
+    }
     return rc;
   }
 
@@ -175,15 +189,10 @@ class TestScript {
     if( null==chunks ){
       outer.outln("This test contains content which forces it to be skipped.");
     }else{
-      int n = 0;
-      for(String chunk : chunks){
-        ++n;
-        //outer.verbose("CHUNK #",n," ",chunk,"<EOF>");
-        final String[] parts = chunk.split("\\n", 2);
-        final String[] argv = parts[0].split("\\s+");
-        CommandDispatcher.dispatch(
-          tester, argv, parts.length>1 ? parts[1] : null
-        );
+      //int n = 0;
+      for(CommandChunk chunk : chunks){
+        //outer.verbose("CHUNK #",++n," ",chunk,"<EOF>");
+        CommandDispatcher.dispatch(tester, chunk.argv, chunk.content);
       }
     }
   }
index 859a634ca0546392ee6fec6f8f4c6ee3c83932db..fe3d35b045e752925c014d178a7e5d3cf9c0ba07 100644 (file)
@@ -16,8 +16,9 @@ select 2;
 -- comment
 -- uncomment to introduce intentional syntax error
 --oom
---print
-This is from the print command.
+--print These are args to the print command.
+This is from the print command's body.
+Also from the print command.
 --- also ignored
 --testcase first
 select 'a', 'b';
@@ -31,6 +32,5 @@ select 'a'
 --notglob #
 --close
 --open SQLTester.db
---print
-Re-opened db.
+--print Re-opened db.
 --an-uknown-command
index 3d7601d397ba4d0a9208ac3b965af56cb870ccbe..eec0b5d2602689e58f0f74bbada6638166e9e22d 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Some\sminor\stweaks\sin\sSQLTester\sin\sprep\sfor\slarger\srefactoring.
-D 2023-08-09T09:56:37.905
+C Rework\show\sSQLTester's\sCommand\sobjects\sare\sdispatched\sand\show\sTestScript\sstores\sits\scommand\sentries.
+D 2023-08-09T11:05:43.390
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -266,10 +266,10 @@ 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 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01
-F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d771f9e08f229a6bab80283c4ab5197df5aba9aa09d30e34d13fdc3f35dcbca1
-F ext/jni/src/org/sqlite/jni/tester/TestScript.java e2000ce5db1f2ea23a417bcf6f2ce6ceb93415d81deefce44af5e29dcd7cef7c
+F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 3ab7c123fcd85932c0c5082419d20d284bd0520755b777bc68d6eb16950d35f9
+F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9d9e60cf62eb66d4c3b1567c03b84f5354c72605bf826d4375a6831ff53ba66b
 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c
-F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e
+F ext/jni/src/tests/000_first.test e4f643e631567a67a21cd9406c13b031580f9703079490480c7bbd1cae421183
 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd
 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
@@ -2090,8 +2090,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 4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad
-R 4e458d29aca7fb31fe732db9fc592362
+P 1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2
+R 51876d83c95ab59b5aaaafa4581c6850
 U stephan
-Z cd7ae073790f5058633c4b99263e9219
+Z ebf66076159eae05ac15c85362d3eee5
 # Remove this line to create a well-formed Fossil manifest.
index 8826aaa5d8a34583eb0d2b6d3ac3ab389abb0936..2092ba70d81fd81d817f4e5713e78215e3a0361b 100644 (file)
@@ -1 +1 @@
-1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2
\ No newline at end of file
+f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de
\ No newline at end of file