mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
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
This commit is contained in:
@ -83,11 +83,7 @@ ifeq (1,$(enable.fts5))
|
|||||||
TesterFts5.java \
|
TesterFts5.java \
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\
|
JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java
|
||||||
Outer.java \
|
|
||||||
SQLTester.java \
|
|
||||||
TestScript.java \
|
|
||||||
)
|
|
||||||
|
|
||||||
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
||||||
CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
|
CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class)
|
||||||
|
@ -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;}
|
|
||||||
|
|
||||||
}
|
|
@ -20,12 +20,13 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.regex.*;
|
import java.util.regex.*;
|
||||||
import org.sqlite.jni.*;
|
import org.sqlite.jni.*;
|
||||||
import static org.sqlite.jni.SQLite3Jni.*;
|
import static org.sqlite.jni.SQLite3Jni.*;
|
||||||
|
import org.sqlite.jni.sqlite3;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Modes for how to handle SQLTester.execSql()'s
|
Modes for how to escape (or not) column values and names from
|
||||||
result output.
|
SQLTester.execSql() to the result buffer output.
|
||||||
*/
|
*/
|
||||||
enum ResultBufferMode {
|
enum ResultBufferMode {
|
||||||
//! Do not append to result buffer
|
//! Do not append to result buffer
|
||||||
NONE,
|
NONE,
|
||||||
@ -35,6 +36,10 @@ enum ResultBufferMode {
|
|||||||
ASIS
|
ASIS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Modes to specify how to emit multi-row output from
|
||||||
|
SQLTester.execSql() to the result buffer.
|
||||||
|
*/
|
||||||
enum ResultRowMode {
|
enum ResultRowMode {
|
||||||
//! Keep all result rows on one line, space-separated.
|
//! Keep all result rows on one line, space-separated.
|
||||||
ONELINE,
|
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
|
This class provides an application which aims to implement the
|
||||||
rudimentary SQL-driven test tool described in the accompanying
|
rudimentary SQL-driven test tool described in the accompanying
|
||||||
@ -64,45 +134,54 @@ public class SQLTester {
|
|||||||
private final StringBuilder inputBuffer = new StringBuilder();
|
private final StringBuilder inputBuffer = new StringBuilder();
|
||||||
//! Test result buffer.
|
//! Test result buffer.
|
||||||
private final StringBuilder resultBuffer = new StringBuilder();
|
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;
|
private int nTotalTest = 0;
|
||||||
|
//! Total test script files run.
|
||||||
private int nTestFile = 0;
|
private int nTestFile = 0;
|
||||||
|
//! Number of scripts which were aborted.
|
||||||
private int nAbortedScript = 0;
|
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;
|
private boolean emitColNames;
|
||||||
|
//! The list of available db handles.
|
||||||
private final sqlite3[] aDb = new sqlite3[7];
|
private final sqlite3[] aDb = new sqlite3[7];
|
||||||
|
//! Index into aDb of the current db.
|
||||||
private int iCurrentDb = 0;
|
private int iCurrentDb = 0;
|
||||||
|
//! Name of the default db, re-created for each script.
|
||||||
private final String initialDbName = "test.db";
|
private final String initialDbName = "test.db";
|
||||||
private TestScript currentScript;
|
|
||||||
|
|
||||||
public SQLTester(){
|
public SQLTester(){
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVerbosity(int level){
|
void setVerbosity(int level){
|
||||||
this.outer.setVerbosity( level );
|
this.outer.setVerbosity( level );
|
||||||
}
|
}
|
||||||
public int getVerbosity(){
|
int getVerbosity(){
|
||||||
return this.outer.getVerbosity();
|
return this.outer.getVerbosity();
|
||||||
}
|
}
|
||||||
public boolean isVerbose(){
|
boolean isVerbose(){
|
||||||
return this.outer.isVerbose();
|
return this.outer.isVerbose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputColumnNames(boolean b){ emitColNames = b; }
|
void outputColumnNames(boolean b){ emitColNames = b; }
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void verbose(Object... vals){
|
void verbose(Object... vals){
|
||||||
outer.verbose(vals);
|
outer.verbose(vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void outln(Object... vals){
|
void outln(Object... vals){
|
||||||
outer.outln(vals);
|
outer.outln(vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void out(Object... vals){
|
void out(Object... vals){
|
||||||
outer.out(vals);
|
outer.out(vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,16 +191,12 @@ public class SQLTester {
|
|||||||
//verbose("Added file ",filename);
|
//verbose("Added file ",filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupInitialDb() throws Exception {
|
private void setupInitialDb() throws Exception {
|
||||||
Util.unlink(initialDbName);
|
Util.unlink(initialDbName);
|
||||||
openDb(0, initialDbName, true);
|
openDb(0, initialDbName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestScript getCurrentScript(){
|
public void runTests() throws Exception {
|
||||||
return currentScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runTests() throws Exception {
|
|
||||||
for(String f : listInFiles){
|
for(String f : listInFiles){
|
||||||
reset();
|
reset();
|
||||||
setupInitialDb();
|
setupInitialDb();
|
||||||
@ -270,10 +345,12 @@ public class SQLTester {
|
|||||||
|
|
||||||
void incrementTestCounter(){ ++nTest; ++nTotalTest; }
|
void incrementTestCounter(){ ++nTest; ++nTotalTest; }
|
||||||
|
|
||||||
|
//! "Special" characters - we have to escape output if it contains any.
|
||||||
static final Pattern patternSpecial = Pattern.compile(
|
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
|
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,
|
public int execSql(sqlite3 db, boolean throwOnError,
|
||||||
ResultBufferMode appendMode,
|
ResultBufferMode appendMode,
|
||||||
ResultRowMode lineMode,
|
ResultRowMode lineMode,
|
||||||
@ -462,14 +551,19 @@ public class SQLTester {
|
|||||||
of digits, e.g. "#23" or "1#3", but will match at the end,
|
of digits, e.g. "#23" or "1#3", but will match at the end,
|
||||||
e.g. "12#".
|
e.g. "12#".
|
||||||
*/
|
*/
|
||||||
public static int strglob(String glob, String txt){
|
static int strglob(String glob, String txt){
|
||||||
return strglob(
|
return strglob(
|
||||||
(glob+"\0").getBytes(StandardCharsets.UTF_8),
|
(glob+"\0").getBytes(StandardCharsets.UTF_8),
|
||||||
(txt+"\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 {
|
static {
|
||||||
System.loadLibrary("sqlite3-jni")
|
System.loadLibrary("sqlite3-jni")
|
||||||
/* Interestingly, when SQLTester is the main app, we have to
|
/* Interestingly, when SQLTester is the main app, we have to
|
||||||
@ -486,7 +580,7 @@ public class SQLTester {
|
|||||||
final class Util {
|
final class Util {
|
||||||
|
|
||||||
//! Throws a new T, appending all msg args into a string for the message.
|
//! Throws a new T, appending all msg args into a string for the message.
|
||||||
public static void toss(Class<? extends Exception> errorType, Object... msg) throws Exception {
|
static void toss(Class<? extends Exception> errorType, Object... msg) throws Exception {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for(Object s : msg) sb.append(s);
|
for(Object s : msg) sb.append(s);
|
||||||
final java.lang.reflect.Constructor<? extends Exception> ctor =
|
final java.lang.reflect.Constructor<? extends Exception> ctor =
|
||||||
@ -494,16 +588,12 @@ final class Util {
|
|||||||
throw ctor.newInstance(sb.toString());
|
throw ctor.newInstance(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void toss(Object... msg) throws Exception{
|
static void toss(Object... msg) throws Exception{
|
||||||
toss(RuntimeException.class, msg);
|
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.
|
//! Tries to delete the given file, silently ignoring failure.
|
||||||
public static void unlink(String filename){
|
static void unlink(String filename){
|
||||||
try{
|
try{
|
||||||
final java.io.File f = new java.io.File(filename);
|
final java.io.File f = new java.io.File(filename);
|
||||||
f.delete();
|
f.delete();
|
||||||
@ -514,10 +604,10 @@ final class Util {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Appends all entries in argv[1..end] into a space-separated
|
Appends all entries in argv[1..end] into a space-separated
|
||||||
string, argv[0] is not included because it's expected to
|
string, argv[0] is not included because it's expected to be a
|
||||||
be a command name.
|
command name.
|
||||||
*/
|
*/
|
||||||
public static String argvToString(String[] argv){
|
static String argvToString(String[] argv){
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for(int i = 1; i < argv.length; ++i ){
|
for(int i = 1; i < argv.length; ++i ){
|
||||||
if( i>1 ) sb.append(" ");
|
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<min || (max>=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<String,Command> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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<min || (max>=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<String,Command> 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;
|
|
||||||
}
|
|
||||||
}
|
|
16
manifest
16
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.
|
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:19:40.795
|
D 2023-08-10T01:44:48.660
|
||||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||||
@ -231,7 +231,7 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
|
|||||||
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
|
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
|
||||||
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
|
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
|
||||||
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
|
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/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d
|
||||||
F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520
|
F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520
|
||||||
F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328
|
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_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810
|
||||||
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
|
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/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 e7148d734ee13baa8a49508a9a7890914d8d57ff8353fce78b60a875f407cf91
|
||||||
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/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba
|
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-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002
|
||||||
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
|
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.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||||
P 52fa6f78414c41073431c166550806bb8a835bd38cfc1236c9363784c78b81b9
|
P e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848
|
||||||
R e4a7dc0518dd6056979335c8c6e73e83
|
R 4272692fbef77fb1007b0ab4a881bf94
|
||||||
U stephan
|
U stephan
|
||||||
Z 125c5fe250744e87fc135cd6f93efe47
|
Z 62b15385eb7331c7503b86f0cead2232
|
||||||
# Remove this line to create a well-formed Fossil manifest.
|
# Remove this line to create a well-formed Fossil manifest.
|
||||||
|
@ -1 +1 @@
|
|||||||
e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848
|
2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471
|
Reference in New Issue
Block a user