1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Initial skeleton for adding an SQL-driven test script interpreter for the JNI bindings.

FossilOrigin-Name: 2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb
This commit is contained in:
stephan
2023-08-07 21:04:13 +00:00
parent c7f7b45a15
commit 3897a882f7
7 changed files with 358 additions and 10 deletions

View File

@ -0,0 +1,81 @@
package org.sqlite.jni.tester;
import java.util.List;
import java.util.ArrayList;
import static org.sqlite.jni.SQLite3Jni.*;
/**
This class provides an application which aims to implement the
rudimentary SQL-driven test tool described in the accompanying
test-script-interpreter.md.
This is a work in progress.
*/
public class SQLTester {
//! List of input script files.
private java.util.List<String> listInFiles = new ArrayList<>();
private boolean isVerbose = true;
public SQLTester(){
}
public void setVerbose(boolean b){
isVerbose = b;
}
public static <T> void out(T val){
System.out.print(val);
}
public static <T> void outln(T val){
System.out.println(val);
}
@SuppressWarnings("unchecked")
public static <T> void out(T... vals){
int n = 0;
for(T v : vals) out((n++>0 ? " " : "")+v);
}
@SuppressWarnings("unchecked")
public static <T> void outln(T... vals){
out(vals);
out("\n");
}
@SuppressWarnings("unchecked")
private <T> void verbose(T... vals){
if(isVerbose) outln(vals);
}
//! Adds the given test script to the to-test list.
public void addTestScript(String filename){
listInFiles.add(filename);
verbose("Added file",filename);
}
public void runTests() throws Exception {
// process each input file
for(String f : listInFiles){
verbose("Running test script",f);
final TestScript ts = new TestScript(f);
}
}
public static void main(String[] argv) throws Exception{
final SQLTester t = new SQLTester();
for(String a : argv){
if(a.startsWith("-")){
final String flag = a.replaceFirst("-+","");
if( flag.equals("verbose") ){
t.setVerbose(true);
}else if( flag.equals("quiet") ) {
t.setVerbose(false);
}else{
throw new IllegalArgumentException("Unhandled flag: "+flag);
}
}
t.addTestScript(a);
}
t.runTests();
}
}

View File

@ -0,0 +1,36 @@
package org.sqlite.jni.tester;
import java.io.*;
import java.nio.charset.StandardCharsets;
//import java.util.List;
//import java.util.ArrayList;
/**
This class represents a single test script. It handles (or delegates)
its input and parsing. Iteration and evalution are deferred to other,
as-yet-non-existent, classes.
*/
public class TestScript {
//! Test script content.
private String content;
private byte[] readFile(String filename) throws IOException {
return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename));
}
/**
Initializes the script with the content of the given file.
*/
public TestScript(String filename) throws IOException{
this.content = new String(readFile(filename),
StandardCharsets.UTF_8);
}
/**
Initializes the script with the given content, copied
at construction-time.
*/
public TestScript(StringBuffer content){
this.content = content.toString();
}
}

View File

