1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Add more JNI docs, tests, and a handful of Java-side overloads.

FossilOrigin-Name: d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0
This commit is contained in:
stephan
2023-08-24 11:57:51 +00:00
parent 8cafdfa916
commit bfdc7ab5a7
11 changed files with 395 additions and 138 deletions

View File

@ -1429,7 +1429,7 @@ typedef struct {
} ResultJavaVal;
/* For use with sqlite3_result/value_pointer() */
#define ResultJavaValuePtrStr "ResultJavaVal"
#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal"
/*
** Allocate a new ResultJavaVal and assign it a new global ref of
@ -1915,15 +1915,10 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
int rc;
if(!baData){
rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx);
}else{
jbyte * const pBuf = JBA_TOC(baData);
rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax,
SQLITE_TRANSIENT);
JBA_RELEASE(baData,pBuf);
}
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData,pBuf);
return (jint)rc;
}
@ -1960,15 +1955,21 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName
JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
if(baData){
jbyte * const pBuf = JBA_TOC(baData);
int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf,
(int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData, pBuf);
return (jint)rc;
}else{
return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
(const char *)pBuf,
(int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData, pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
JBA_RELEASE(baData, pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
@ -3595,18 +3596,19 @@ JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
}
static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){
int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal));
sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
int const nLen = sqlite3_value_bytes16(sv);
jbyteArray jba;
const jbyte * pBytes;
switch(mode){
case SQLITE_UTF16:
pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal));
pBytes = sqlite3_value_text16(sv);
break;
case SQLITE_UTF16LE:
pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal));
pBytes = sqlite3_value_text16le(sv);
break;
case SQLITE_UTF16BE:
pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal));
pBytes = sqlite3_value_text16be(sv);
break;
default:
assert(!"not possible");

View File

@ -843,6 +843,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text
(JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_bind_text16
* Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text16
(JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_bind_zeroblob

View File

@ -14,24 +14,24 @@
package org.sqlite.jni;
/**
A callback for use with sqlite3_auto_extension().
A callback for use with the sqlite3_auto_extension() family of
APIs.
*/
public interface AutoExtension {
/**
Must function as described for a sqlite3_auto_extension()
callback, with the caveat that the signature is more limited.
callback, with the caveat that the signature is shorter.
As an exception (as it were) to the callbacks-must-not-throw
rule, AutoExtensions may throw and the exception's error message
AutoExtensions may throw and the exception's error message
will be set as the db's error string.
Hints for implementations:
Tips for implementations:
- Opening a database from an auto-extension handler will lead to
an endless recursion of the auto-handler triggering itself
indirectly for each newly-opened database.
- If this routine is stateful, it is a good idea to make the
- If this routine is stateful, it may be useful to make the
overridden method synchronized.
- Results are undefined if db is closed by an auto-extension.

View File

@ -154,32 +154,35 @@ public final class SQLite3Jni {
Functions almost as documented for the C API, with these
exceptions:
- The callback interface is more limited because of
cross-language differences. Specifically, auto-extensions do
not have access to the sqlite3_api object which native
auto-extensions do.
- The callback interface is is shorter because of cross-language
differences. Specifically, 3rd argument to the C auto-extension
callback interface is unnecessary here.
- If the list of auto-extensions is manipulated from an
auto-extension, it is undefined which, if any, auto-extensions
will subsequently execute for the current database (it depends
on multiple factors).
The C API docs do not specifically say so, if the list of
auto-extensions is manipulated from an auto-extension, it is
undefined which, if any, auto-extensions will subsequently
execute for the current database.
See the AutoExtension class docs for more information.
*/
public static native int sqlite3_auto_extension(@NotNull AutoExtension callback);
/**
Results are undefined if data is not null and n<0 || n>=data.length.
*/
public static native int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
);
public static int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
){
return (null == data)
return (null==data)
? sqlite3_bind_null(stmt, ndx)
: sqlite3_bind_blob(stmt, ndx, data, data.length);
}
private static native int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
);
public static native int sqlite3_bind_double(
@NotNull sqlite3_stmt stmt, int ndx, double v
);
@ -200,13 +203,10 @@ public final class SQLite3Jni {
@NotNull sqlite3_stmt stmt
);
/**
A level of indirection required to ensure that the input to the
C-level function of the same name is a NUL-terminated UTF-8
string.
Requires that paramName be a NUL-terminated UTF-8 string.
*/
private static native int sqlite3_bind_parameter_index(
public static native int sqlite3_bind_parameter_index(
@NotNull sqlite3_stmt stmt, byte[] paramName
);
@ -218,14 +218,22 @@ public final class SQLite3Jni {
}
/**
Works like the C-level sqlite3_bind_text() but (A) assumes
SQLITE_TRANSIENT for the final parameter and (B) behaves like
sqlite3_bind_null() if the data argument is null.
Works like the C-level sqlite3_bind_text() but assumes
SQLITE_TRANSIENT for the final C API parameter.
Results are undefined if data is not null and
maxBytes>=data.length. If maxBytes is negative then results are
undefined if data is not null and does not contain a NUL byte.
*/
private static native int sqlite3_bind_text(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
);
/**
Converts data, if not null, to a UTF-8-encoded byte array and
binds it as such, returning the result of the C-level
sqlite3_bind_null() or sqlite3_bind_text().
*/
public static int sqlite3_bind_text(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
){
@ -234,6 +242,9 @@ public final class SQLite3Jni {
return sqlite3_bind_text(stmt, ndx, utf8, utf8.length);
}
/**
Requires that data be null or in UTF-8 encoding.
*/
public static int sqlite3_bind_text(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
){
@ -242,6 +253,41 @@ public final class SQLite3Jni {
: sqlite3_bind_text(stmt, ndx, data, data.length);
}
/**
Identical to the sqlite3_bind_text() overload with the same
signature but requires that its input be encoded in UTF-16 in
platform byte order.
*/
private static native int sqlite3_bind_text16(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
);
/**
Converts its string argument to UTF-16 and binds it as such, returning
the result of the C-side function of the same name. The 3rd argument
may be null.
*/
public static int sqlite3_bind_text16(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
){
if(null == data) return sqlite3_bind_null(stmt, ndx);
final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
return sqlite3_bind_text16(stmt, ndx, bytes, bytes.length);
}
/**
Requires that data be null or in UTF-16 encoding in platform byte
order. Returns the result of the C-level sqlite3_bind_null() or
sqlite3_bind_text().
*/
public static int sqlite3_bind_text16(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
){
return (null == data)
? sqlite3_bind_null(stmt, ndx)
: sqlite3_bind_text16(stmt, ndx, data, data.length);
}
public static native int sqlite3_bind_zeroblob(
@NotNull sqlite3_stmt stmt, int ndx, int n
);
@ -253,8 +299,7 @@ public final class SQLite3Jni {
/**
As for the C-level function of the same name, with a BusyHandler
instance in place of a callback function. Pass it a null handler
to clear the busy handler. Calling this multiple times with the
same object is a no-op on the second and subsequent calls.
to clear the busy handler.
*/
public static native int sqlite3_busy_handler(
@NotNull sqlite3 db, @Nullable BusyHandler handler
@ -595,15 +640,41 @@ public final class SQLite3Jni {
@Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
);
/**
Convenience overload which returns its db handle directly. The returned
object might not have been successfully opened: use sqlite3_errcode() to
check whether it is in an error state.
Ownership of the returned value is passed to the caller, who must eventually
pass it to sqlite3_close() or sqlite3_close_v2().
*/
public static sqlite3 sqlite3_open(@Nullable String filename){
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
sqlite3_open(filename, out);
return out.take();
};
public static native int sqlite3_open_v2(
@Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
int flags, @Nullable String zVfs
);
/**
Has the same semantics as the sqlite3-returning sqlite3_open()
but uses sqlite3_open_v2() instead of sqlite3_open().
*/
public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
@Nullable String zVfs){
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
sqlite3_open_v2(filename, out, flags, zVfs);
return out.take();
};
/**
The sqlite3_prepare() family of functions require slightly
different signatures than their native counterparts, but
overloading allows us to install several convenience forms.
different signatures than their native counterparts, but (A) they
retain functionally equivalent semantics and (B) overloading
allows us to install several convenience forms.
All of them which take their SQL in the form of a byte[] require
that it be in UTF-8 encoding unless explicitly noted otherwise.
@ -648,6 +719,26 @@ public final class SQLite3Jni {
return sqlite3_prepare(db, utf8, utf8.length, outStmt, null);
}
/**
Convenience overload which returns its statement handle directly,
or null on error or when reading only whitespace or
comments. sqlite3_errcode() can be used to determine whether
there was an error or the input was empty. Ownership of the
returned object is passed to the caller, who must eventually pass
it to sqlite3_finalize().
*/
public static sqlite3_stmt sqlite3_prepare(
@NotNull sqlite3 db, @NotNull String sql
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare(db, sql, out);
return out.take();
}
/**
See sqlite3_prepare() for details about the slight API differences
from the C API.
*/
private static native int sqlite3_prepare_v2(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
@NotNull OutputPointer.sqlite3_stmt outStmt,
@ -677,6 +768,18 @@ public final class SQLite3Jni {
return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null);
}
/**
Works identically to the sqlite3_stmt-returning sqlite3_prepare()
but uses sqlite3_prepare_v2().
*/
public static sqlite3_stmt sqlite3_prepare_v2(
@NotNull sqlite3 db, @NotNull String sql
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare_v2(db, sql, out);
return out.take();
}
private static native int sqlite3_prepare_v3(
@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
@ -706,6 +809,18 @@ public final class SQLite3Jni {
return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null);
}
/**
Works identically to the sqlite3_stmt-returning sqlite3_prepare()
but uses sqlite3_prepare_v3().
*/
public static sqlite3_stmt sqlite3_prepare_v3(
@NotNull sqlite3 db, @NotNull String sql, int prepFlags
){
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
sqlite3_prepare_v3(db, sql, prepFlags, out);
return out.take();
}
/**
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns

View File

@ -21,6 +21,16 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
An annotation for Tester1 tests which we do not want to run in
reflection-driven test mode because either they are not suitable
for multi-threaded threaded mode or we have to control their execution
order.
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface ManualTest{}
public class Tester1 implements Runnable {
//! True when running in multi-threaded mode.
private static boolean mtMode = false;
@ -30,6 +40,10 @@ public class Tester1 implements Runnable {
private static boolean shuffle = false;
//! True to dump the list of to-run tests to stdout.
private static boolean listRunTests = false;
//! True to squelch all out() and outln() output.
private static boolean quietMode = false;
//! Total number of runTests() calls.
private static int nTestRuns = 0;
//! List of test*() methods to run.
private static List<java.lang.reflect.Method> testMethods = null;
//! List of exceptions collected by run()
@ -48,27 +62,37 @@ public class Tester1 implements Runnable {
static final Metrics metrics = new Metrics();
public synchronized static void outln(){
System.out.println("");
if( !quietMode ){
System.out.println("");
}
}
public synchronized static void outln(Object val){
System.out.print(Thread.currentThread().getName()+": ");
System.out.println(val);
if( !quietMode ){
System.out.print(Thread.currentThread().getName()+": ");
System.out.println(val);
}
}
public synchronized static void out(Object val){
System.out.print(val);
if( !quietMode ){
System.out.print(val);
}
}
@SuppressWarnings("unchecked")
public synchronized static void out(Object... vals){
System.out.print(Thread.currentThread().getName()+": ");
for(Object v : vals) out(v);
if( !quietMode ){
System.out.print(Thread.currentThread().getName()+": ");
for(Object v : vals) out(v);
}
}
@SuppressWarnings("unchecked")
public synchronized static void outln(Object... vals){
out(vals); out("\n");
if( !quietMode ){
out(vals); out("\n");
}
}
static volatile int affirmCount = 0;
@ -85,6 +109,7 @@ public class Tester1 implements Runnable {
affirm(v, "Affirmation failed.");
}
@ManualTest /* because testing this for threading is pointless */
private void test1(){
affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
affirm(SQLITE_MAX_LENGTH > 0);
@ -280,6 +305,16 @@ public class Tester1 implements Runnable {
affirm(0 != stmt.getNativePointer());
sqlite3_finalize(stmt);
affirm(0 == stmt.getNativePointer() );
affirm( 0==sqlite3_errcode(db) );
stmt = sqlite3_prepare(db, "intentional error");
affirm( null==stmt );
affirm( 0!=sqlite3_errcode(db) );
affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
sqlite3_finalize(stmt);
stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
affirm( null==stmt );
affirm( 0==sqlite3_errcode(db) );
sqlite3_close_v2(db);
}
@ -383,8 +418,11 @@ public class Tester1 implements Runnable {
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
String[] list1 = { "hell🤩", "w😃rld", "!" };
int rc;
int n = 0;
for( String e : list1 ){
rc = sqlite3_bind_text(stmt, 1, e);
rc = (0==n)
? sqlite3_bind_text(stmt, 1, e)
: sqlite3_bind_text16(stmt, 1, e);
affirm(0 == rc);
rc = sqlite3_step(stmt);
affirm(SQLITE_DONE==rc);
@ -393,7 +431,7 @@ public class Tester1 implements Runnable {
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
StringBuilder sbuf = new StringBuilder();
int n = 0;
n = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
String txt = sqlite3_column_text16(stmt, 0);
//outln("txt = "+txt);
@ -521,6 +559,7 @@ public class Tester1 implements Runnable {
affirm(xDestroyCalled.value);
}
@ManualTest /* because threading is meaningless here */
private void testToUtf8(){
/**
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
@ -873,6 +912,7 @@ public class Tester1 implements Runnable {
affirm( 7 == counter.value );
}
@ManualTest /* because threads inherently break this test */
private void testBusy(){
final String dbName = "_busy-handler.db";
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
@ -1156,6 +1196,8 @@ public class Tester1 implements Runnable {
it throws.
*/
@SuppressWarnings("unchecked")
@ManualTest /* because the Fts5 parts are not yet known to be
thread-safe */
private void testFts5() throws Exception {
if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
//outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
@ -1206,6 +1248,8 @@ public class Tester1 implements Runnable {
sqlite3_close(db);
}
@ManualTest/* because multiple threads legitimately make these
results unpredictable */
private synchronized void testAutoExtension(){
final ValueHolder<Integer> val = new ValueHolder<>(0);
final ValueHolder<String> toss = new ValueHolder<>(null);
@ -1296,6 +1340,7 @@ public class Tester1 implements Runnable {
affirm( 8 == val.value );
}
@ManualTest /* because we only want to run this test manually */
private void testSleep(){
out("Sleeping briefly... ");
sqlite3_sleep(600);
@ -1308,6 +1353,7 @@ public class Tester1 implements Runnable {
}
}
@ManualTest /* because we only want to run this test on demand */
private void testFail(){
affirm( false, "Intentional failure." );
}
@ -1355,6 +1401,9 @@ public class Tester1 implements Runnable {
testFts5();
}
}
synchronized( this.getClass() ){
++nTestRuns;
}
}
public void run() {
@ -1375,6 +1424,8 @@ public class Tester1 implements Runnable {
CLI flags:
-q|-quiet: disables most test output.
-t|-thread N: runs the tests in N threads
concurrently. Default=1.
@ -1400,6 +1451,7 @@ public class Tester1 implements Runnable {
Integer nRepeat = 1;
boolean forceFail = false;
boolean sqlLog = false;
boolean squelchTestOutput = false;
for( int i = 0; i < args.length; ){
String arg = args[i++];
if(arg.startsWith("-")){
@ -1421,6 +1473,8 @@ public class Tester1 implements Runnable {
sqlLog = true;
}else if(arg.equals("naps")){
takeNaps = true;
}else if(arg.equals("q") || arg.equals("quiet")){
squelchTestOutput = true;
}else{
throw new IllegalArgumentException("Unhandled flag:"+arg);
}
@ -1430,19 +1484,16 @@ public class Tester1 implements Runnable {
{
// Build list of tests to run from the methods named test*().
testMethods = new ArrayList<>();
final List<String> excludes = new ArrayList<>();
// Tests we want to control the order of:
if( !forceFail ) excludes.add("testFail");
excludes.add("test1");
excludes.add("testAutoExtension");
excludes.add("testBusy");
excludes.add("testFts5");
excludes.add("testSleep");
excludes.add("testToUtf8");
for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
final String name = m.getName();
if( name.startsWith("test") && excludes.indexOf(name)<0 ){
testMethods.add(m);
if( name.equals("testFail") ){
if( forceFail ){
testMethods.add(m);
}
}else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( name.startsWith("test") ){
testMethods.add(m);
}
}
}
}
@ -1465,6 +1516,11 @@ public class Tester1 implements Runnable {
}
}
quietMode = squelchTestOutput;
outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
"you are very likely seeing the side effects of a known openjdk8 ",
"bug. It is unsightly but does not affect the library.");
final long timeStart = System.currentTimeMillis();
int nLoop = 0;
affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
@ -1476,7 +1532,7 @@ public class Tester1 implements Runnable {
outln("libversion_number: ",
sqlite3_libversion_number(),"\n",
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID);
outln("Running ",nRepeat," loop(s) over ",nThread," thread(s).");
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
if( takeNaps ) outln("Napping between tests is enabled.");
for( int n = 0; n < nRepeat; ++n ){
if( nThread==null || nThread<=1 ){
@ -1500,6 +1556,7 @@ public class Tester1 implements Runnable {
Thread.currentThread().interrupt();
}
if( !listErrors.isEmpty() ){
quietMode = false;
outln("TEST ERRORS:");
Exception err = null;
for( Exception e : listErrors ){
@ -1510,9 +1567,10 @@ public class Tester1 implements Runnable {
}
}
outln();
quietMode = false;
final long timeEnd = System.currentTimeMillis();
outln("Tests done. Metrics:");
outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
outln("\tAssertions checked: ",affirmCount);
outln("\tDatabases opened: ",metrics.dbOpen);
if( doSomethingForDev ){

View File

@ -72,13 +72,18 @@ public class TesterFts5 {
affirm( xDestroyCalled.value );
}
public TesterFts5(){
final long timeStart = System.currentTimeMillis();
final int oldAffirmCount = Tester1.affirmCount;
test1();
final int affirmCount = Tester1.affirmCount - oldAffirmCount;
final long timeEnd = System.currentTimeMillis();
outln("FTS5 Tests done. Assertions checked = ",affirmCount,
", Total time = ",(timeEnd - timeStart),"ms");
public TesterFts5(boolean verbose){
if(verbose){
final long timeStart = System.currentTimeMillis();
final int oldAffirmCount = Tester1.affirmCount;
test1();
final int affirmCount = Tester1.affirmCount - oldAffirmCount;
final long timeEnd = System.currentTimeMillis();
outln("FTS5 Tests done. Assertions checked = ",affirmCount,
", Total time = ",(timeEnd - timeStart),"ms");
}else{
test1();
}
}
public TesterFts5(){ this(false); }
}