diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 39047fa98e..03bde86fdd 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -234,7 +234,7 @@ public class SQLTester { sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } sqlite3 getDbById(int id) throws Exception{ - return affirmDbId(id).aDb[iCurrentDb]; + return affirmDbId(id).aDb[id]; } void closeDb(int id) throws Exception{ diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java index 07533a6d4a..ee85b72bd8 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java @@ -12,6 +12,8 @@ ** 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.*; @@ -22,6 +24,12 @@ class SQLTestException extends RuntimeException { } } +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"); @@ -73,23 +81,221 @@ abstract class Command2 { } } +//! --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(),": "); - if( 1==argv.length ){ - st.outln( st.getInputText() ); + final String body = ts.fetchCommandBody(); + if( 1==argv.length && null==body ){ + st.out( st.getInputText() ); }else{ st.outln( Util.argvToString(argv) ); } - final String body = ts.fetchCommandBody(); if( null!=body ){ - st.out(body,"\n"); + st.out(body); } } } +//! --result command +class ResultCommand2 extends Command2 { + private final ResultBufferMode bufferMode; + protected ResultCommand2(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand2(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand2 extends Command2 { + private final boolean jsonMode; + protected TableResultCommand2(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand2(){ this(false); } + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,1); + // TODO?: do something with the test name + final String body = ts.fetchCommandBody(); + t.clearResultBuffer(); + t.clearInputBuffer().append(null==body ? "" : body); + } +} + class CommandDispatcher2 { private static java.util.Map commandMap = @@ -103,7 +309,21 @@ class CommandDispatcher2 { 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); @@ -187,11 +407,10 @@ class TestScript2 { return "["+(moduleName==null ? filename : moduleName)+"] line "+ cur.lineNo; } - @SuppressWarnings("unchecked") - private TestScript2 verbose(Object... vals){ + private TestScript2 verboseN(int level, Object... vals){ final int verbosity = outer.getVerbosity(); - if(verbosity>0){ + if(verbosity>=level){ outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), getOutputPrefix(),": "); outer.outln(vals); @@ -199,6 +418,9 @@ class TestScript2 { 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(),": "); @@ -355,7 +577,7 @@ class TestScript2 { if(line.startsWith("#")){ throw new IncompatibleDirective(this, "C-preprocessor input: "+line); }else if(line.startsWith("---")){ - new IncompatibleDirective(this, "Triple-dash: "+line); + new IncompatibleDirective(this, "triple-dash: "+line); } Matcher m = patternScriptModuleName.matcher(line); if( m.find() ){ @@ -370,12 +592,19 @@ class TestScript2 { 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){ + public boolean isCommandLine(String line, boolean checkForImpl){ final Matcher m = patternCommand.matcher(line); - return m.find(); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); + } + return rc; } /** @@ -388,33 +617,45 @@ class TestScript2 { } /** - Fetches lines until the next command. Throws if + 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 is trim()'d of - leading/trailing whitespace. + 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) ){ + if( !isCommandLine(line, true) ){ sb.append(line).append("\n"); consumePeeked(); }else{ break; } } - line = sb.toString().trim(); - return line.isEmpty() ? null : line; + line = sb.toString(); + return line.trim().isEmpty() ? null : line; } public void processCommand(SQLTester t, String[] argv) throws Exception{ - //verbose("got argv: ",argv[0], " ", Util.argvToString(argv)); - //verbose("Input buffer = ",t.getInputBuffer()); + 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. */ diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test2 index 5511291878..3ad4c39680 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test2 @@ -1,4 +1,4 @@ - /* +/* ** This is a comment. There are many like it but this one is mine. ** ** SCRIPT_MODULE_NAME: sanity-check @@ -8,14 +8,35 @@ ** REQUIRED_PROPERTIES: ** */ - -/*#if foo*/ -/*---foo*/ -/* --print without flags dumps current input buffer */ ---print - -non-command/non-directive text after --print is also emitted. ---print 🤩😃 - SELECT 1; - SELECT 2; ---print the end +--close all +--oom +--db 0 +--new my.db +--null zilch +--testcase 1.0 +SELECT 1, null; +--result 1 zilch +--glob *zil* +--notglob *ZIL* +SELECT 1, 2; +intentional error; +--run +--testcase json-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase tableresult-1 + select 1, 'a'; + select 2, 'b'; +--tableresult + # [a-z] + 2 b +--end +--testcase json-block-1 + select json_array(1,2,3); + select json_object('a',1,'b',2); +--json-block + [1,2,3] + {"a":1,"b":2} +--end +--close +--print 🤩😃 the end diff --git a/manifest b/manifest index cd7f7de5bb..8119a67605 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sREQUIRED_PROPERTIES\shandling\sto\snot\sfail\sif\sthere\sare\sno\sproperties. -D 2023-08-09T22:30:10.275 +C Port\sthe\sSQLTester\s'v1'\scommands\sto\sthe\s'v2'\sevaluation\sbits.\sStill\sTODO\sis\sswapping\sout\sv1\swith\sthese\sseparate\simpls. +D 2023-08-09T23:47:14.521 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,11 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ae38d872d2cb582e1a1abd67b5e9c276bf2f610cacc918428b63c668131642e F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 3fc6700ab92e614f61856eeb87469589e57342cb66f5c4f9de425b45425f278f +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/tests/000-000-sanity.test2 dca0364ca25dacdff38355870fee51be7112eade930b3597c808c2f88c44a782 +F ext/jni/src/tests/000-000-sanity.test2 dfbcccc7b3548ae56deb2ef8fe17dd9235a81fbd552536ef9202284549c7fcf3 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2092,8 +2092,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 -R c8b3185b3c963903b037db53ffaa5711 +P 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b +R eed3d04c8636ba991a620a2a8f5a013d U stephan -Z f51d1e9a0eee42db59afa92b063b01d4 +Z bdeddd00da26b413ab473bf39ad731fc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c0b4f14f97..d522acb938 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b \ No newline at end of file +0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 \ No newline at end of file