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,"]");
}
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);
}
}
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;
}
}
+//! --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]) );
}
}
+//! --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);
" 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];
}
}
+//! 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]);
}
}
+//! --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];
}
}
-
+//! --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();
}
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)
}
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
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(),
"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);
}
}
}
return sb.toString();
}
+
}
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.
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 = "[/][*]([*](?![/])|[^*])*[*][/]";
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;
}
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);
}
}
}