changeset 591:b2cd3b828967

Simplify OnClientTester program. It no longer uses reflection and method naming conventions to find out which tests to run, but just executes them sequentially. This is much easier to read, understand and maintain. The TestRunner superclass has also been merged into OnClientTester, so only one file is now needed. TestRunner.java file will be removed later, after all has proven to work well in testweb.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 18 Nov 2021 16:41:07 +0100 (2021-11-18)
parents 73f4f30e4c62
children b58f6f26fab0
files tests/JDBC_API_Tester.java tests/OnClientTester.java tests/build.xml
diffstat 3 files changed, 405 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -105,8 +105,8 @@ final public class JDBC_API_Tester {
 		if (jt.foundDifferences)
 			System.exit(-1);
 
-		OnClientTester oct = new OnClientTester(con_URL, 0, true);
-		int failures = oct.runTests("");
+		OnClientTester oct = new OnClientTester(con_URL, 0);
+		int failures = oct.runTests();
 		if (failures > 0)
 			System.exit(-1);
 	}
--- a/tests/OnClientTester.java
+++ b/tests/OnClientTester.java
@@ -14,56 +14,208 @@ import org.monetdb.util.FileTransferHand
 import java.io.*;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Statement;
 import java.sql.SQLException;
 import java.util.List;
 
 import static java.nio.file.StandardOpenOption.CREATE_NEW;
 
