mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Replace the SQLTester infrastructure with a line-oriented, non-regex-heavy parser. Add --column-names command.
FossilOrigin-Name: 88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d
This commit is contained in:
@ -87,7 +87,6 @@ JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\
|
|||||||
Outer.java \
|
Outer.java \
|
||||||
SQLTester.java \
|
SQLTester.java \
|
||||||
TestScript.java \
|
TestScript.java \
|
||||||
TestScript2.java \
|
|
||||||
)
|
)
|
||||||
|
|
||||||
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
CLASS_FILES.main := $(JAVA_FILES.main:.java=.class)
|
||||||
|
@ -21,19 +21,6 @@ import java.util.regex.*;
|
|||||||
import org.sqlite.jni.*;
|
import org.sqlite.jni.*;
|
||||||
import static org.sqlite.jni.SQLite3Jni.*;
|
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
|
Modes for how to handle SQLTester.execSql()'s
|
||||||
@ -55,6 +42,12 @@ enum ResultRowMode {
|
|||||||
NEWLINE
|
NEWLINE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SQLTesterException extends RuntimeException {
|
||||||
|
public SQLTesterException(String msg){
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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
|
||||||
@ -76,6 +69,7 @@ public class SQLTester {
|
|||||||
private int nTestFile = 0;
|
private int nTestFile = 0;
|
||||||
private int nAbortedScript = 0;
|
private int nAbortedScript = 0;
|
||||||
private int nTest;
|
private int nTest;
|
||||||
|
private boolean emitColNames;
|
||||||
private final sqlite3[] aDb = new sqlite3[7];
|
private final sqlite3[] aDb = new sqlite3[7];
|
||||||
private int iCurrentDb = 0;
|
private int iCurrentDb = 0;
|
||||||
private final String initialDbName = "test.db";
|
private final String initialDbName = "test.db";
|
||||||
@ -95,6 +89,8 @@ public class SQLTester {
|
|||||||
return this.outer.isVerbose();
|
return this.outer.isVerbose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void outputColumnNames(boolean b){ emitColNames = b; }
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void verbose(Object... vals){
|
public void verbose(Object... vals){
|
||||||
outer.verbose(vals);
|
outer.verbose(vals);
|
||||||
@ -125,56 +121,29 @@ public class SQLTester {
|
|||||||
return currentScript;
|
return currentScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runTests() throws Exception {
|
private 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 {
|
|
||||||
for(String f : listInFiles){
|
for(String f : listInFiles){
|
||||||
reset();
|
reset();
|
||||||
setupInitialDb();
|
setupInitialDb();
|
||||||
++nTestFile;
|
++nTestFile;
|
||||||
final TestScript2 ts = new TestScript2(f);
|
final TestScript ts = new TestScript(f);
|
||||||
|
outln("----->>>>> running [",f,"]");
|
||||||
try{
|
try{
|
||||||
ts.run(this);
|
ts.run(this);
|
||||||
}catch(SkipTestRemainder2 e){
|
}catch(UnknownCommand e){
|
||||||
/* not fatal */
|
/* currently not fatal */
|
||||||
outln(e);
|
outln(e);
|
||||||
++nAbortedScript;
|
++nAbortedScript;
|
||||||
}catch(IncompatibleDirective e){
|
}catch(IncompatibleDirective e){
|
||||||
/* not fatal */
|
/* not fatal */
|
||||||
outln(e);
|
outln(e);
|
||||||
++nAbortedScript;
|
++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);
|
Util.unlink(initialDbName);
|
||||||
}
|
}
|
||||||
@ -264,7 +233,7 @@ public class SQLTester {
|
|||||||
if( 0!=rc ){
|
if( 0!=rc ){
|
||||||
final String msg = sqlite3_errmsg(db);
|
final String msg = sqlite3_errmsg(db);
|
||||||
sqlite3_close(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);
|
rc," and message: ",msg);
|
||||||
}
|
}
|
||||||
return aDb[iCurrentDb] = db;
|
return aDb[iCurrentDb] = db;
|
||||||
@ -364,70 +333,89 @@ public class SQLTester {
|
|||||||
final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
|
final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
|
||||||
? null : resultBuffer;
|
? null : resultBuffer;
|
||||||
//outln("sqlChunk len= = ",sqlChunk.length);
|
//outln("sqlChunk len= = ",sqlChunk.length);
|
||||||
while(pos < sqlChunk.length){
|
try{
|
||||||
if(pos > 0){
|
while(pos < sqlChunk.length){
|
||||||
sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
|
if(pos > 0){
|
||||||
sqlChunk.length);
|
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<EOSQL>");*/
|
|
||||||
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;
|
if( 0==sqlChunk.length ) break;
|
||||||
}
|
rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
|
||||||
pos = oTail.getValue();
|
/*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ",
|
||||||
stmt = outStmt.getValue();
|
new String(sqlChunk,StandardCharsets.UTF_8),"\n<EOSQL>");*/
|
||||||
if( null == stmt ){
|
if( 0!=rc ){
|
||||||
// empty statement was parsed.
|
if(throwOnError){
|
||||||
continue;
|
Util.toss(RuntimeException.class, "db op failed with rc="
|
||||||
}
|
+rc+": "+sqlite3_errmsg(db));
|
||||||
if( null!=sb ){
|
}else if( null!=sb ){
|
||||||
// Add the output to the result buffer...
|
appendDbErr(db, sb, rc);
|
||||||
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;
|
|
||||||
}
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos = oTail.getValue();
|
||||||
|
stmt = outStmt.getValue();
|
||||||
|
if( null == stmt ){
|
||||||
|
// empty statement was parsed.
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){}
|
|
||||||
}
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
|
|
||||||
else if( rc!=0 ){
|
|
||||||
if( null!=sb ){
|
if( null!=sb ){
|
||||||
appendDbErr(db, sb, rc);
|
// 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}finally{
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
if( 0!=rc && throwOnError ){
|
if( 0!=rc && throwOnError ){
|
||||||
Util.toss(RuntimeException.class, "db op failed with rc="
|
Util.toss(RuntimeException.class, "db op failed with rc="
|
||||||
+rc+": "+sqlite3_errmsg(db));
|
+rc+": "+sqlite3_errmsg(db));
|
||||||
@ -443,8 +431,6 @@ public class SQLTester {
|
|||||||
final String flag = a.replaceFirst("-+","");
|
final String flag = a.replaceFirst("-+","");
|
||||||
if( flag.equals("verbose") ){
|
if( flag.equals("verbose") ){
|
||||||
t.setVerbosity(t.getVerbosity() + 1);
|
t.setVerbosity(t.getVerbosity() + 1);
|
||||||
}else if( flag.equals("2") ){
|
|
||||||
v2 = true;
|
|
||||||
}else{
|
}else{
|
||||||
throw new IllegalArgumentException("Unhandled flag: "+flag);
|
throw new IllegalArgumentException("Unhandled flag: "+flag);
|
||||||
}
|
}
|
||||||
@ -452,11 +438,13 @@ public class SQLTester {
|
|||||||
}
|
}
|
||||||
t.addTestScript(a);
|
t.addTestScript(a);
|
||||||
}
|
}
|
||||||
if( v2 ) t.runTests2();
|
try {
|
||||||
else t.runTests();
|
t.runTests();
|
||||||
t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
|
}finally{
|
||||||
if( t.nAbortedScript > 0 ){
|
t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
|
||||||
t.outln("Aborted ",t.nAbortedScript," script(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<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);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! 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<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 "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.
|
General utilities for the SQLTester bits.
|
||||||
*/
|
*/
|
||||||
|
@ -12,29 +12,384 @@
|
|||||||
** This file contains the TestScript part of the SQLTester framework.
|
** This file contains the TestScript part of the SQLTester framework.
|
||||||
*/
|
*/
|
||||||
package org.sqlite.jni.tester;
|
package org.sqlite.jni.tester;
|
||||||
import java.util.List;
|
import static org.sqlite.jni.SQLite3Jni.*;
|
||||||
import java.util.ArrayList;
|
import org.sqlite.jni.sqlite3;
|
||||||
import java.io.*;
|
import java.util.Arrays;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.regex.*;
|
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);
|
||||||
|
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<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
|
This class represents a single test script. It handles (or
|
||||||
delegates) its the reading-in and parsing, but the details of
|
delegates) its the reading-in and parsing, but the details of
|
||||||
evaluation are delegated elsewhere.
|
evaluation are delegated elsewhere.
|
||||||
*/
|
*/
|
||||||
class TestScript {
|
class TestScript {
|
||||||
private String name = null;
|
private String filename = null;
|
||||||
private String moduleName = null;
|
private String moduleName = null;
|
||||||
private List<CommandChunk> chunks = null;
|
private final Cursor cur = new Cursor();
|
||||||
private final Outer outer = new Outer();
|
private final Outer outer = new Outer();
|
||||||
private String ignoreReason = null;
|
|
||||||
private byte[] baScript = null;
|
|
||||||
|
|
||||||
/* One "chunk" of input, representing a single command and
|
private static final class Cursor {
|
||||||
its optional body content. */
|
private final StringBuilder sb = new StringBuilder();
|
||||||
private static final class CommandChunk {
|
byte[] src = null;
|
||||||
public String[] argv = null;
|
int pos = 0;
|
||||||
public String content = null;
|
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 {
|
private byte[] readFile(String filename) throws Exception {
|
||||||
@ -43,229 +398,289 @@ class TestScript {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Initializes the script with the content of the given file.
|
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{
|
public TestScript(String filename) throws Exception{
|
||||||
name = filename;
|
this.filename = filename;
|
||||||
baScript = readFile(filename);
|
setVerbosity(2);
|
||||||
setContent(new String(
|
cur.src = readFile(filename);
|
||||||
baScript, java.nio.charset.StandardCharsets.UTF_8
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getFilename(){
|
||||||
Initializes the script with the given content, copied at
|
return filename;
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setContent(String c){
|
|
||||||
this.chunks = chunkContent(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName(){
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getModuleName(){
|
public String getModuleName(){
|
||||||
return moduleName;
|
return moduleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnored(){
|
|
||||||
return null!=ignoreReason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIgnoredReason(){
|
|
||||||
return ignoreReason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVerbosity(int level){
|
public void setVerbosity(int level){
|
||||||
outer.setVerbosity(level);
|
outer.setVerbosity(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOutputPrefix(){
|
||||||
|
return "["+(moduleName==null ? filename : moduleName)+"] line "+
|
||||||
|
cur.lineNo;
|
||||||
|
}
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private TestScript verbose(Object... vals){
|
private TestScript verboseN(int level, Object... vals){
|
||||||
outer.verbose(vals);
|
final int verbosity = outer.getVerbosity();
|
||||||
|
if(verbosity>=level){
|
||||||
|
outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),
|
||||||
|
getOutputPrefix(),": ");
|
||||||
|
outer.outln(vals);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern patternHashLine =
|
private TestScript verbose1(Object... vals){return verboseN(1,vals);}
|
||||||
Pattern.compile("^#", Pattern.MULTILINE);
|
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 =
|
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 =
|
||||||
Returns true if the given script content should be ignored
|
Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
|
||||||
(because it contains certain content which indicates such).
|
private static final Pattern patternMixedModuleName =
|
||||||
*/
|
Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
|
||||||
private boolean shouldBeIgnored(String content){
|
private static final Pattern patternCommand =
|
||||||
if( null == moduleName ){
|
Pattern.compile("^--(([a-z-]+)( .*)?)$");
|
||||||
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;
|
|
||||||
}
|
|
||||||
Matcher m = patternHashLine.matcher(content);
|
|
||||||
if( m.find() ){
|
|
||||||
ignoreReason = "C-preprocessor line found.";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m = patternRequiredProperties.matcher(content);
|
|
||||||
if( m.find() ){
|
|
||||||
ignoreReason = "REQUIRED_PROPERTIES found: "+m.group(1).trim();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Chop script up into chunks containing individual commands and
|
Looks for "directives." If a compatible one is found, it is
|
||||||
their inputs. The approach taken here is not as robust as
|
processed and this function returns. If an incompatible one is found,
|
||||||
line-by-line parsing would be but the framework is structured
|
a description of it is returned and processing of the test must
|
||||||
such that we could replace this part without unduly affecting the
|
end immediately.
|
||||||
evaluation bits. The potential problems with this approach
|
|
||||||
include:
|
|
||||||
|
|
||||||
- It's potentially possible that it will strip content out of a
|
|
||||||
testcase block.
|
|
||||||
|
|
||||||
- It loses all file location information, so we can't report line
|
|
||||||
numbers of errors.
|
|
||||||
|
|
||||||
If/when that becomes a problem, it can be refactored.
|
|
||||||
*/
|
*/
|
||||||
private List<CommandChunk> chunkContent(String content){
|
private void checkForDirective(String line) throws IncompatibleDirective {
|
||||||
findModuleName(content);
|
if(line.startsWith("#")){
|
||||||
if( shouldBeIgnored(content) ){
|
throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
|
||||||
chunks = null;
|
}else if(line.startsWith("---")){
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// First, strip out any content which we know we can ignore...
|
boolean isCommandLine(String line, boolean checkForImpl){
|
||||||
final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]";
|
final Matcher m = patternCommand.matcher(line);
|
||||||
final String s3Dash = "^---+[^\\n]*\\n";
|
boolean rc = m.find();
|
||||||
final String sEmptyLine = "^\\n";
|
if( rc && checkForImpl ){
|
||||||
final String sOom = "^--oom\\n"
|
rc = null!=CommandDispatcher2.getCommandByName(m.group(2));
|
||||||
/* 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<String> lPats = new ArrayList<>();
|
|
||||||
lPats.add(sCComment);
|
|
||||||
lPats.add(s3Dash);
|
|
||||||
lPats.add(sEmptyLine);
|
|
||||||
lPats.add(sOom);
|
|
||||||
//verbose("Content:").verbose(content).verbose("<EOF>");
|
|
||||||
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<String> blocks = new ArrayList<>();
|
|
||||||
final Pattern p = Pattern.compile(
|
|
||||||
"^--(?!end)[a-z]+", Pattern.MULTILINE
|
|
||||||
// --end is a marker used by --tableresult and --(not)glob.
|
|
||||||
);
|
|
||||||
final Matcher m = p.matcher(content);
|
|
||||||
int ndxPrev = 0, pos = 0, i = 0;
|
|
||||||
//verbose("Trimmed content:").verbose(content).verbose("<EOF>");
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Next, convert those blocks into higher-level CommandChunks...
|
|
||||||
final List<CommandChunk> rc = new ArrayList<>();
|
|
||||||
for( String block : blocks ){
|
|
||||||
final CommandChunk chunk = new CommandChunk();
|
|
||||||
final String[] parts = block.split("\\n", 2);
|
|
||||||
chunk.argv = parts[0].split("\\s+");
|
|
||||||
if( parts.length>1 && parts[1].length()>0 ){
|
|
||||||
chunk.content = parts[1]
|
|
||||||
/* 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 );
|
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Runs this test script in the context of the given tester object.
|
If line looks like a command, returns an argv for that command
|
||||||
|
invocation, else returns null.
|
||||||
*/
|
*/
|
||||||
public void run(SQLTester tester) throws Exception {
|
String[] getCommandArgv(String line){
|
||||||
final int verbosity = tester.getVerbosity();
|
final Matcher m = patternCommand.matcher(line);
|
||||||
if( null==chunks ){
|
return m.find() ? m.group(1).trim().split("\\s+") : null;
|
||||||
outer.outln("This test contains content which forces it to be skipped.");
|
}
|
||||||
}else{
|
|
||||||
int n = 0;
|
/**
|
||||||
for(CommandChunk chunk : chunks){
|
Fetches lines until the next recognized command. Throws if
|
||||||
if(verbosity>0){
|
checkForDirective() does. Returns null if there is no input or
|
||||||
outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),moduleName,
|
it's only whitespace. The returned string retains all whitespace.
|
||||||
" #",++n," ",chunk.argv[0],
|
|
||||||
" ",Util.argvToString(chunk.argv));
|
Note that "subcommands", --command-like constructs in the body
|
||||||
if(verbosity>1 && null!=chunk.content){
|
which do not match a known command name are considered to be
|
||||||
outer.out("\n", chunk.content);
|
content, not commands.
|
||||||
}
|
*/
|
||||||
outer.out("\n");
|
String fetchCommandBody(){
|
||||||
}
|
final StringBuilder sb = new StringBuilder();
|
||||||
CommandDispatcher.dispatch(
|
String line;
|
||||||
tester, chunk.argv,
|
while( (null != (line = peekLine())) ){
|
||||||
(null==chunk.content) ? null : chunk.content.trim()
|
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,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<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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! --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<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 "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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,7 @@
|
|||||||
** REQUIRED_PROPERTIES:
|
** REQUIRED_PROPERTIES:
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
--print starting up 😃
|
||||||
--close all
|
--close all
|
||||||
--oom
|
--oom
|
||||||
--db 0
|
--db 0
|
||||||
@ -38,5 +39,13 @@ SELECT json_array(1,2,3)
|
|||||||
[1,2,3]
|
[1,2,3]
|
||||||
{"a":1,"b":2}
|
{"a":1,"b":2}
|
||||||
--end
|
--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
|
--close
|
||||||
--print 🤩😃 the end
|
--print reached the end 😃
|
@ -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
|
|
22
manifest
22
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.
|
C Replace\sthe\sSQLTester\sinfrastructure\swith\sa\sline-oriented,\snon-regex-heavy\sparser.\sAdd\s--column-names\scommand.
|
||||||
D 2023-08-09T23:47:14.521
|
D 2023-08-10T00:34:38.136
|
||||||
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
|
||||||
@ -230,7 +230,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 d7300b7e124214afde7f11bddd5c0d336a9be0220fe2b74e787078e1aa2db778
|
F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8
|
||||||
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,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_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/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b
|
||||||
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ae38d872d2cb582e1a1abd67b5e9c276bf2f610cacc918428b63c668131642e
|
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354
|
||||||
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28
|
F ext/jni/src/org/sqlite/jni/tester/TestScript.java 496b402c7faedf18be41542c6dc77c19f2735663821a5973639eb614e33aa707
|
||||||
F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 25895a534a1e4634268beecd1a689bdfc0aafbfe32071c27b5189ccb8aeec31e
|
|
||||||
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.test2 dfbcccc7b3548ae56deb2ef8fe17dd9235a81fbd552536ef9202284549c7fcf3
|
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_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd
|
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 w ext/jni/src/tests/010_ignored.test
|
||||||
F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
|
|
||||||
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
|
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
|
||||||
F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
|
F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
|
||||||
F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86
|
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.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 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b
|
P 0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6
|
||||||
R eed3d04c8636ba991a620a2a8f5a013d
|
R aaacd016e2b2dfc7887fd83bed604d98
|
||||||
U stephan
|
U stephan
|
||||||
Z bdeddd00da26b413ab473bf39ad731fc
|
Z fb4c2d6a86f69138bd39103974c9d8cc
|
||||||
# Remove this line to create a well-formed Fossil manifest.
|
# Remove this line to create a well-formed Fossil manifest.
|
||||||
|
@ -1 +1 @@
|
|||||||
0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6
|
88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d
|
Reference in New Issue
Block a user