From: stephan Date: Wed, 9 Aug 2023 11:05:43 +0000 (+0000) Subject: Rework how SQLTester's Command objects are dispatched and how TestScript stores its... X-Git-Tag: version-3.43.0~47^2~53 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e6c29da77795f079fca51856f61c701d2dca9795;p=thirdparty%2Fsqlite.git Rework how SQLTester's Command objects are dispatched and how TestScript stores its command entries. FossilOrigin-Name: f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de --- diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 277065a5c6..e38a9877b4 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -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(argcmax){ + if(argc=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 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 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(); } + } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index f5a0134cc9..96b372e8ae 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -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 chunks = null; + private List 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 chunkContent(){ + private List 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(""); - 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 rc = new ArrayList<>(); + // First split up the input into command-size blocks... + final List 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(""); + //verbose("Trimmed content:").verbose(content).verbose(""); 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 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,""); - 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,""); + CommandDispatcher.dispatch(tester, chunk.argv, chunk.content); } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 859a634ca0..fe3d35b045 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -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 diff --git a/manifest b/manifest index 3d7601d397..eec0b5d260 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index 8826aaa5d8..2092ba70d8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 \ No newline at end of file +f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de \ No newline at end of file