-public final class OnClientTester extends TestRunner {
 
-	public OnClientTester(String jdbcUrl, int verbosity, boolean watchDogEnabled) {
-		super(jdbcUrl, verbosity, watchDogEnabled);
+/**
+ * Program to test MonetDB JDBC Driver in combination with SQL:
+ *   COPY ... INTO ... ON CLIENT
+ * commands.
+ * This allows Java programmers to locally (so ON CLIENT) stream csv data
+ * to and from the MonetDB server for fast bulk data import / export.
+ *
+ * Specifically it tests the MonetDB specific extensions to register upload and download handlers
+ * see {@link org.monetdb.jdbc.MonetConnection#setUploadHandler(UploadHandler)}
+ * see {@link org.monetdb.jdbc.MonetConnection#setUploadHandler(DownloadHandler)}
+ * and streaming of csv data to and from the MonetDB server using MAPI protocol.
+ *
+ * It also tests reading / writing data from / to a local file using
+ * {@link org.monetdb.util.FileTransferHandler}
+ *
+ * @author JvR
+ * @version 0.1
+ */
+public final class OnClientTester {
+	public static final int VERBOSITY_NONE = 0;
+	public static final int VERBOSITY_ON = 1;
+	public static final int VERBOSITY_SHOW_ALL = 2;
+
+	private final String jdbcUrl;
+	private final int verbosity;
+	private String currentTestName;
+	private long startTime;
+	private MonetConnection conn;
+	private Statement stmt;
+	private StringBuilder outBuffer;
+	private Path tmpDir;
+
+	public OnClientTester(String jdbcUrl, int verbosity) {
+		this.jdbcUrl = jdbcUrl;
+		this.verbosity = verbosity;
 	}
 
-	public static void main(String[] args) throws SQLException, NoSuchMethodException, ClassNotFoundException {
+	public static void main(String[] args) {
 		String jdbcUrl = null;
-		String requiredPrefix = null;
 		int verbosity = 0;
-		boolean watchDogEnabled = true;
-
-		// Don't know why I need this all of a sudden.. is it only on my system?
-		// Class.forName("org.monetdb.jdbc.MonetDriver");	// should not be needed if you add: import java.sql.DriverManager;
 
 		for (String arg : args) {
 			if (arg.equals("-v"))
 				verbosity++;
 			else if (arg.equals("-vv"))
 				verbosity += 2;
-			else if (arg.equals("-w"))
-				watchDogEnabled = false;
 			else if (jdbcUrl == null)
 				jdbcUrl = arg;
-			else if (requiredPrefix == null)
-				requiredPrefix = arg;
 			else {
 				System.err.println("Unexpected argument " + arg);
 				System.exit(2);
 			}
 		}
+		if (jdbcUrl == null || jdbcUrl.isEmpty()) {
+			System.err.println("Missing required startup argument: JDBC_connection_URL");
+			System.exit(1);
+		}
 
-		OnClientTester tester = new OnClientTester(jdbcUrl, verbosity, watchDogEnabled);
-		int failures = tester.runTests(requiredPrefix);
+		OnClientTester tester = new OnClientTester(jdbcUrl, verbosity);
+		int failures = tester.runTests();
+		if (failures > 0)
+			System.exit(-1);
+	}
+
+	public int runTests() {
+		if (! openConnection())
+			return 1;	// failed to open JDBC connection to MonetDB
+
+		outBuffer = new StringBuilder(1024);
 
-		if (failures > 0)
-			System.exit(1);
+		int failures = 0;
+		try {
+			// all test methods start with test_ and have no arguments
+			test_BugFixLevel();
+			test_Upload();
+			test_ClientRefusesUpload();
+			test_Offset0();
+			test_Offset1();
+			test_Offset5();
+			test_ServerStopsReading();
+			test_Download();
+			test_ClientRefusesDownload();
+			test_LargeUpload();
+			test_LargeDownload();
+			test_UploadFromStream();
+			test_UploadFromReader();
+			test_UploadFromReaderOffset();
+			test_FailUploadLate();
+			test_FailUploadLate2();
+			test_FailDownloadLate();
+			test_FileTransferHandlerUploadUtf8();
+			test_FileTransferHandlerUploadLatin1();
+			test_FileTransferHandlerUploadNull();
+			test_FileTransferHandlerUploadRefused();
+			test_FileTransferHandlerDownloadUtf8();
+			test_FileTransferHandlerDownloadLatin1();
+			test_FileTransferHandlerDownloadNull();
+			test_FileTransferHandlerDownloadRefused();
+		} catch (Failure e) {
+			failures++;
+			System.err.println();
+			System.err.println("Test " + currentTestName + " failed");
+			dumpOutput();
+		} catch (Exception e) {
+			failures++;
+			System.err.println();
+			System.err.println("Test " + currentTestName + " failed:");
+			e.printStackTrace(System.err);
+			dumpOutput();
+			// Show the inner bits of the exception again, they may have scrolled off screen
+			Throwable t = e;
+			while (t.getCause() != null) {
+				t = t.getCause();
+			}
+			System.err.println("Innermost cause was " + t);
+			if (t.getStackTrace().length > 0) {
+				System.err.println("                 at " + t.getStackTrace()[0]);
+			}
+		}
+		closeConnection();
+		return failures;
+	}
+
+	private boolean openConnection() {
+		try {
+			// make a connection to MonetDB, its reused for all tests
+			final Connection genericConnection = DriverManager.getConnection(jdbcUrl);
+			conn = genericConnection.unwrap(MonetConnection.class);
+			stmt = conn.createStatement();
+			return true;
+		} catch (SQLException e) {
+			System.err.println("Failed to connect using JDBC URL: " + jdbcUrl);
+			System.err.println(e);
+		}
+		return false;
+	}
+
+	private void closeConnection() {
+		if (stmt != null) {
+			try {
+				stmt.close();
+			} catch (SQLException e) { /* ignore */ }
+			stmt = null;
+		}
+		if (conn != null) {
+			try {
+				conn.close();
+			} catch (Exception e) { /* ignore */ }
+			conn = null;
+		}
+	}
+
+	private void initTest(final String name) {
+		currentTestName = name;
+		outBuffer.setLength(0);		// clear the output log buffer
+		startTime = System.currentTimeMillis();
+	}
+
+	private void exitTest() {
+		if (verbosity > VERBOSITY_ON)
+			System.err.println();
+		if (verbosity >= VERBOSITY_ON) {
+			final long duration = System.currentTimeMillis() - startTime;
+			System.err.println("Test " + currentTestName + " succeeded in " + duration + "ms");
+		}
+		if (verbosity >= VERBOSITY_SHOW_ALL)
+			dumpOutput();
+
+		if (conn.isClosed())
+			openConnection();	// restore connection for next test
+	}
+
+	private void dumpOutput() {
+		final String output = outBuffer.toString();
+		if (output.isEmpty()) {
+			System.err.println("(Test " + currentTestName + " did not produce any output)");
+		} else {
+			System.err.println("------ Accumulated output for test " + currentTestName + ":");
+			System.err.println(output);
+			System.err.println("------ End of accumulated output");
+		}
 	}
 
 	/// Some tests have to work limitations of the protocol or bugs in the server.
 	/// This Enum is used to indicate the possibilities.
-	public enum BugFixLevel {
+	private enum BugFixLevel {
 		/// Only those tests that work with older MonetDB versions
 		Baseline(0, 0, 0),
 
@@ -116,19 +268,21 @@ public final class OnClientTester extend
 		}
 	}
 
-	void prepare() throws SQLException {
+	private void prepare() throws SQLException {
 		execute("DROP TABLE IF EXISTS foo");
-		execute("CREATE TABLE foo (i INT, t TEXT)");
+		execute("CREATE TABLE foo (i INT, t CLOB)");
 	}
 
 	private BugFixLevel getLevel() throws SQLException, Failure {
 		String version = queryString("SELECT value FROM environment WHERE name = 'monet_version'");
 		BugFixLevel level = BugFixLevel.forVersion(version);
-		out.println("  NOTE: version " + version + " means level = " + level);
+		outBuffer.append("  NOTE: version ").append(version).append(" means level = ").append(level).append("\n");
 		return level;
 	}
 
-	public void test_BugFixLevel() throws Failure {
+	private void test_BugFixLevel() throws Failure {
+		initTest("test_BugFixLevel");
+
 		assertEq("Baseline includes 0.0.0", true, BugFixLevel.Baseline.includesVersion(0, 0, 0));
 		assertEq("Baseline includes 11.41.11", true, BugFixLevel.Baseline.includesVersion(11, 41, 11));
 		assertEq("Baseline includes 11.41.12", true, BugFixLevel.Baseline.includesVersion(11, 41, 12));
@@ -154,27 +308,34 @@ public final class OnClientTester extend
 
 		assertEq("Level for \"11.41.11\"", BugFixLevel.Baseline, BugFixLevel.forVersion("11.41.11"));
 		assertEq("Level for \"11.41.12\"", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion("11.41.12"));
+
+		exitTest();
 	}
 
-	public void test_Upload() throws SQLException, Failure {
+	private void test_Upload() throws SQLException, Failure {
+		initTest("test_Upload");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100);
 		conn.setUploadHandler(handler);
 		update("COPY INTO foo FROM 'banana' ON CLIENT");
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertQueryInt("SELECT COUNT(*) FROM foo", 100);
+		exitTest();
 	}
 
-	public void test_ClientRefusesUpload() throws SQLException, Failure {
+	private void test_ClientRefusesUpload() throws SQLException, Failure {
+		initTest("test_ClientRefusesUpload");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler("immediate error");
 		conn.setUploadHandler(handler);
 		expectError("COPY INTO foo FROM 'banana' ON CLIENT", "immediate error");
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertQueryInt("SELECT COUNT(*) FROM foo", 0);
+		exitTest();
 	}
 
-	public void test_Offset0() throws SQLException, Failure {
+	private void test_Offset0() throws SQLException, Failure {
+		initTest("test_Offset0");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100);
 		conn.setUploadHandler(handler);
@@ -182,9 +343,11 @@ public final class OnClientTester extend
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertQueryInt("SELECT MIN(i) FROM foo", 1);
 		assertQueryInt("SELECT MAX(i) FROM foo", 100);
+		exitTest();
 	}
 
-	public void test_Offset1() throws SQLException, Failure {
+	private void test_Offset1() throws SQLException, Failure {
+		initTest("test_Offset1");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100);
 		conn.setUploadHandler(handler);
@@ -192,9 +355,11 @@ public final class OnClientTester extend
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertQueryInt("SELECT MIN(i) FROM foo", 1);
 		assertQueryInt("SELECT MAX(i) FROM foo", 100);
+		exitTest();
 	}
 
-	public void test_Offset5() throws SQLException, Failure {
+	private void test_Offset5() throws SQLException, Failure {
+		initTest("test_Offset5");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100);
 		conn.setUploadHandler(handler);
@@ -202,9 +367,11 @@ public final class OnClientTester extend
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertQueryInt("SELECT MIN(i) FROM foo", 5);
 		assertQueryInt("SELECT MAX(i) FROM foo", 100);
+		exitTest();
 	}
 
-	public void test_ServerStopsReading() throws SQLException, Failure {
+	private void test_ServerStopsReading() throws SQLException, Failure {
+		initTest("test_ServerStopsReading");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100);
 		conn.setUploadHandler(handler);
@@ -213,9 +380,10 @@ public final class OnClientTester extend
 		assertEq("handler encountered write error", true, handler.encounteredWriteError());
 		// connection is still alive
 		assertQueryInt("SELECT COUNT(i) FROM foo", 10);
+		exitTest();
 	}
 
-	public void test_Download(int n) throws SQLException, Failure {
+	private void test_Download(int n) throws SQLException, Failure {
 		prepare();
 		MyDownloadHandler handler = new MyDownloadHandler();
 		conn.setDownloadHandler(handler);
@@ -228,11 +396,14 @@ public final class OnClientTester extend
 		assertQueryInt("SELECT COUNT(*) FROM foo", n);
 	}
 
-	public void test_Download() throws SQLException, Failure {
+	private void test_Download() throws SQLException, Failure {
+		initTest("test_Download");
 		test_Download(100);
+		exitTest();
 	}
 
-	public void test_ClientRefusesDownload() throws SQLException, Failure {
+	private void test_ClientRefusesDownload() throws SQLException, Failure {
+		initTest("test_ClientRefusesDownload");
 		prepare();
 		BugFixLevel level = getLevel();
 		MyDownloadHandler handler = new MyDownloadHandler("download refused");
@@ -245,10 +416,11 @@ public final class OnClientTester extend
 			// connection is still alive
 			assertQueryInt("SELECT COUNT(*) FROM foo", 100);
 		}
+		exitTest();
 	}
 
-	public void test_LargeUpload() throws SQLException, Failure {
-		watchDog.setDuration(25_000);
+	private void test_LargeUpload() throws SQLException, Failure {
+		initTest("test_LargeUpload");
 		prepare();
 		int n = 4_000_000;
 		MyUploadHandler handler = new MyUploadHandler(n);
@@ -258,14 +430,17 @@ public final class OnClientTester extend
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		// connection is still alive
 		assertQueryInt("SELECT COUNT(DISTINCT i) FROM foo", n);
+		exitTest();
 	}
 
-	public void test_LargeDownload() throws SQLException, Failure {
-		watchDog.setDuration(25_000);
+	private void test_LargeDownload() throws SQLException, Failure {
+		initTest("test_LargeDownload");
 		test_Download(4_000_000);
+		exitTest();
 	}
 
-	public void test_UploadFromStream() throws SQLException, Failure {
+	private void test_UploadFromStream() throws SQLException, Failure {
+		initTest("test_UploadFromStream");
 		prepare();
 		UploadHandler handler = new UploadHandler() {
 			final String data = "1|one\n2|two\n3|three\n";
@@ -281,9 +456,11 @@ public final class OnClientTester extend
 		update("COPY INTO foo FROM 'banana' ON CLIENT");
 		// connection is still alive
 		assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
+		exitTest();
 	}
 
-	public void test_UploadFromReader() throws SQLException, Failure {
+	private void test_UploadFromReader() throws SQLException, Failure {
+		initTest("test_UploadFromReader");
 		prepare();
 		UploadHandler handler = new UploadHandler() {
 			final String data = "1|one\n2|two\n3|three\n";
@@ -298,9 +475,11 @@ public final class OnClientTester extend
 		conn.setUploadHandler(handler);
 		update("COPY INTO foo FROM 'banana' ON CLIENT");
 		assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
+		exitTest();
 	}
 
-	public void test_UploadFromReaderOffset() throws SQLException, Failure {
+	private void test_UploadFromReaderOffset() throws SQLException, Failure {
+		initTest("test_UploadFromReaderOffset");
 		prepare();
 		UploadHandler handler = new UploadHandler() {
 			final String data = "1|one\n2|two\n3|three\n";
@@ -314,19 +493,23 @@ public final class OnClientTester extend
 		conn.setUploadHandler(handler);
 		update("COPY OFFSET 2 INTO foo FROM 'banana' ON CLIENT");
 		assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
+		exitTest();
 	}
 
-	public void test_FailUploadLate() throws SQLException, Failure {
+	private void test_FailUploadLate() throws SQLException, Failure {
+		initTest("test_FailUploadLate");
 		prepare();
 		MyUploadHandler handler = new MyUploadHandler(100, 50, "i don't like line 50");
 		conn.setUploadHandler(handler);
 		expectError("COPY INTO foo FROM 'banana' ON CLIENT", "i don't like");
 		assertEq("cancellation callback called", false, handler.isCancelled());
 		assertEq("connection is closed", true, conn.isClosed());
+		exitTest();
 	}
 
-	public void test_FailUploadLate2() throws SQLException, Failure {
-		// Here we send empty lines only, to check if the server detects is properly instead
+	private void test_FailUploadLate2() throws SQLException, Failure {
+		initTest("test_FailUploadLate2");
+		// Here we send empty lines only, to check if the server detects it properly instead
 		// of simply complaining about an incomplete file.
 		prepare();
 		UploadHandler handler = new UploadHandler() {
@@ -344,9 +527,11 @@ public final class OnClientTester extend
 		expectError("COPY INTO foo(t) FROM 'banana'(t) ON CLIENT", "after all");
 		assertEq("connection is closed", true, conn.isClosed());
 		// Cannot check the server log, but at the time I checked, it said "prematurely stopped client", which is fine.
+		exitTest();
 	}
 
-	public void test_FailDownloadLate() throws SQLException, Failure {
+	private void test_FailDownloadLate() throws SQLException, Failure {
+		initTest("test_FailDownloadLate");
 		prepare();
 		MyDownloadHandler handler = new MyDownloadHandler(200, "download refused");
 		conn.setDownloadHandler(handler);
@@ -354,21 +539,28 @@ public final class OnClientTester extend
 		expectError("COPY (SELECT * FROM sys.generate_series(0,200)) INTO 'banana' ON CLIENT", "download refused");
 		// Exception closes the connection
 		assertEq("connection is closed", conn.isClosed(), true);
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerUploadUtf8() throws IOException, SQLException, Failure {
+	private void test_FileTransferHandlerUploadUtf8() throws IOException, SQLException, Failure {
+		initTest("test_FileTransferHandlerUploadUtf8");
 		testFileTransferHandlerUpload(StandardCharsets.UTF_8, "UTF-8");
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerUploadLatin1() throws IOException, SQLException, Failure {
+	private void test_FileTransferHandlerUploadLatin1() throws IOException, SQLException, Failure {
+		initTest("test_FileTransferHandlerUploadLatin1");
 		testFileTransferHandlerUpload(Charset.forName("latin1"), "latin1");
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerUploadNull() throws IOException, SQLException, Failure {
+	private void test_FileTransferHandlerUploadNull() throws IOException, SQLException, Failure {
+		initTest("test_FileTransferHandlerUploadNull");
 		testFileTransferHandlerUpload(null, Charset.defaultCharset().name());
+		exitTest();
 	}
 
-	public void testFileTransferHandlerUpload(Charset handlerEncoding, String fileEncoding) throws IOException, SQLException, Failure {
+	private void testFileTransferHandlerUpload(Charset handlerEncoding, String fileEncoding) throws IOException, SQLException, Failure {
 		prepare();
 		Path d = getTmpDir(currentTestName);
 		Path f = d.resolve("data.txt");
@@ -384,7 +576,8 @@ public final class OnClientTester extend
 		assertQueryString("SELECT t FROM foo WHERE i = 2", "twø");
 	}
 
-	public void test_FileTransferHandlerUploadRefused() throws IOException, SQLException, Failure {
+	private void test_FileTransferHandlerUploadRefused() throws IOException, SQLException, Failure {
+		initTest("test_FileTransferHandlerUploadRefused");
 		prepare();
 		Path d = getTmpDir(currentTestName);
 		Path f = d.resolve("data.txt");
@@ -401,22 +594,29 @@ public final class OnClientTester extend
 		expectError("COPY INTO foo FROM R'"+ quoted + "' ON CLIENT", "not in upload directory");
 		// connection is still alive
 		assertQueryInt("SELECT SUM(i) FROM foo", 0);
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerDownloadUtf8() throws SQLException, Failure, IOException {
+	private void test_FileTransferHandlerDownloadUtf8() throws SQLException, Failure, IOException {
+		initTest("test_FileTransferHandlerDownloadUtf8");
 		testFileTransferHandlerDownload(StandardCharsets.UTF_8, StandardCharsets.UTF_8);
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerDownloadLatin1() throws SQLException, Failure, IOException {
+	private void test_FileTransferHandlerDownloadLatin1() throws SQLException, Failure, IOException {
+		initTest("test_FileTransferHandlerDownloadLatin1");
 		Charset latin1 = Charset.forName("latin1");
 		testFileTransferHandlerDownload(latin1, latin1);
+		exitTest();
 	}
 
-	public void test_FileTransferHandlerDownloadNull() throws SQLException, Failure, IOException {
+	private void test_FileTransferHandlerDownloadNull() throws SQLException, Failure, IOException {
+		initTest("test_FileTransferHandlerDownloadNull");
 		testFileTransferHandlerDownload(null, Charset.defaultCharset());
+		exitTest();
 	}
 
-	public void testFileTransferHandlerDownload(Charset handlerEncoding, Charset fileEncoding) throws SQLException, Failure, IOException {
+	private void testFileTransferHandlerDownload(Charset handlerEncoding, Charset fileEncoding) throws SQLException, Failure, IOException {
 		prepare();
 		update("INSERT INTO foo VALUES (42, 'forty-twø')");
 		Path d = getTmpDir(currentTestName);
@@ -429,7 +629,8 @@ public final class OnClientTester extend
 		assertQueryInt("SELECT SUM(i) FROM foo", 42);
 	}
 
-	public void test_FileTransferHandlerDownloadRefused() throws SQLException, Failure, IOException {
+	private void test_FileTransferHandlerDownloadRefused() throws SQLException, Failure, IOException {
+		initTest("test_FileTransferHandlerDownloadRefused");
 		prepare();
 		BugFixLevel level = getLevel();
 		update("INSERT INTO foo VALUES (42, 'forty-two')");
@@ -442,8 +643,143 @@ public final class OnClientTester extend
 			// connection is still alive
 			assertQueryInt("SELECT SUM(i) FROM foo", 42);
 		}
+		exitTest();
+	}
+
+
+	/* utility methods */
+	private void fail(String message) throws Failure {
+		outBuffer.append("FAILURE: ").append(message).append("\n");
+		throw new Failure(message);
+	}
+
+	private void checked(String quantity, Object actual) {
+		outBuffer.append("  CHECKED: <").append(quantity).append("> is ").append(actual).append(" as expected").append("\n");
+	}
+
+	private void assertEq(String quantity, Object expected, Object actual) throws Failure {
+		if (expected.equals(actual)) {
+			checked(quantity, actual);
+		} else {
+			fail("Expected <" + quantity + "> to be " + expected + " got " + actual);
+		}
+	}
+
+	private boolean execute(String query) throws SQLException {
+		outBuffer.append("EXECUTE: ").append(query).append("\n");
+		final boolean result = stmt.execute(query);
+		if (result) {
+			outBuffer.append("  OK").append("\n");
+		} else {
+			outBuffer.append("  OK, updated ").append(stmt.getUpdateCount()).append(" rows").append("\n");
+		}
+		return result;
+	}
+
+	private void update(String query) throws SQLException, Failure {
+		execute(query);
+	}
+
+	private void expectError(String query, String expectedError) throws SQLException {
+		try {
+			execute(query);
+		} catch (SQLException e) {
+			if (e.getMessage().contains(expectedError)) {
+				outBuffer.append("  GOT EXPECTED EXCEPTION: ").append(e.getMessage()).append("\n");
+			} else {
+				throw e;
+			}
+		}
+	}
+
+	private void assertQueryInt(String query, int expected) throws SQLException, Failure {
+		if (execute(query) == false) {
+			fail("Query does not return a result set");
+		}
+		final ResultSet rs = stmt.getResultSet();
+		final ResultSetMetaData metaData = rs.getMetaData();
+		assertEq("column count", 1, metaData.getColumnCount());
+		if (!rs.next()) {
+			fail("Result set is empty");
+		}
+		final int result = rs.getInt(1);
+		if (rs.next()) {
+			fail("Result set has more than one row");
+		}
+		rs.close();
+		checked("row count", 1);
+		assertEq("query result", expected, result);
 	}
 
+	private void assertQueryString(String query, String expected) throws SQLException, Failure {
+		if (execute(query) == false) {
+			fail("Query does not return a result set");
+		}
+		final ResultSet rs = stmt.getResultSet();
+		final ResultSetMetaData metaData = rs.getMetaData();
+		assertEq("column count", 1, metaData.getColumnCount());
+		if (!rs.next()) {
+			fail("Result set is empty");
+		}
+		final String result = rs.getString(1);
+		if (rs.next()) {
+			fail("Result set has more than one row");
+		}
+		rs.close();
+		checked("row count", 1);
+		assertEq("query result", expected, result);
+	}
+
+	private String queryString(String query) throws SQLException, Failure {
+		if (execute(query) == false) {
+			fail("Query does not return a result set");
+		}
+		final ResultSet rs = stmt.getResultSet();
+		final ResultSetMetaData metaData = rs.getMetaData();
+		assertEq("column count", 1, metaData.getColumnCount());
+		if (!rs.next()) {
+			fail("Result set is empty");
+		}
+		final String result = rs.getString(1);
+		if (rs.next()) {
+			fail("Result set has more than one row");
+		}
+		rs.close();
+		checked("row count", 1);
+		return result;
+	}
+
+	private synchronized Path getTmpDir(String name) throws IOException {
+		if (tmpDir == null) {
+			tmpDir = Files.createTempDirectory("testMonetDB");
+			Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+				try {
+					Files.walkFileTree(tmpDir, new SimpleFileVisitor<Path>() {
+						@Override
+						public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+							Files.delete(file);
+							return FileVisitResult.CONTINUE;
+						}
+
+						@Override
+						public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+							Files.delete(dir);
+							return FileVisitResult.CONTINUE;
+						}
+					});
+				} catch (IOException e) {
+					// we do this on a best effort basis
+				}
+			}));
+		}
+		final Path p = tmpDir.resolve(name);
+		Files.createDirectory(p);
+		return p;
+	}
+
+	/**
+	 * Implementation of an UploadHandler
+	 */
 	static class MyUploadHandler implements UploadHandler {
 		private final long rows;
 		private final long errorAt;
@@ -505,6 +841,9 @@ public final class OnClientTester extend
 		}
 	}
 
+	/**
+	 * Implementation of a DownloadHandler
+	 */
 	static class MyDownloadHandler implements DownloadHandler {
 		private final int errorAtByte;
 		private final String errorMessage;
@@ -572,4 +911,16 @@ public final class OnClientTester extend
 			return lines;
 		}
 	}
+
+	static class Failure extends Exception {
+		static final long serialVersionUID = 3387516993124229948L;
+
+		public Failure(String message) {
+			super(message);
+		}
+
+		public Failure(String message, Throwable cause) {
+			super(message, cause);
+		}
+	}
 }
--- a/tests/build.xml
+++ b/tests/build.xml
@@ -72,8 +72,6 @@ Copyright 1997 - July 2008 CWI, August 2
     <jar jarfile="${jdbctests-jar}">
       <fileset dir="${builddir}">
         <include name="JDBC_API_Tester.class" />
-        <include name="TestRunner.class" />
-        <include name="TestRunner$*.class" />
         <include name="OnClientTester.class" />
         <include name="OnClientTester$*.class" />
       </fileset>