More for the SQLTester rework. Can read input and dispatch commands, but only --print is currently implemented.

FossilOrigin-Name: 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314
This commit is contained in:
stephan
2023-08-09 22:18:22 +00:00
parent e41c09ac8c
commit bef0369ff4
6 changed files with 358 additions and 66 deletions
@@ -80,7 +80,6 @@ public class SQLTester {
private int iCurrentDb = 0;
private final String initialDbName = "test.db";
private TestScript currentScript;
private TestScript2 currentScript2;
public SQLTester(){
reset();
@@ -159,23 +158,23 @@ public class SQLTester {
//! Not yet funcional
private void runTests2() throws Exception {
try {
for(String f : listInFiles){
reset();
setupInitialDb();
++nTestFile;
final TestScript2 ts = new TestScript2(f);
currentScript2 = ts;
try{
ts.run(this);
}catch(SkipTestRemainder e){
/* not an error */
++nAbortedScript;
}
outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename());
for(String f : listInFiles){
reset();
setupInitialDb();
++nTestFile;
final TestScript2 ts = new TestScript2(f);
try{
ts.run(this);
}catch(SkipTestRemainder2 e){
/* not fatal */
outln(e);
++nAbortedScript;
}catch(IncompatibleDirective e){
/* not fatal */
outln(e);
++nAbortedScript;
}
}finally{
currentScript2 = null;
outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename());
}
Util.unlink(initialDbName);
}
@@ -195,6 +194,16 @@ public class SQLTester {
StringBuilder getInputBuffer(){ return inputBuffer; }
void appendInput(String n, boolean addNL){
inputBuffer.append(n);
if(addNL) inputBuffer.append('\n');
}
void appendResult(String n, boolean addNL){
resultBuffer.append(n);
if(addNL) resultBuffer.append('\n');
}
String getInputText(){ return inputBuffer.toString(); }
String getResultText(){ return resultBuffer.toString(); }
@@ -90,7 +90,7 @@ class TestScript {
}
@SuppressWarnings("unchecked")
private <T> TestScript verbose(T... vals){
private TestScript verbose(Object... vals){
outer.verbose(vals);
return this;
}
@@ -12,9 +12,123 @@
** This file contains the TestScript2 part of the SQLTester framework.
*/
package org.sqlite.jni.tester;
//import java.util.regex.*;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.regex.*;
class SQLTestException extends RuntimeException {
public SQLTestException(String msg){
super(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<min || (max>=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);
}
}
class PrintCommand2 extends Command2 {
public void process(
SQLTester st, TestScript2 ts, String[] argv
) throws Exception{
st.out(ts.getOutputPrefix(),": ");
if( 1==argv.length ){
st.outln( st.getInputText() );
}else{
st.outln( Util.argvToString(argv) );
}
final String body = ts.fetchCommandBody();
if( null!=body ){
st.out(body,"\n");
}
}
}
class CommandDispatcher2 {
private static java.util.Map<String,Command2> 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 "print": rv = new PrintCommand2(); 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
@@ -23,18 +137,23 @@ import java.nio.charset.StandardCharsets;
*/
class TestScript2 {
private String filename = null;
private final Cursor curs = new Cursor();
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 lineNo = 1;
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 = 1; inComment = false;
sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false;
}
}
@@ -49,76 +168,102 @@ class TestScript2 {
public TestScript2(String filename) throws Exception{
this.filename = filename;
setVerbosity(2);
curs.src = readFile(filename);
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 <T> TestScript2 verbose(T... vals){
outer.verbose(vals);
private TestScript2 verbose(Object... vals){
final int verbosity = outer.getVerbosity();
if(verbosity>0){
outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),
getOutputPrefix(),": ");
outer.outln(vals);
}
return this;
}
@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(curs.lineNo).
sb.append(this.filename).append(":").append(cur.lineNo).
append(": ");
for(Object o : msg) sb.append(o);
throw new RuntimeException(sb.toString());
}
private void reset(){
curs.reset();
cur.reset();
}
/**
Returns the next line from the buffer, minus the trailing EOL.
If skipLeadingWs is true then all leading whitespace (including
blank links) is skipped over and will not appear in the resulting
string.
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(boolean skipLeadingWs){
curs.sb.setLength(0);
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 = curs.pos;
int i = cur.pos;
if(skipLeadingWs) {
/* Skip any leading spaces, including newlines. This will eliminate
blank lines. */
for(; i < curs.src.length; ++i, prevB=b){
b = curs.src[i];
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*/: ++curs.lineNo; continue;
case 10/*NL*/: ++cur.lineNo; continue;
default: break;
}
break;
}
}
if( i==curs.src.length ){
return null /* EOF */;
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 < curs.src.length && !doBreak; ++i){
b = curs.src[i];
for(; i < cur.src.length && !doBreak; ++i){
b = cur.src[i];
switch( (int)b ){
case 13/*CR*/: continue;
case 10/*NL*/:
++curs.lineNo;
if(curs.sb.length()>0) doBreak = true;
++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
@@ -134,33 +279,161 @@ class TestScript2 {
break;
}
if( 1==nChar ){
curs.sb.append((char)b);
cur.sb.append((char)b);
}else{
for(int x = 0; x < nChar; ++x) aChar[x] = curs.src[i+x];
curs.sb.append(new String(Arrays.copyOf(aChar, nChar),
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;
}
}
curs.pos = i;
if( 0==curs.sb.length() && i==curs.src.length ){
cur.pos = i;
final String rv = cur.sb.toString();
if( i==cur.src.length && 0==rv.length() ){
return null /* EOF */;
}
return curs.sb.toString();
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*$");
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));
}
return;
}
public boolean isCommandLine(String line){
final Matcher m = patternCommand.matcher(line);
return m.find();
}
/**
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 command. Throws if
checkForDirective() does. Returns null if there is no input or
it's only whitespace. The returned string is trim()'d of
leading/trailing whitespace.
*/
public String fetchCommandBody(){
final StringBuilder sb = new StringBuilder();
String line;
while( (null != (line = peekLine())) ){
checkForDirective(line);
if( !isCommandLine(line) ){
sb.append(line).append("\n");
consumePeeked();
}else{
break;
}
}
line = sb.toString().trim();
return line.isEmpty() ? null : line;
}
public void processCommand(SQLTester t, String[] argv) throws Exception{
//verbose("got argv: ",argv[0], " ", Util.argvToString(argv));
//verbose("Input buffer = ",t.getInputBuffer());
CommandDispatcher2.dispatch(t, this, argv);
}
/**
Runs this test script in the context of the given tester object.
*/
@SuppressWarnings("unchecked")
public void run(SQLTester tester) throws Exception {
public boolean run(SQLTester tester) throws Exception {
reset();
setVerbosity(tester.getVerbosity());
String line;
while( null != (line = getLine(false)) ){
verbose("LINE #",curs.lineNo-1,": ",line);
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;
}
}
+13 -3
View File
@@ -1,10 +1,20 @@
/*
** This is a comment. There are many like it but this one is mine.
**
*/
--command 1
** SCRIPT_MODULE_NAME: sanity-check
** xMIXED_MODULE_NAME: mixed-module
** xMODULE_NAME: module-name
** xREQUIRED_PROPERTIES: small fast reliable
**
*/
/*#if foo*/
/*---foo*/
/* --print without flags dumps current input buffer */
--print
--command 🤩😃
non-command/non-directive text after --print is also emitted.
--print 🤩😃
SELECT 1;
SELECT 2;
--print the end
+9 -9
View File
@@ -1,5 +1,5 @@
C Initial\ssketches\sof\sa\sline-by-line\sparser\sfor\sSQLTester\sto\sovercome\sits\scompatibility\sshortcomings.\sFar\sfrom\scomplete.
D 2023-08-09T19:51:39.077
C More\sfor\sthe\sSQLTester\srework.\sCan\sread\sinput\sand\sdispatch\scommands,\sbut\sonly\s--print\sis\scurrently\simplemented.
D 2023-08-09T22:18:22.852
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -266,11 +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 35ea65f416b41b1e21119f58109e35518a39cbc1edbdd8883cb581772665c18d
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 18f55e1e3001c4ccfc359d57448729227c3eaf4a7c774964fe6418e07aefd541
F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 1c8426039f2050cf623dab17e5d05a976ee061429c7be1eb5a3e73f7b00daf65
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28
F ext/jni/src/org/sqlite/jni/tester/TestScript2.java f01faf9facbc029f5baf731edf5de37cdd2921ec55ca4d8bd1e764c13211828f
F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba
F ext/jni/src/tests/000-000-sanity.test2 0b4bb90e12cfa96e24999f5890ce46305b6a83efcf9ef0ff4df16521f636a9d5
F ext/jni/src/tests/000-000-sanity.test2 8cb312a3dde4d09ead6a33a056ef685a99225b18635a07d03fa21d83879fb1c0
F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd
F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
@@ -2092,8 +2092,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 f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c
R e3dd73f0afa45739c2d0daea22589f05
P 43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9
R 02b26ab163237a92c9aeaab8dbe6493d
U stephan
Z 24fe8a8f37b2f666f1b6b1c10180bf33
Z 4fb10f0d619801f5f787d45bc947ea2c
# Remove this line to create a well-formed Fossil manifest.
+1 -1
View File
@@ -1 +1 @@
43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9
4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314