From 0c6df29cba6b17a0c06c2b85f4899efdb4efde49 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 01:44:48 +0000 Subject: [PATCH] Move all of the SQLTester code into a single file, since it's only got 1 public class. Remove 'public' from many methods which don't need it. Add more documentation to it. FossilOrigin-Name: 2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 --- ext/jni/GNUmakefile | 6 +- ext/jni/src/org/sqlite/jni/tester/Outer.java | 60 -- .../src/org/sqlite/jni/tester/SQLTester.java | 799 +++++++++++++++++- .../src/org/sqlite/jni/tester/TestScript.java | 682 --------------- manifest | 16 +- manifest.uuid | 2 +- 6 files changed, 776 insertions(+), 789 deletions(-) delete mode 100644 ext/jni/src/org/sqlite/jni/tester/Outer.java delete mode 100644 ext/jni/src/org/sqlite/jni/tester/TestScript.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 9d78f9b0e6..8506014462 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -83,11 +83,7 @@ ifeq (1,$(enable.fts5)) TesterFts5.java \ ) endif -JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ - Outer.java \ - SQLTester.java \ - TestScript.java \ -) +JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java deleted file mode 100644 index e6f90ddf31..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -** 2023-08-08 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file contains a utility class for generating console output. -*/ -package org.sqlite.jni.tester; - -/** - Console output utility class. -*/ -class Outer { - public int verbosity = 0; - - public static void out(Object val){ - System.out.print(val); - } - - public static void outln(Object val){ - System.out.println(val); - } - - @SuppressWarnings("unchecked") - public static void out(Object... vals){ - for(Object v : vals) out(v); - } - - @SuppressWarnings("unchecked") - public static void outln(Object... vals){ - out(vals); - out("\n"); - } - - @SuppressWarnings("unchecked") - public Outer verbose(Object... vals){ - if(verbosity>0){ - out("VERBOSE",(verbosity>1 ? "+: " : ": ")); - outln(vals); - } - return this; - } - - public void setVerbosity(int level){ - verbosity = level; - } - - public int getVerbosity(){ - return verbosity; - } - - public boolean isVerbose(){return verbosity > 0;} - -} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index f2c5da3132..1e3443b718 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -20,12 +20,13 @@ import java.nio.charset.StandardCharsets; import java.util.regex.*; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; /** - Modes for how to handle SQLTester.execSql()'s - result output. - */ + Modes for how to escape (or not) column values and names from + SQLTester.execSql() to the result buffer output. +*/ enum ResultBufferMode { //! Do not append to result buffer NONE, @@ -35,6 +36,10 @@ enum ResultBufferMode { ASIS }; +/** + Modes to specify how to emit multi-row output from + SQLTester.execSql() to the result buffer. +*/ enum ResultRowMode { //! Keep all result rows on one line, space-separated. ONELINE, @@ -48,6 +53,71 @@ class SQLTesterException extends RuntimeException { } } +class TestScriptFailed extends SQLTesterException { + public TestScriptFailed(TestScript ts, String msg){ + super(ts.getOutputPrefix()+": "+msg); + } +} + +class UnknownCommand extends SQLTesterException { + public UnknownCommand(TestScript ts, String cmd){ + super(ts.getOutputPrefix()+": unknown command: "+cmd); + } +} + +class IncompatibleDirective extends SQLTesterException { + public IncompatibleDirective(TestScript ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line); + } +} + +/** + Console output utility class. +*/ +class Outer { + private int verbosity = 0; + + static void out(Object val){ + System.out.print(val); + } + + static void outln(Object val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + Outer out(Object... vals){ + for(Object v : vals) out(v); + return this; + } + + @SuppressWarnings("unchecked") + Outer outln(Object... vals){ + out(vals).out("\n"); + return this; + } + + @SuppressWarnings("unchecked") + Outer verbose(Object... vals){ + if(verbosity>0){ + out("VERBOSE",(verbosity>1 ? "+: " : ": ")); + outln(vals); + } + return this; + } + + void setVerbosity(int level){ + verbosity = level; + } + + int getVerbosity(){ + return verbosity; + } + + public boolean isVerbose(){return verbosity > 0;} + +} + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -64,45 +134,54 @@ public class SQLTester { private final StringBuilder inputBuffer = new StringBuilder(); //! Test result buffer. private final StringBuilder resultBuffer = new StringBuilder(); - private String nullView; + //! Output representation of SQL NULL. + private String nullView = "nil"; + //! Total tests run. private int nTotalTest = 0; + //! Total test script files run. private int nTestFile = 0; + //! Number of scripts which were aborted. private int nAbortedScript = 0; - private int nTest; + //! Per-script test counter. + private int nTest = 0; + //! True to enable column name output from execSql() private boolean emitColNames; + //! The list of available db handles. private final sqlite3[] aDb = new sqlite3[7]; + //! Index into aDb of the current db. private int iCurrentDb = 0; + //! Name of the default db, re-created for each script. private final String initialDbName = "test.db"; - private TestScript currentScript; + public SQLTester(){ reset(); } - public void setVerbosity(int level){ + void setVerbosity(int level){ this.outer.setVerbosity( level ); } - public int getVerbosity(){ + int getVerbosity(){ return this.outer.getVerbosity(); } - public boolean isVerbose(){ + boolean isVerbose(){ return this.outer.isVerbose(); } void outputColumnNames(boolean b){ emitColNames = b; } @SuppressWarnings("unchecked") - public void verbose(Object... vals){ + void verbose(Object... vals){ outer.verbose(vals); } @SuppressWarnings("unchecked") - public void outln(Object... vals){ + void outln(Object... vals){ outer.outln(vals); } @SuppressWarnings("unchecked") - public void out(Object... vals){ + void out(Object... vals){ outer.out(vals); } @@ -112,16 +191,12 @@ public class SQLTester { //verbose("Added file ",filename); } - public void setupInitialDb() throws Exception { + private void setupInitialDb() throws Exception { Util.unlink(initialDbName); openDb(0, initialDbName, true); } - TestScript getCurrentScript(){ - return currentScript; - } - - private void runTests() throws Exception { + public void runTests() throws Exception { for(String f : listInFiles){ reset(); setupInitialDb(); @@ -270,10 +345,12 @@ public class SQLTester { void incrementTestCounter(){ ++nTest; ++nTotalTest; } + //! "Special" characters - we have to escape output if it contains any. static final Pattern patternSpecial = Pattern.compile( - "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]", Pattern.MULTILINE + "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]" ); - static final Pattern patternSquiggly = Pattern.compile("[{}]", Pattern.MULTILINE); + //! Either of '{' or '}'. + static final Pattern patternSquiggly = Pattern.compile("[{}]"); /** Returns v or some escaped form of v, as defined in the tester's @@ -317,6 +394,18 @@ public class SQLTester { } } + /** + Runs SQL on behalf of test commands and outputs the results following + the very specific rules of the test framework. + + If db is null, getCurrentDb() is assumed. If throwOnError is true then + any db-side error will result in an exception, else they result in + the db's result code. + + appendMode specifies how/whether to append results to the result + buffer. lineMode specifies whether to output all results in a + single line or one line per row. + */ public int execSql(sqlite3 db, boolean throwOnError, ResultBufferMode appendMode, ResultRowMode lineMode, @@ -462,14 +551,19 @@ public class SQLTester { of digits, e.g. "#23" or "1#3", but will match at the end, e.g. "12#". */ - public static int strglob(String glob, String txt){ + static int strglob(String glob, String txt){ return strglob( (glob+"\0").getBytes(StandardCharsets.UTF_8), (txt+"\0").getBytes(StandardCharsets.UTF_8) ); } - private static native void installCustomExtensions(); + /** + Sets up C-side components needed by the test framework. This must + not be called until main() is triggered so that it does not + interfere with library clients who don't use this class. + */ + static native void installCustomExtensions(); static { System.loadLibrary("sqlite3-jni") /* Interestingly, when SQLTester is the main app, we have to @@ -486,7 +580,7 @@ public class SQLTester { final class Util { //! Throws a new T, appending all msg args into a string for the message. - public static void toss(Class errorType, Object... msg) throws Exception { + static void toss(Class errorType, Object... msg) throws Exception { StringBuilder sb = new StringBuilder(); for(Object s : msg) sb.append(s); final java.lang.reflect.Constructor ctor = @@ -494,16 +588,12 @@ final class Util { throw ctor.newInstance(sb.toString()); } - public static void toss(Object... msg) throws Exception{ + static void toss(Object... msg) throws Exception{ toss(RuntimeException.class, msg); } - public static void badArg(Object... msg) throws Exception{ - toss(IllegalArgumentException.class, msg); - } - //! Tries to delete the given file, silently ignoring failure. - public static void unlink(String filename){ + static void unlink(String filename){ try{ final java.io.File f = new java.io.File(filename); f.delete(); @@ -514,10 +604,10 @@ final class Util { /** Appends all entries in argv[1..end] into a space-separated - string, argv[0] is not included because it's expected to - be a command name. + string, argv[0] is not included because it's expected to be a + command name. */ - public static String argvToString(String[] argv){ + static String argvToString(String[] argv){ StringBuilder sb = new StringBuilder(); for(int i = 1; i < argv.length; ++i ){ if( i>1 ) sb.append(" "); @@ -527,3 +617,648 @@ final class Util { } } + +/** + Base class for test script commands. It provides a set of utility + APIs for concrete command implementations. + + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. + + Commands are intended to be stateless, except perhaps for counters + and similar internals. Specifically, no state which changes the + behavior between any two invocations of process() should be + retained. +*/ +abstract class Command { + protected Command(){} + + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first two arguments specify 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. + */ + public abstract void process( + SQLTester st, TestScript 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(TestScript ts, String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + ts.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + ts.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + ts.toss(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ + argcCheck(ts, argv, argc, argc); + } +} + +//! --close command +class CloseDbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); + } +} + +//! --db command +class DbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = Util.argvToString(argv); + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + public NewDbCommand(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(){ + super(true); + } +} + +//! --null command +class NullCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand extends Command { + private boolean createIfNeeded = false; + public OpenDbCommand(){} + protected OpenDbCommand(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command +class PrintCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + if( 1==argv.length ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + } +} + +//! --result command +class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + // TODO?: do something with the test name + t.clearResultBuffer(); + t.clearInputBuffer(); + } +} + +class CommandDispatcher2 { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command getCommandByName(String name){ + Command rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names":rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); 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 "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); 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, TestScript ts, String[] argv) throws Exception{ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ + throw new UnknownCommand(ts, argv[0]); + } + cmd.process(tester, ts, argv); + } +} + + +/** + 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 filename = null; + 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 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 = 0/*yes, zero*/; inComment = false; + } + } + + private byte[] readFile(String filename) throws Exception { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + Throws if it cannot read the file. + */ + public TestScript(String filename) throws Exception{ + this.filename = filename; + setVerbosity(2); + 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 TestScript verboseN(int level, Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>=level){ + outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), + getOutputPrefix(),": ") + .outln(vals); + } + return this; + } + + private TestScript verbose1(Object... vals){return verboseN(1,vals);} + private TestScript verbose2(Object... vals){return verboseN(2,vals);} + + @SuppressWarnings("unchecked") + public TestScript warn(Object... vals){ + outer.out("WARNING ", getOutputPrefix(),": ") + .outln(vals); + return this; + } + + private void reset(){ + cur.reset(); + } + + + /** + Returns the next line from the buffer, minus the trailing EOL. + + 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(){ + 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 = cur.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + 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*/: ++cur.lineNo; continue; + default: break; + } + break; + } + 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 < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++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 + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + cur.sb.append((char)b); + }else{ + 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; + } + } + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ + return null /* EOF */; + } + 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. + */ + 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(). + */ + void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + + private static final Pattern patternRequiredProperties = + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\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)); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + return; + } + + boolean isCommandLine(String line, boolean checkForImpl){ + final Matcher m = patternCommand.matcher(line); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); + } + return rc; + } + + /** + If line looks like a command, returns an argv for that command + invocation, else returns null. + */ + 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 recognized command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. + */ + String fetchCommandBody(){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(line); + if( !isCommandLine(line, true) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; + } + } + line = sb.toString(); + return line.trim().isEmpty() ? null : line; + } + + private void processCommand(SQLTester t, String[] argv) throws Exception{ + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose2("Input buffer = ",input); + } + CommandDispatcher2.dispatch(t, this, argv); + } + + void toss(Object... msg) throws TestScriptFailed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScriptFailed(this, sb.toString()); + } + + /** + Runs this test script in the context of the given tester object. + */ + @SuppressWarnings("unchecked") + public boolean run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + 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; + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java deleted file mode 100644 index c075ceb1a5..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ /dev/null @@ -1,682 +0,0 @@ -/* -** 2023-08-08 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file contains the TestScript part of the SQLTester framework. -*/ -package org.sqlite.jni.tester; -import static org.sqlite.jni.SQLite3Jni.*; -import org.sqlite.jni.sqlite3; -import java.util.Arrays; -import java.nio.charset.StandardCharsets; -import java.util.regex.*; - -class TestScriptFailed extends SQLTesterException { - public TestScriptFailed(TestScript ts, String msg){ - super(ts.getOutputPrefix()+": "+msg); - } -} - -class UnknownCommand extends SQLTesterException { - public UnknownCommand(TestScript ts, String cmd){ - super(ts.getOutputPrefix()+": unknown command: "+cmd); - } -} - -class IncompatibleDirective extends SQLTesterException { - public IncompatibleDirective(TestScript ts, String line){ - super(ts.getOutputPrefix()+": incompatible directive: "+line); - } -} - -/** - Base class for test script commands. It provides a set of utility - APIs for concrete command implementations. - - Each subclass must have a public no-arg ctor and must implement - the process() method which is abstract in this class. - - Commands are intended to be stateless, except perhaps for counters - and similar internals. Specifically, no state which changes the - behavior between any two invocations of process() should be - retained. -*/ -abstract class Command { - protected Command(){} - - /** - Must process one command-unit of work and either return - (on success) or throw (on error). - - The first two arguments specify 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. - */ - public abstract void process( - SQLTester st, TestScript 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(TestScript ts, String[] argv, int min, int max) throws Exception{ - int argc = argv.length-1; - if(argc=0 && argc>max)){ - if( min==max ){ - ts.toss(argv[0]," requires exactly ",min," argument(s)"); - }else if(max>0){ - ts.toss(argv[0]," requires ",min,"-",max," arguments."); - }else{ - ts.toss(argv[0]," requires at least ",min," arguments."); - } - } - } - - /** - Equivalent to argcCheck(argv,argc,argc). - */ - protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ - argcCheck(ts, argv, argc, argc); - } -} - -//! --close command -class CloseDbCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,1); - Integer id; - if(argv.length>1){ - String arg = argv[1]; - if("all".equals(arg)){ - t.closeAllDbs(); - return; - } - else{ - id = Integer.parseInt(arg); - } - }else{ - id = t.getCurrentDbId(); - } - t.closeDb(id); - } -} - -//! --column-names command -class ColumnNamesCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - argcCheck(ts,argv,1); - st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); - } -} - -//! --db command -class DbCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - t.setCurrentDb( Integer.parseInt(argv[1]) ); - } -} - -//! --glob command -class GlobCommand extends Command { - private boolean negate = false; - public GlobCommand(){} - protected GlobCommand(boolean negate){ this.negate = negate; } - - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1,-1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, - ResultRowMode.ONELINE, sql); - final String result = t.getResultText(); - final String sArgs = Util.argvToString(argv); - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = Util.argvToString(argv); - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); - } - } -} - -//! --json command -class JsonCommand extends ResultCommand { - public JsonCommand(){ super(ResultBufferMode.ASIS); } -} - -//! --json-block command -class JsonBlockCommand extends TableResultCommand { - public JsonBlockCommand(){ super(true); } -} - -//! --new command -class NewDbCommand extends OpenDbCommand { - public NewDbCommand(){ super(true); } -} - -//! Placeholder dummy/no-op commands -class NoopCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand extends GlobCommand { - public NotGlobCommand(){ - super(true); - } -} - -//! --null command -class NullCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - argcCheck(ts,argv,1); - st.setNullValue( argv[1] ); - } -} - -//! --open command -class OpenDbCommand extends Command { - private boolean createIfNeeded = false; - public OpenDbCommand(){} - protected OpenDbCommand(boolean c){createIfNeeded = c;} - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - t.openDb(argv[1], createIfNeeded); - } -} - -//! --print command -class PrintCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - st.out(ts.getOutputPrefix(),": "); - if( 1==argv.length ){ - st.out( st.getInputText() ); - }else{ - st.outln( Util.argvToString(argv) ); - } - } -} - -//! --result command -class ResultCommand extends Command { - private final ResultBufferMode bufferMode; - protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } - public ResultCommand(){ this(ResultBufferMode.ESCAPED); } - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,-1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); - final String result = t.getResultText().trim(); - final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - if( !result.equals(sArgs) ){ - t.outln(argv[0]," FAILED comparison. Result buffer:\n", - result,"\nargs:\n",sArgs); - ts.toss(argv[0]+" comparison failed."); - } - } -} - -//! --run command -class RunCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,1); - final sqlite3 db = (1==argv.length) - ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, sql); - if( 0!=rc && t.isVerbose() ){ - String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",sql); - } - } -} - -//! --tableresult command -class TableResultCommand extends Command { - private final boolean jsonMode; - protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } - public TableResultCommand(){ this(false); } - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0); - t.incrementTestCounter(); - String body = ts.fetchCommandBody(); - if( null==body ) ts.toss("Missing ",argv[0]," body."); - body = body.trim(); - if( !body.endsWith("\n--end") ){ - ts.toss(argv[0], " must be terminated with --end."); - }else{ - int n = body.length(); - body = body.substring(0, n-6); - } - final String[] globs = body.split("\\s*\\n\\s*"); - if( globs.length < 1 ){ - ts.toss(argv[0], " requires 1 or more ", - (jsonMode ? "json snippets" : "globs"),"."); - } - final String sql = t.takeInputBuffer(); - t.execSql(null, true, - jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, - ResultRowMode.NEWLINE, sql); - final String rbuf = t.getResultText(); - final String[] res = rbuf.split("\n"); - if( res.length != globs.length ){ - ts.toss(argv[0], " failure: input has ", res.length, - " row(s) but expecting ",globs.length); - } - for(int i = 0; i < res.length; ++i){ - final String glob = globs[i].replaceAll("\\s+"," ").trim(); - //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); - if( jsonMode ){ - if( !glob.equals(res[i]) ){ - ts.toss(argv[0], " json <<",glob, ">> does not match: <<", - res[i],">>"); - } - }else if( 0 != SQLTester.strglob(glob, res[i]) ){ - ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); - } - } - } -} - -//! --testcase command -class TestCaseCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - // TODO?: do something with the test name - t.clearResultBuffer(); - t.clearInputBuffer(); - } -} - -class CommandDispatcher2 { - - private static java.util.Map commandMap = - new java.util.HashMap<>(); - - /** - Returns a (cached) instance mapped to name, or null if no match - is found. - */ - static Command getCommandByName(String name){ - Command rv = commandMap.get(name); - if( null!=rv ) return rv; - switch(name){ - case "close": rv = new CloseDbCommand(); break; - case "column-names":rv = new ColumnNamesCommand(); break; - case "db": rv = new DbCommand(); break; - case "glob": rv = new GlobCommand(); break; - case "json": rv = new JsonCommand(); break; - case "json-block": rv = new JsonBlockCommand(); 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 "tableresult": rv = new TableResultCommand(); break; - case "testcase": rv = new TestCaseCommand(); 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, TestScript ts, String[] argv) throws Exception{ - final Command cmd = getCommandByName(argv[0]); - if(null == cmd){ - throw new UnknownCommand(ts, argv[0]); - } - cmd.process(tester, ts, argv); - } -} - - -/** - 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 filename = null; - 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 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 = 0/*yes, zero*/; inComment = false; - } - } - - private byte[] readFile(String filename) throws Exception { - return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); - } - - /** - Initializes the script with the content of the given file. - Throws if it cannot read the file. - */ - public TestScript(String filename) throws Exception{ - this.filename = filename; - setVerbosity(2); - 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 TestScript verboseN(int level, Object... vals){ - final int verbosity = outer.getVerbosity(); - if(verbosity>=level){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), - getOutputPrefix(),": "); - outer.outln(vals); - } - return this; - } - - private TestScript verbose1(Object... vals){return verboseN(1,vals);} - private TestScript verbose2(Object... vals){return verboseN(2,vals);} - - @SuppressWarnings("unchecked") - public TestScript warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": "); - outer.outln(vals); - return this; - } - - private void reset(){ - cur.reset(); - } - - - /** - Returns the next line from the buffer, minus the trailing EOL. - - 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(){ - 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 = cur.pos; - if(skipLeadingWs) { - /* Skip any leading spaces, including newlines. This will eliminate - blank lines. */ - 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*/: ++cur.lineNo; continue; - default: break; - } - break; - } - 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 < cur.src.length && !doBreak; ++i){ - b = cur.src[i]; - switch( (int)b ){ - case 13/*CR*/: continue; - case 10/*NL*/: - ++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 - one time. Appending individual bytes to the StringBuffer - appends their integer value. */ - nChar = 1; - switch( b & 0xF0 ){ - case 0xC0: nChar = 2; break; - case 0xE0: nChar = 3; break; - case 0xF0: nChar = 4; break; - default: - if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); - break; - } - if( 1==nChar ){ - cur.sb.append((char)b); - }else{ - 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; - } - } - cur.pos = i; - final String rv = cur.sb.toString(); - if( i==cur.src.length && 0==rv.length() ){ - return null /* EOF */; - } - 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. - */ - 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(). - */ - void consumePeeked(){ - cur.pos = cur.peekedPos; - cur.lineNo = cur.peekedLineNo; - } - - /** - Restores the cursor to the position it had before the previous - call to getLine(). - */ - void putbackLine(){ - cur.pos = cur.putbackPos; - cur.lineNo = cur.putbackLineNo; - } - - private static final Pattern patternRequiredProperties = - Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\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)); - } - if( line.indexOf("\n|")>=0 ){ - throw new IncompatibleDirective(this, "newline-pipe combination."); - } - return; - } - - boolean isCommandLine(String line, boolean checkForImpl){ - final Matcher m = patternCommand.matcher(line); - boolean rc = m.find(); - if( rc && checkForImpl ){ - rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); - } - return rc; - } - - /** - If line looks like a command, returns an argv for that command - invocation, else returns null. - */ - 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 recognized command. Throws if - checkForDirective() does. Returns null if there is no input or - it's only whitespace. The returned string retains all whitespace. - - Note that "subcommands", --command-like constructs in the body - which do not match a known command name are considered to be - content, not commands. - */ - String fetchCommandBody(){ - final StringBuilder sb = new StringBuilder(); - String line; - while( (null != (line = peekLine())) ){ - checkForDirective(line); - if( !isCommandLine(line, true) ){ - sb.append(line).append("\n"); - consumePeeked(); - }else{ - break; - } - } - line = sb.toString(); - return line.trim().isEmpty() ? null : line; - } - - private void processCommand(SQLTester t, String[] argv) throws Exception{ - verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); - if(outer.getVerbosity()>1){ - final String input = t.getInputText(); - if( !input.isEmpty() ) verbose2("Input buffer = ",input); - } - CommandDispatcher2.dispatch(t, this, argv); - } - - void toss(Object... msg) throws TestScriptFailed { - StringBuilder sb = new StringBuilder(); - for(Object s : msg) sb.append(s); - throw new TestScriptFailed(this, sb.toString()); - } - - /** - Runs this test script in the context of the given tester object. - */ - @SuppressWarnings("unchecked") - public boolean run(SQLTester tester) throws Exception { - reset(); - setVerbosity(tester.getVerbosity()); - 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; - } -} diff --git a/manifest b/manifest index 83a8306b8c..0714a98a0b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Defer\sstatic\sJNI-side\sinit\sof\sSQLTester\suntil\smain()\sis\scalled\sso\sthat\sits\sauto-extensions\sdo\snot\sleak\sover\sto\sclients\sof\sthe\smain\slibrary. -D 2023-08-10T01:19:40.795 +C Move\sall\sof\sthe\sSQLTester\scode\sinto\sa\ssingle\sfile,\ssince\sit's\sonly\sgot\s1\spublic\sclass.\sRemove\s'public'\sfrom\smany\smethods\swhich\sdon't\sneed\sit.\sAdd\smore\sdocumentation\sto\sit. +D 2023-08-10T01:44:48.660 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 -F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 +F ext/jni/GNUmakefile dcaf23ca24ee2ec2d2e36afdc58886fcf147830c25989a669599c69d38796493 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 @@ -266,9 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 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 a5d843ade1f4456cf5cd3d021f5593ff60c0d8786fb55a2d59d7a984738c299e -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9f172fcffae9935c6d1c9686b3f69df199a191b7280c926bf85fa8fa30ebbddf +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e7148d734ee13baa8a49508a9a7890914d8d57ff8353fce78b60a875f407cf91 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2089,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 52fa6f78414c41073431c166550806bb8a835bd38cfc1236c9363784c78b81b9 -R e4a7dc0518dd6056979335c8c6e73e83 +P e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 +R 4272692fbef77fb1007b0ab4a881bf94 U stephan -Z 125c5fe250744e87fc135cd6f93efe47 +Z 62b15385eb7331c7503b86f0cead2232 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 904da66b33..f0f3317811 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 \ No newline at end of file +2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 \ No newline at end of file