@ -0,0 +1,200 @@
# Specifications For A Rudimentary SQLite Test Script Interpreter
## Overview
The purpose of the Test Script Interpreter is to read and interpret
script files that contain SQL commands and desired results. The
interpreter will check results and report an discrepencies found.
The test script files are ASCII text files. The filename always ends with
".test". Each script is evaluated independently; context does not carry
forward from one script to the next. So, for example, the --null command
run in one test script does not cause any changes in the behavior of
subsequent test scripts. All open database connections are closed
at the end of each test script. All database files created by a test
script are deleted when the script finishes.
## Parsing Rules:
1. Ignore the entire script if the script does not contain the
string "SCRIPT_MODULE_NAME:".
2. Ignore any script that contains the character sequence "\\n\|"
(0x0a, 0x7c). In other words, ignore scripts that contain the
pipe character at the beginning of a line. Such lines represent
test database file content in the "dbtotxt" format. We might add
support for this later, but omit it for the first version.
3. Ignore individual lines that begin with '#' (C-preprocessor lines).
4. If a line begins with exactly two minus signs followed by a
lowercase letter, that is a command. Process commands as described
below.
5. All other lines should be accumulated into the "input buffer".
The various commands will have access to this input buffer.
Some commands will reset the buffer.
## Commands:
Each command looks like an SQL comment. The command begins at the left
margin (no leading space) and starts with exactly 2 minus signs ("-").
The command name consists of lowercase letters and maybe a "-" or two.
Some commands have arguments.
The arguments are separated from the command name by one or more spaces.
Commands have access to the input buffer and might reset the input buffer.
The command can also optionally read (and consume) additional text from
script that comes after the command.
Unknown or unrecognized commands should cause an error message to be
printed and execution to stop.
The initial implemention will only recognize a few commands. Other
commands may be added later. The following is the initial set of
commands:
### The --testcase command
Every test case starts with a --testcase command. The --testcase command
resets both the "input buffer" and the "result buffer".
The argument to the --testcase command is the
name of the test case. That test case name is used for logging and debugging
and when printing errors.
### The --result command
The --result command tries to execute the text in the input buffer as SQL.
For each row of result coming out of this SQL, the text of that result is
appended to the "result buffer". If a result row contains multiple columns,
the columns are processed from left to right. For each column, text is
appended to the result buffer according to the following rules:
* If the result buffer already contains some text, append a space.
(In this way, all column values and all row values are separated from
each other by a single space.)
* If the sqlite3_column_text() returns NULL, then append "nil" - or
some other text that is specified by the --null command - and skip
all subsequent rules.
* If sqlite3_column_text() does not contain any special characters,
append it to the result buffer without any formatting and skip all
subsequent rules.
* If sqlite3_column_text() does not contains curly braces, then put
the text inside of `{...}` and append it and skip all subsequent rules.
* Append the text within double-quotes (`"..."`) and within the text
escape '"' and '\\' by prepending a single '\\' and escape any
control characters (characters less than 0x20) using octal notation:
'\\NNN'.
If an error is encountered while running the SQL, then append the
symbolic C-preprocessor name for the error
code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append
the error message text as if it where a column value. Then stop processing.
After the SQL text has been run, compare the content of the result buffer
against the argument to the --result command and report a testing error if
there are any differences.
The --result command resets the input buffer, but it does not reset
the result buffer. This distinction does not matter for the --result
command itself, but it is important for related commands like --glob
and --notglob. Sometimes test cases will contains a bunch of SQL
followed by multiple --glob and/or --notglob statements. All of the
globs should be evaluted agains the result buffer correct, but the SQL
should only be run once. This is accomplished by resetting the input
buffer but not the result buffer.
### The --glob command
The --glob command works just like --result except that the argument to
--glob is interpreted as a TEST-GLOB pattern and the results are compared
using that glob pattern rather than using strcmp(). Other than that,
the two operate the same.
The TEST-GLOB pattern is slightly different for a standard GLOB:
* The '*' character matches zero or more characters.
* The '?' character matches any single character
* The '[...]' character sequence machines a single character
in between the brackets.
* The '#' character matches one or more digits (This is the main
difference between standard unix-glob and TEST-GLOB. unix-glob
does not have this feature. It was added to because it comes
up a lot during SQLite testing.)
### The --notglob command
The --notglob command works just like --glob except that it reports an
error if the GLOB does match, rather than if the GLOB does not matches.
### The --oom command
This command is to be used for out-of-memory testing. It means that
OOM errors should be simulated to ensure that SQLite is able to deal with
them. This command can be silently ignored for now. We might add support
for this later.
### The --tableresult command
The --tableresult command works like --glob except that the GLOB pattern
to be matched is taken from subsequent lines of the input script up to
the next --end. Every span of one or more whitespace characters in this
pattern text is collapsed into a single space (0x20).
Leading and trailing whitespace are removed from the pattern.
The --end that ends the GLOB pattern is not part of the GLOB pattern, but
the --end is consumed from the script input.
### The --new and --open commands
The --new and --open commands cause a database file to be opened.
The name of the file is the argument to the command. The --new command
opens an initially empty database (it deletes the file before opening it)
whereas the --open command opens an existing database if it already
exists.
### The --db command
The script interpreter can have up to 7 different SQLite database
connections open at a time. The --db command is used to switch between
them. The argument to --db is an integer between 0 and 6 that selects
which database connection to use moving forward.
### The --close command
The --close command causes an existing database connetion to close.
This command is a no-op if the database connection is not currently
open. There can be up to 7 different database connections, numbered
0 through 6. The number of the database connection to close is an argument
to the --close command. Or if the argument to --close is "all" then all
open database connections are closed.
### The --null command
The NULL command changes the text that is used to represent SQL NULL
values in the result buffer.
### The --run command
The --run command executes text in the input buffer as if it where SQL.
However, nothing is added to the result buffer. Any output from the SQL
is silently ignored. Errors in the SQL are silently ignored.
The --run command normally executes the SQL in the current database
connection. However, if --run has an argument that is an integer between
0 and 6 then the SQL is run in the alternative database connection specified
by that argument.
### The --json and --json-block commands
The --json and --json-block commands work like --result and --tableresult,
respectively. The difference is that column values are appended to the
result buffer literally, without every enclosing the values in `{...}` or
`"..."` and without escaping any characters in the column value and comparison
is always an exact strcmp() not a GLOB.

View File

@ -0,0 +1,4 @@
/* A script for testing the org.sqlite.jni.tester infrastructure */
# this line is ignored