Mercurial > hg > monetdb-java
view tests/TestRunner.java @ 574:3370027aeb7f onclient
Test more systematically that the connection is still alive after file transfer
Some MonetDB versions have a bug where the connection is closed by the server
when the client refuses to download.
The tests look at the the server version and skip the
lifeness check if the server is too old.
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Wed, 06 Oct 2021 11:34:18 +0200 (2021-10-06) |
parents | 8a4e6c82815a |
children | 08c9918177b2 |
line wrap: on
line source
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V. */ import org.monetdb.jdbc.MonetConnection; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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.*; import java.util.ArrayList; public class TestRunner { public static final int VERBOSITY_NONE = 0; public static final int VERBOSITY_ON = 1; public static final int VERBOSITY_SHOW_ALL = 2; protected final String jdbcUrl; private final int verbosity; protected String currentTestName; protected final WatchDog watchDog; protected MonetConnection conn; protected Statement stmt; private StringWriter outBuffer; protected PrintWriter out; private Path tmpDir = null; public TestRunner(String jdbcUrl, int verbosity, boolean watchDogEnabled) { this.jdbcUrl = jdbcUrl; this.verbosity = verbosity; watchDog = new WatchDog(); if (watchDogEnabled) watchDog.enable(); else watchDog.disable(); } protected int runTests(String testPrefix) throws SQLException { int testCount = 0; int skippedCount = 0; ArrayList<String> failures = new ArrayList<>(); watchDog.stop(); try { final String initialPrefix = "test_"; if (testPrefix == null) testPrefix = ""; final String methodPrefix = initialPrefix + testPrefix; for (Method method : this.getClass().getDeclaredMethods()) { if (method.getParameterCount() != 0) { continue; } if (!method.getName().startsWith(initialPrefix)) { continue; } testCount++; // so user can add $ to force full match String augmentedMethodName = method.getName() + "$"; if (!augmentedMethodName.startsWith(methodPrefix)) { skippedCount++; continue; } String testName = method.getName().substring(initialPrefix.length()); boolean succeeded = runTest(testName, method); if (!succeeded) failures.add(testName); } } finally { watchDog.stop(); } if (testCount > 0 && skippedCount == testCount && !testPrefix.isEmpty()) { System.err.printf("None of the %d tests matched prefix '%s'%n", testCount, testPrefix); return 1; } int failureCount = failures.size(); if (failureCount > 0) { System.out.println(); System.out.printf("Ran %d out of %d %s tests, %d failed: %s%n", testCount - skippedCount, testCount, this.getClass().getSimpleName(), failureCount, String.join(", ", failures) ); } else if (verbosity >= VERBOSITY_ON) { System.out.println(); System.out.printf("Ran %d out of %d tests, none failed%n", testCount - skippedCount, testCount); } return failureCount; } private synchronized boolean runTest(String testName, Method method) throws SQLException { currentTestName = testName; watchDog.setContext("test " + testName); watchDog.setDuration(3_000); outBuffer = new StringWriter(); out = new PrintWriter(outBuffer); Connection genericConnection = DriverManager.getConnection(jdbcUrl); conn = genericConnection.unwrap(MonetConnection.class); stmt = conn.createStatement(); boolean failed = true; try { long duration; try { long t0 = System.currentTimeMillis(); method.invoke(this); long t1 = System.currentTimeMillis(); duration = t1 - t0; } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof Failure) throw (Failure) cause; else if (cause instanceof Exception) { throw (Exception) cause; } else { throw e; } } failed = false; if (verbosity > VERBOSITY_ON) System.out.println(); if (verbosity >= VERBOSITY_ON) System.out.println("Test " + testName + " succeeded in " + duration + "ms"); if (verbosity >= VERBOSITY_SHOW_ALL) dumpOutput(testName); } catch (Failure e) { System.out.println(); System.out.println("Test " + testName + " failed"); dumpOutput(testName); } catch (Exception e) { System.out.println(); System.out.println("Test " + testName + " failed:"); e.printStackTrace(System.out); dumpOutput(testName); // 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.out.println("Innermost cause was " + t); if (t.getStackTrace().length > 0) { System.out.println(" at " + t.getStackTrace()[0]); } } finally { watchDog.setContext(null); if (failed && verbosity == VERBOSITY_ON) { // next test case will not print separator System.out.println(); } stmt.close(); conn.close(); } return !failed; } private void dumpOutput(String testName) { String output = outBuffer.getBuffer().toString(); if (output.isEmpty()) { System.out.println("(Test did not produce any output)"); } else { System.out.println("------ Accumulated output for test " + testName + ":"); boolean terminated = output.endsWith(System.lineSeparator()); if (terminated) { System.out.print(output); } else { System.out.println(output); } System.out.println("------ End of accumulated output" + (terminated ? "" : " (no trailing newline)")); } } private void fail(String message) throws Failure { out.println("FAILURE: " + message); throw new Failure(message); } private void checked(String quantity, Object actual) { out.println(" CHECKED: " + "<" + quantity + "> is " + actual + " as expected"); } protected 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); } } protected boolean execute(String query) throws SQLException { try { watchDog.start(); out.println("EXECUTE: " + query); boolean result; result = stmt.execute(query); if (result) { out.println(" OK"); } else { out.println(" OK, updated " + stmt.getUpdateCount() + " rows"); } return result; } finally { watchDog.stop(); } } protected void update(String query, int expectedUpdateCount) throws SQLException, Failure { execute(query); int updateCount = stmt.getUpdateCount(); assertEq("Update count", expectedUpdateCount, updateCount); } protected void expectError(String query, String expectedError) throws SQLException { try { execute(query); } catch (SQLException e) { if (e.getMessage().contains(expectedError)) { out.println(" GOT EXPECTED EXCEPTION: " + e.getMessage()); } else { throw e; } } } protected void assertQueryInt(String query, int expected) throws SQLException, Failure { if (execute(query) == false) { fail("Query does not return a result set"); } ResultSet rs = stmt.getResultSet(); ResultSetMetaData metaData = rs.getMetaData(); assertEq("column count", 1, metaData.getColumnCount()); if (!rs.next()) { fail("Result set is empty"); } int result = rs.getInt(1); if (rs.next()) { String message = "Result set has more than one row"; fail(message); } rs.close(); checked("row count", 1); assertEq("query result", expected, result); } protected String queryString(String query) throws SQLException, Failure { if (execute(query) == false) { fail("Query does not return a result set"); } ResultSet rs = stmt.getResultSet(); ResultSetMetaData metaData = rs.getMetaData(); assertEq("column count", 1, metaData.getColumnCount()); if (!rs.next()) { fail("Result set is empty"); } String result = rs.getString(1); if (rs.next()) { String message = "Result set has more than one row"; fail(message); } rs.close(); checked("row count", 1); return result; } protected 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 } })); } Path p = tmpDir.resolve(name); Files.createDirectory(p); return p; } static class Failure extends Exception { public Failure(String message) { super(message); } public Failure(String message, Throwable cause) { super(message, cause); } } static class WatchDog { private boolean enabled; private long duration = 1000; private long started = 0; private String context = "no context"; WatchDog() { Thread watchDog = new Thread(this::work); watchDog.setName("watchdog_timer"); watchDog.setDaemon(true); watchDog.start(); } synchronized void enable() { this.enabled = true; this.notifyAll(); } synchronized void disable() { this.enabled = false; this.notifyAll(); } synchronized void setContext(String context) { this.context = context; } synchronized void setDuration(long duration) { if (duration <= 0) throw new IllegalArgumentException("duration should be > 0"); this.duration = duration; this.notifyAll(); } synchronized void start() { started = System.currentTimeMillis(); this.notifyAll(); } synchronized void stop() { started = 0; this.notifyAll(); } private synchronized void work() { long now; try { while (true) { now = System.currentTimeMillis(); final long sleepTime; if (started < 0) { // client asked us to go away // System.err.println("++ EXIT"); return; } else if (!enabled || started == 0) { // wait for client to enable/start us sleepTime = 600_000; } else { long deadline = started + duration; sleepTime = deadline - now; } // System.err.printf("++ now=%d, started=now%+d, duration=%d, sleep=%d%n", // now, started - now, duration, sleepTime // ); if (sleepTime > 0) { this.wait(sleepTime); } else { trigger(); return; } } } catch (InterruptedException e) { System.err.println("WATCHDOG TIMER INTERRUPTED, SHOULDN'T HAPPEN"); System.exit(4); } } private void trigger() { String c = context != null ? context : "no context"; System.err.println(); System.err.println(); System.err.println("WATCHDOG TIMER EXPIRED [" + c + "], KILLING TESTS"); System.exit(3); } } }