Rework how SQLTester's Command objects are dispatched and how TestScript stores its command entries.

FossilOrigin-Name: f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de
This commit is contained in:
stephan
2023-08-09 11:05:43 +00:00
parent 23a35c2145
commit e6c29da777
5 changed files with 172 additions and 119 deletions
+115 -71
View 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
argv is a list with the command name followed by any
arguments to that command. The argcCheck() method provides
very basic argc validation.
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().
For simplicity, instantiating the test is intended to execute it,
as opposed to delaying execution until a method devoted to that.
Tests must throw on error.
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.
*/
class Command {
abstract class Command {
protected Command(){}
/**
Must process one command-unit of work and either return
(on success) or throw (on error).
The first argument is the context of the test.
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.
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();
}
}
@@ -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);
}
}
}
+4 -4
View 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
+8 -8
View File
@@ -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.
+1 -1
View File
@@ -1 +1 @@
1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2
f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de