From: stephan Date: Thu, 10 Aug 2023 00:34:38 +0000 (+0000) Subject: Replace the SQLTester infrastructure with a line-oriented, non-regex-heavy parser... X-Git-Tag: version-3.43.0~47^2~33 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=84c1a7b49f8fa7c499403cbd8733153718a16620;p=thirdparty%2Fsqlite.git Replace the SQLTester infrastructure with a line-oriented, non-regex-heavy parser. Add --column-names command. FossilOrigin-Name: 88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d --- diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 6d7f35f900..9d78f9b0e6 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -87,7 +87,6 @@ JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ Outer.java \ SQLTester.java \ TestScript.java \ - TestScript2.java \ ) CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 03bde86fdd..431679c7ce 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -21,19 +21,6 @@ import java.util.regex.*; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; -class TestFailure extends RuntimeException { - public TestFailure(String msg){ - super(msg); - } -} - -class SkipTestRemainder extends RuntimeException { - public TestScript testScript; - public SkipTestRemainder(TestScript ts){ - super("Skipping remainder of "+ts.getName()); - testScript = ts; - } -} /** Modes for how to handle SQLTester.execSql()'s @@ -55,6 +42,12 @@ enum ResultRowMode { NEWLINE }; +class SQLTesterException extends RuntimeException { + public SQLTesterException(String msg){ + super(msg); + } +} + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -76,6 +69,7 @@ public class SQLTester { private int nTestFile = 0; private int nAbortedScript = 0; private int nTest; + private boolean emitColNames; private final sqlite3[] aDb = new sqlite3[7]; private int iCurrentDb = 0; private final String initialDbName = "test.db"; @@ -95,6 +89,8 @@ public class SQLTester { return this.outer.isVerbose(); } + void outputColumnNames(boolean b){ emitColNames = b; } + @SuppressWarnings("unchecked") public void verbose(Object... vals){ outer.verbose(vals); @@ -125,56 +121,29 @@ public class SQLTester { return currentScript; } - public void runTests() throws Exception { - // process each input file - try { - for(String f : listInFiles){ - reset(); - setupInitialDb(); - ++nTestFile; - final TestScript ts = new TestScript(f); - currentScript = ts; - outln("----->>>>> ",ts.getModuleName()," [",ts.getName(),"]"); - if( ts.isIgnored() ){ - outln("WARNING: skipping [",ts.getModuleName(),"]: ", - ts.getIgnoredReason()); - continue; - }else{ - try{ - ts.run(this); - }catch(SkipTestRemainder e){ - /* not an error */ - ++nAbortedScript; - } - } - outln("<<<<<----- ",ts.getModuleName(),": ",nTest," test(s)"); - } - }finally{ - currentScript = null; - } - Util.unlink(initialDbName); - } - - - //! Not yet funcional - private void runTests2() throws Exception { + private void runTests() throws Exception { for(String f : listInFiles){ reset(); setupInitialDb(); ++nTestFile; - final TestScript2 ts = new TestScript2(f); + final TestScript ts = new TestScript(f); + outln("----->>>>> running [",f,"]"); try{ ts.run(this); - }catch(SkipTestRemainder2 e){ - /* not fatal */ + }catch(UnknownCommand e){ + /* currently not fatal */ outln(e); ++nAbortedScript; }catch(IncompatibleDirective e){ /* not fatal */ outln(e); ++nAbortedScript; + }catch(Exception e){ + ++nAbortedScript; + throw e; + }finally{ + outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } - outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } Util.unlink(initialDbName); } @@ -264,7 +233,7 @@ public class SQLTester { if( 0!=rc ){ final String msg = sqlite3_errmsg(db); sqlite3_close(db); - Util.toss(TestFailure.class, "db open failed with code ", + Util.toss(SQLTesterException.class, "db open failed with code ", rc," and message: ",msg); } return aDb[iCurrentDb] = db; @@ -364,70 +333,89 @@ public class SQLTester { final StringBuilder sb = (ResultBufferMode.NONE==appendMode) ? null : resultBuffer; //outln("sqlChunk len= = ",sqlChunk.length); - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", - new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ - if( 0!=rc ){ - if(throwOnError){ - Util.toss(RuntimeException.class, "db op failed with rc=" - +rc+": "+sqlite3_errmsg(db)); - }else if( null!=sb ){ - appendDbErr(db, sb, rc); + try{ + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); } - break; - } - pos = oTail.getValue(); - stmt = outStmt.getValue(); - if( null == stmt ){ - // empty statement was parsed. - continue; - } - if( null!=sb ){ - // Add the output to the result buffer... - final int nCol = sqlite3_column_count(stmt); - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ - for(int i = 0; i < nCol; ++i){ - if( spacing++ > 0 ) sb.append(' '); - String val = sqlite3_column_text16(stmt, i); - if( null==val ){ - sb.append( nullView ); - continue; + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", + new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ + if( 0!=rc ){ + if(throwOnError){ + Util.toss(RuntimeException.class, "db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + }else if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + pos = oTail.getValue(); + stmt = outStmt.getValue(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + if( null!=sb ){ + // Add the output to the result buffer... + final int nCol = sqlite3_column_count(stmt); + String colName = null, val = null; + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + for(int i = 0; i < nCol; ++i){ + if( spacing++ > 0 ) sb.append(' '); + if( emitColNames ){ + colName = sqlite3_column_name(stmt, i); + switch(appendMode){ + case ASIS: + sb.append( colName ); + break; + case ESCAPED: + sb.append( escapeSqlValue(colName) ); + break; + default: + Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + } + sb.append(' '); + } + val = sqlite3_column_text16(stmt, i); + if( null==val ){ + sb.append( nullView ); + continue; + } + switch(appendMode){ + case ASIS: + sb.append( val ); + break; + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; + default: + Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + } } - switch(appendMode){ - case ASIS: - sb.append( val ); - break; - case ESCAPED: - sb.append( escapeSqlValue(val) ); - break; - default: - Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + if( ResultRowMode.NEWLINE == lineMode ){ + spacing = 0; + sb.append('\n'); } } - if( ResultRowMode.NEWLINE == lineMode ){ - spacing = 0; - sb.append('\n'); + }else{ + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} + } + sqlite3_finalize(stmt); + stmt = null; + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + else if( rc!=0 ){ + if( null!=sb ){ + appendDbErr(db, sb, rc); } + break; } - }else{ - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} } + }finally{ sqlite3_finalize(stmt); - if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; - else if( rc!=0 ){ - if( null!=sb ){ - appendDbErr(db, sb, rc); - } - break; - } } - sqlite3_finalize(stmt); if( 0!=rc && throwOnError ){ Util.toss(RuntimeException.class, "db op failed with rc=" +rc+": "+sqlite3_errmsg(db)); @@ -443,8 +431,6 @@ public class SQLTester { final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ t.setVerbosity(t.getVerbosity() + 1); - }else if( flag.equals("2") ){ - v2 = true; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } @@ -452,11 +438,13 @@ public class SQLTester { } t.addTestScript(a); } - if( v2 ) t.runTests2(); - else t.runTests(); - t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); - if( t.nAbortedScript > 0 ){ - t.outln("Aborted ",t.nAbortedScript," script(s)."); + try { + t.runTests(); + }finally{ + t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + if( t.nAbortedScript > 0 ){ + t.outln("Aborted ",t.nAbortedScript," script(s)."); + } } } @@ -492,363 +480,6 @@ 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 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. 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 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(argc=0 && argc>max)){ - if( min==max ){ - Util.badArg(argv[0]," requires exactly ",min," argument(s)"); - }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); - } - - //! Throws if content is not null. - protected void affirmNoContent(String content) throws Exception{ - if(null != content){ - Util.badArg(this.getClass().getName()," does not accept content ", - "but got:\n",content); - } - } - - //! Throws if content is null. - protected void affirmHasContent(String content) throws Exception{ - if(null == content){ - Util.badArg(this.getClass().getName()," requires content."); - } - } -} - -class CloseDbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,1); - affirmNoContent(content); - Integer id; - if(argv.length>1){ - String arg = argv[1]; - if("all".equals(arg)){ - //t.verbose(argv[0]," all dbs"); - t.closeAllDbs(); - return; - } - else{ - id = Integer.parseInt(arg); - } - }else{ - id = t.getCurrentDbId(); - } - t.closeDb(id); - t.verbose(argv[0]," db ",id); - } -} - -//! --db command -class DbCommand extends Command { - 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]) ); - //t.verbose(argv[0]," set db to ",db); - } -} - -//! --glob command -class GlobCommand extends Command { - 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); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - 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 = argv[1]; - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - Util.toss(TestFailure.class, 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 Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - String fname = argv[1]; - Util.unlink(fname); - final sqlite3 db = t.openDb(fname, true); - //t.verbose(argv[0]," db ",db); - } -} - -//! Placeholder dummy/no-op commands -class NoopCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand extends GlobCommand { - public NotGlobCommand(){ - super(true); - } -} - -//! --null command -class NullCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - t.setNullValue(argv[1]); - //t.verbose(argv[0]," ",argv[1]); - } -} - -//! --open command -class OpenDbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - String fname = argv[1]; - final sqlite3 db = t.openDb(fname, false); - //t.verbose(argv[0]," db ",db); - } -} - -//! --print command -class PrintCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - if( 1==argv.length && null==content ){ - Util.badArg(argv[0]," requires at least 1 argument or body content."); - } - if( argv.length > 1 ) t.outln("\t",Util.argvToString(argv)); - if( null!=content ) t.outln(content.replaceAll("(?m)^", "\t")); - } -} - -//! --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, String[] argv, String content) throws Exception{ - argcCheck(argv,0,-1); - affirmNoContent(content); - 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); - Util.toss(TestFailure.class, argv[0]," comparison failed."); - } - } -} - -//! --run command -class RunCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,1); - affirmHasContent(content); - final sqlite3 db = (1==argv.length) - ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, content); - if( 0!=rc && t.isVerbose() ){ - String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",content); - } - } -} - -//! --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, String[] argv, String content) throws Exception{ - argcCheck(argv,0); - affirmHasContent(content); - t.incrementTestCounter(); - if( !content.endsWith("\n--end") ){ - Util.toss(TestFailure.class, argv[0], " must be terminated with --end."); - }else{ - int n = content.length(); - content = content.substring(0, n-6); - } - final String[] globs = content.split("\\s*\\n\\s*"); - if( globs.length < 1 ){ - Util.toss(TestFailure.class, 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 ){ - Util.toss(TestFailure.class, 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]) ){ - Util.toss(TestFailure.class, argv[0], " json <<",glob, - ">> does not match: <<",res[i],">>"); - } - }else if( 0 != SQLTester.strglob(glob, res[i]) ){ - Util.toss(TestFailure.class, argv[0], " glob <<",glob, - ">> does not match: <<",res[i],">>"); - } - } - } -} - -//! --testcase command -class TestCaseCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmHasContent(content); - // TODO: do something with the test name - t.clearResultBuffer(); - t.clearInputBuffer().append(content); - //t.verbose(argv[0]," input buffer: ",content); - } -} - -/** - Helper for dispatching Command instances. -*/ -class CommandDispatcher { - - 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 "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, String[] argv, String content) throws Exception{ - final Command cmd = getCommandByName(argv[0]); - if(null == cmd){ - final TestScript ts = tester.getCurrentScript(); - if( tester.skipUnknownCommands() ){ - tester.outln("WARNING: skipping remainder of [",ts.getModuleName(), - "] because it contains unknown command '",argv[0],"'."); - throw new SkipTestRemainder(ts); - } - Util.toss(IllegalArgumentException.class, - "No command handler found for '"+argv[0]+"' in ", - ts.getName()); - } - //tester.verbose("Running ",argv[0]," with:\n", content); - cmd.process(tester, argv, content); - } -} - /** General utilities for the SQLTester bits. */ diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 742cf4fa52..8f6863eef0 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -12,29 +12,384 @@ ** This file contains the TestScript part of the SQLTester framework. */ package org.sqlite.jni.tester; -import java.util.List; -import java.util.ArrayList; -import java.io.*; +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); + 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 = argv[1]; + 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(),": "); + final String body = ts.fetchCommandBody(); + if( 1==argv.length && null==body ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + if( null!=body ){ + st.out(body); + } + } +} + +//! --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 name = null; + private String filename = null; private String moduleName = null; - private List chunks = null; + private final Cursor cur = new Cursor(); private final Outer outer = new Outer(); - private String ignoreReason = null; - private byte[] baScript = null; - /* 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 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 { @@ -43,229 +398,289 @@ class TestScript { /** Initializes the script with the content of the given file. - Throws if it cannot read the file or if tokenizing it fails. + Throws if it cannot read the file. */ public TestScript(String filename) throws Exception{ - name = filename; - baScript = readFile(filename); - setContent(new String( - baScript, java.nio.charset.StandardCharsets.UTF_8 - )); + this.filename = filename; + setVerbosity(2); + cur.src = readFile(filename); } - /** - Initializes the script with the given content, copied at - construction-time. The first argument is a filename for that - content. It need not refer to a real file - it's for display - purposes only. - */ - public TestScript(String virtualName, StringBuffer content) - throws RuntimeException { - name = virtualName; - setContent(content.toString()); + public String getFilename(){ + return filename; } - private void setContent(String c){ - this.chunks = chunkContent(c); + public String getModuleName(){ + return moduleName; } - public String getName(){ - return name; + public void setVerbosity(int level){ + outer.setVerbosity(level); } - public String getModuleName(){ - return moduleName; + 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);} - public boolean isIgnored(){ - return null!=ignoreReason; + @SuppressWarnings("unchecked") + public TestScript warn(Object... vals){ + outer.out("WARNING ", getOutputPrefix(),": "); + outer.outln(vals); + return this; } - public String getIgnoredReason(){ - return ignoreReason; + private void reset(){ + cur.reset(); } - public void setVerbosity(int level){ - outer.setVerbosity(level); + + /** + 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; } - @SuppressWarnings("unchecked") - private TestScript verbose(Object... vals){ - outer.verbose(vals); - return this; + /** + 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 patternHashLine = - Pattern.compile("^#", Pattern.MULTILINE); private static final Pattern patternRequiredProperties = - Pattern.compile("REQUIRED_PROPERTIES:[ \\t]*(\\S+\\s*)\\n"); + 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-]+)( .*)?)$"); + /** - Returns true if the given script content should be ignored - (because it contains certain content which indicates such). + 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 boolean shouldBeIgnored(String content){ - if( null == moduleName ){ - ignoreReason = "No module name."; - return true; - }else if( content.indexOf("\n|")>=0 ){ - ignoreReason = "Contains newline-pipe combination."; - return true; - }else if( content.indexOf(" MODULE_NAME:")>=0 ){ - ignoreReason = "Contains MODULE_NAME."; - return true; - }else if( content.indexOf("MIXED_MODULE_NAME:")>=0 ){ - ignoreReason = "Contains MIXED_MODULE_NAME."; - return true; + 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 = patternHashLine.matcher(content); + Matcher m = patternScriptModuleName.matcher(line); if( m.find() ){ - ignoreReason = "C-preprocessor line found."; - return true; + moduleName = m.group(1); + return; } - m = patternRequiredProperties.matcher(content); + m = patternRequiredProperties.matcher(line); if( m.find() ){ - ignoreReason = "REQUIRED_PROPERTIES found: "+m.group(1).trim(); - return true; + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); } - return false; + 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; } - private boolean findModuleName(String content){ - final Pattern p = Pattern.compile( - "SCRIPT_MODULE_NAME:\\s+(\\S+)\\s*\n", - Pattern.MULTILINE - ); - final Matcher m = p.matcher(content); - moduleName = m.find() ? m.group(1) : null; - return moduleName != null; + 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; } /** - Chop script up into chunks containing individual commands and - their inputs. The approach taken here is not as robust as - line-by-line parsing would be but the framework is structured - such that we could replace this part without unduly affecting the - evaluation bits. The potential problems with this approach - include: - - - It's potentially possible that it will strip content out of a - testcase block. + 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; + } - - It loses all file location information, so we can't report line - numbers of errors. + /** + 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. - If/when that becomes a problem, it can be refactored. + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. */ - private List chunkContent(String content){ - findModuleName(content); - if( shouldBeIgnored(content) ){ - chunks = null; - return null; - } - - // First, strip out any content which we know we can ignore... - final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; - final String s3Dash = "^---+[^\\n]*\\n"; - final String sEmptyLine = "^\\n"; - final String sOom = "^--oom\\n" - /* Workaround: --oom is a top-level command in some contexts - and appears in --testcase blocks in others. We don't - do anything with --oom commands aside from ignore them, so - elide them all to fix the --testcase blocks which contain - them. */; - final List lPats = new ArrayList<>(); - lPats.add(sCComment); - lPats.add(s3Dash); - lPats.add(sEmptyLine); - lPats.add(sOom); - //verbose("Content:").verbose(content).verbose(""); - for( String s : lPats ){ - final Pattern p = Pattern.compile( - s, Pattern.MULTILINE - ); - 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()); - content = m.replaceAll(""); - } - // Chunk the newly-cleaned text into individual commands and their input... - // 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(content); - int ndxPrev = 0, pos = 0, i = 0; - //verbose("Trimmed content:").verbose(content).verbose(""); - while( m.find() ){ - pos = m.start(); - final String block = content.substring(ndxPrev, pos).trim(); - if( 0==ndxPrev && pos>ndxPrev ){ - /* Initial block of non-command state. Skip it. */ - ndxPrev = pos + 2; - continue; - } - if( !block.isEmpty() ){ - ++i; - //verbose("BLOCK #",i," ",+ndxPrev,"..",pos,block); - blocks.add( block ); - } - ndxPrev = pos + 2; - } - if( ndxPrev < content.length() ){ - // This all belongs to the final command - final String block = content.substring(ndxPrev, content.length()).trim(); - if( !block.isEmpty() ){ - ++i; - //verbose("BLOCK #",(++i)," ",block); - blocks.add( block ); + 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; } } - // 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] - /* reminder: don't trim() here. It would be easier - for Command impls if we did but it makes debug - output look weird. */; - } - rc.add( chunk ); + 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); } - return rc; + 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. */ - public void run(SQLTester tester) throws Exception { - final int verbosity = tester.getVerbosity(); - if( null==chunks ){ - outer.outln("This test contains content which forces it to be skipped."); - }else{ - int n = 0; - for(CommandChunk chunk : chunks){ - if(verbosity>0){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),moduleName, - " #",++n," ",chunk.argv[0], - " ",Util.argvToString(chunk.argv)); - if(verbosity>1 && null!=chunk.content){ - outer.out("\n", chunk.content); - } - outer.out("\n"); - } - CommandDispatcher.dispatch( - tester, chunk.argv, - (null==chunk.content) ? null : chunk.content.trim() - ); + @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/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java deleted file mode 100644 index ee85b72bd8..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ /dev/null @@ -1,680 +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 TestScript2 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 SQLTestException extends RuntimeException { - public SQLTestException(String msg){ - super(msg); - } -} - -class TestScript2Failed extends SQLTestException { - public TestScript2Failed(TestScript2 ts, String msg){ - super(ts.getOutputPrefix()+": "+msg); - } -} - -class SkipTestRemainder2 extends SQLTestException { - public SkipTestRemainder2(TestScript2 ts){ - super(ts.getOutputPrefix()+": skipping remainder"); - } -} - -class IncompatibleDirective extends SQLTestException { - public IncompatibleDirective(TestScript2 ts, String line){ - super(ts.getOutputPrefix()+": incompatible directive: "+line); - } -} - -class UnknownCommand extends SQLTestException { - public UnknownCommand(TestScript2 ts, String line){ - super(ts.getOutputPrefix()+": unknown command: "+line); - } -} - -abstract class Command2 { - protected Command2(){} - - public abstract void process( - SQLTester st, TestScript2 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(String[] argv, int min, int max) throws Exception{ - int argc = argv.length-1; - if(argc=0 && argc>max)){ - if( min==max ){ - Util.badArg(argv[0]," requires exactly ",min," argument(s)"); - }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); - } -} - -//! --close command -class CloseDbCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(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); - } -} - -//! --db command -class DbCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - t.setCurrentDb( Integer.parseInt(argv[1]) ); - } -} - -//! --glob command -class GlobCommand2 extends Command2 { - private boolean negate = false; - public GlobCommand2(){} - protected GlobCommand2(boolean negate){ this.negate = negate; } - - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,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 = argv[1]; - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); - } - } -} - -//! --json command -class JsonCommand2 extends ResultCommand2 { - public JsonCommand2(){ super(ResultBufferMode.ASIS); } -} - -//! --json-block command -class JsonBlockCommand2 extends TableResultCommand2 { - public JsonBlockCommand2(){ super(true); } -} - -//! --new command -class NewDbCommand2 extends OpenDbCommand2 { - public NewDbCommand2(){ super(true); } -} - -//! Placeholder dummy/no-op commands -class NoopCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand2 extends GlobCommand2 { - public NotGlobCommand2(){ - super(true); - } -} - -//! --null command -class NullCommand2 extends Command2 { - public void process( - SQLTester st, TestScript2 ts, String[] argv - ) throws Exception{ - argcCheck(argv,1); - st.setNullValue( argv[1] ); - } -} - -//! --open command -class OpenDbCommand2 extends Command2 { - private boolean createIfNeeded = false; - public OpenDbCommand2(){} - protected OpenDbCommand2(boolean c){createIfNeeded = c;} - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - t.openDb(argv[1], createIfNeeded); - } -} - -//! --print command -class PrintCommand2 extends Command2 { - public void process( - SQLTester st, TestScript2 ts, String[] argv - ) throws Exception{ - st.out(ts.getOutputPrefix(),": "); - final String body = ts.fetchCommandBody(); - if( 1==argv.length && null==body ){ - st.out( st.getInputText() ); - }else{ - st.outln( Util.argvToString(argv) ); - } - if( null!=body ){ - st.out(body); - } - } -} - -//! --result command -class ResultCommand2 extends Command2 { - private final ResultBufferMode bufferMode; - protected ResultCommand2(ResultBufferMode bm){ bufferMode = bm; } - public ResultCommand2(){ this(ResultBufferMode.ESCAPED); } - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(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 RunCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(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 TableResultCommand2 extends Command2 { - private final boolean jsonMode; - protected TableResultCommand2(boolean jsonMode){ this.jsonMode = jsonMode; } - public TableResultCommand2(){ this(false); } - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(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 TestCaseCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - // TODO?: do something with the test name - final String body = ts.fetchCommandBody(); - t.clearResultBuffer(); - t.clearInputBuffer().append(null==body ? "" : body); - } -} - -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 Command2 getCommandByName(String name){ - Command2 rv = commandMap.get(name); - if( null!=rv ) return rv; - switch(name){ - case "close": rv = new CloseDbCommand2(); break; - case "db": rv = new DbCommand2(); break; - case "glob": rv = new GlobCommand2(); break; - case "json": rv = new JsonCommand2(); break; - case "json-block": rv = new JsonBlockCommand2(); break; - case "new": rv = new NewDbCommand2(); break; - case "notglob": rv = new NotGlobCommand2(); break; - case "null": rv = new NullCommand2(); break; - case "oom": rv = new NoopCommand2(); break; - case "open": rv = new OpenDbCommand2(); break; - case "print": rv = new PrintCommand2(); break; - case "result": rv = new ResultCommand2(); break; - case "run": rv = new RunCommand2(); break; - case "tableresult": rv = new TableResultCommand2(); break; - case "testcase": rv = new TestCaseCommand2(); 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, TestScript2 ts, String[] argv) throws Exception{ - final Command2 cmd = getCommandByName(argv[0]); - if(null == cmd){ - if( tester.skipUnknownCommands() ){ - ts.warn("skipping remainder because of unknown command '",argv[0],"'."); - throw new SkipTestRemainder2(ts); - } - Util.toss(IllegalArgumentException.class, - ts.getOutputPrefix()+": no command handler found for '"+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 TestScript2 { - 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 TestScript2(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 TestScript2 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 TestScript2 verbose1(Object... vals){return verboseN(1,vals);} - private TestScript2 verbose2(Object... vals){return verboseN(2,vals);} - - @SuppressWarnings("unchecked") - public TestScript2 warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": "); - outer.outln(vals); - return this; - } - - @SuppressWarnings("unchecked") - private void tossSyntax(Object... msg){ - StringBuilder sb = new StringBuilder(); - sb.append(this.filename).append(":").append(cur.lineNo). - append(": "); - for(Object o : msg) sb.append(o); - throw new RuntimeException(sb.toString()); - } - - 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 ) tossSyntax("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. - */ - public 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(). - */ - public void consumePeeked(){ - cur.pos = cur.peekedPos; - cur.lineNo = cur.peekedLineNo; - } - - /** - Restores the cursor to the position it had before the previous - call to getLine(). - */ - public 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; - } - - public 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. - */ - public 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. - */ - public 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; - } - - public 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); - } - - public void toss(Object... msg) throws TestScript2Failed { - StringBuilder sb = new StringBuilder(); - for(Object s : msg) sb.append(s); - throw new TestScript2Failed(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/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test similarity index 76% rename from ext/jni/src/tests/000-000-sanity.test2 rename to ext/jni/src/tests/000-000-sanity.test index 3ad4c39680..905882dc3b 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test @@ -8,6 +8,7 @@ ** REQUIRED_PROPERTIES: ** */ +--print starting up 😃 --close all --oom --db 0 @@ -38,5 +39,13 @@ SELECT json_array(1,2,3) [1,2,3] {"a":1,"b":2} --end +--testcase col-names-on +--column-names 1 + select 1 as 'a', 2 as 'b'; +--result a 1 b 2 +--testcase col-names-off +--column-names 0 + select 1 as 'a', 2 as 'b'; +--result 1 2 --close ---print 🤩😃 the end +--print reached the end 😃 diff --git a/ext/jni/src/tests/010_ignored.test b/ext/jni/src/tests/000-001-ignored.test similarity index 100% rename from ext/jni/src/tests/010_ignored.test rename to ext/jni/src/tests/000-001-ignored.test diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test deleted file mode 100644 index bcd78a27a3..0000000000 --- a/ext/jni/src/tests/000_first.test +++ /dev/null @@ -1,68 +0,0 @@ -/* A script for testing the org.sqlite.jni.tester infrastructure -** -** SCRIPT_MODULE_NAME: 000_first -** -*/ - -junk - ---new SQLTester.db ---run - SELECT 1; - SELECT 2; --- comment --- uncomment to introduce intentional syntax error ---oom ---print These are args to the print command. -This is from the print command's body. ---print -Also from the print command. ---- also ignored ---testcase 1 - SELECT 'a b', 'c'; - SELECT 'd', 'e'; - SELECT '{}', 'f'; - SELECT '{ }', 'g'; - SELECT '(a-b-c)', '[a-b-c]'; - -- this comment must not cause an error ---result {a b} c d e "{}" f "{\011}" g (a-b-c) [a-b-c] ---testcase 2 - SELECT 123 ---glob 1# ---testcase 3 - SELECT 'a' ---notglob # ---close ---open SQLTester.db ---print Re-opened db. ---testcase fourth - SELECT 1, 2; - SELECT 'b', 'c'; ---tableresult - [0-9] # - b c ---end ---null zilch ---testcase null-command - SELECT null; ---result zilch ---testcase json-array-1 -SELECT json_array(1,2,3) ---json [1,2,3] ---testcase json-array-2 - SELECT json_array(1,2,3); - SELECT json_object('a',1,'b',2); ---json-block -[1,2,3] -{"a":1,"b":2} ---end ---testcase table-result-globs - SELECT 123; - SELECT 'aBc'; - SELECT 456; ---tableresult - # - [a-z][A-Z][a-z] - 4# ---end ---an-unknown-command diff --git a/manifest b/manifest index 8119a67605..8b758a7b5f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Port\sthe\sSQLTester\s'v1'\scommands\sto\sthe\s'v2'\sevaluation\sbits.\sStill\sTODO\sis\sswapping\sout\sv1\swith\sthese\sseparate\simpls. -D 2023-08-09T23:47:14.521 +C Replace\sthe\sSQLTester\sinfrastructure\swith\sa\sline-oriented,\snon-regex-heavy\sparser.\sAdd\s--column-names\scommand. +D 2023-08-10T00:34:38.136 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,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 d7300b7e124214afde7f11bddd5c0d336a9be0220fe2b74e787078e1aa2db778 +F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 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,13 +266,11 @@ 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 b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ae38d872d2cb582e1a1abd67b5e9c276bf2f610cacc918428b63c668131642e -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 25895a534a1e4634268beecd1a689bdfc0aafbfe32071c27b5189ccb8aeec31e +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 496b402c7faedf18be41542c6dc77c19f2735663821a5973639eb614e33aa707 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test2 dfbcccc7b3548ae56deb2ef8fe17dd9235a81fbd552536ef9202284549c7fcf3 -F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd -F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 +F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 w ext/jni/src/tests/000-000-sanity.test2 +F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 w ext/jni/src/tests/010_ignored.test F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2092,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 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b -R eed3d04c8636ba991a620a2a8f5a013d +P 0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 +R aaacd016e2b2dfc7887fd83bed604d98 U stephan -Z bdeddd00da26b413ab473bf39ad731fc +Z fb4c2d6a86f69138bd39103974c9d8cc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d522acb938..2507153dee 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 \ No newline at end of file +88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d \ No newline at end of file