comparison tests/OnClientTester.java @ 520:b4c7816e3592 onclient

Add more tests
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Thu, 26 Aug 2021 11:19:01 +0200 (2021-08-26)
parents 04a72c5bde80
children 279414178dc6
comparison
equal deleted inserted replaced
519:04a72c5bde80 520:b4c7816e3592
1 import org.monetdb.jdbc.MonetConnection; 1 import org.monetdb.jdbc.MonetConnection;
2 import org.monetdb.jdbc.MonetDownloadHandler;
2 import org.monetdb.jdbc.MonetUploadHandler; 3 import org.monetdb.jdbc.MonetUploadHandler;
3 4
4 import java.io.IOException; 5 import java.io.*;
5 import java.io.PrintStream;
6 import java.io.PrintWriter;
7 import java.io.StringWriter;
8 import java.lang.reflect.InvocationTargetException; 6 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method; 7 import java.lang.reflect.Method;
8 import java.nio.charset.StandardCharsets;
10 import java.sql.*; 9 import java.sql.*;
11 10
12 public final class OnClientTester { 11 public final class OnClientTester {
13 private String jdbcUrl; 12 private String jdbcUrl;
14 boolean verbose = false; 13 boolean verbose = false;
15 boolean succeeded = true; 14 int testCount = 0;
15 int failureCount = 0;
16 private MonetConnection conn; 16 private MonetConnection conn;
17 private PrintWriter out; 17 private PrintWriter out;
18 private Statement stmt; 18 private Statement stmt;
19 private StringWriter outBuffer; 19 private StringWriter outBuffer;
20 20
31 default: 31 default:
32 throw new IllegalArgumentException("Usage: OnClientTester JDBC_URL [TESTNAME]"); 32 throw new IllegalArgumentException("Usage: OnClientTester JDBC_URL [TESTNAME]");
33 } 33 }
34 34
35 OnClientTester tester = new OnClientTester(jdbcUrl); 35 OnClientTester tester = new OnClientTester(jdbcUrl);
36 boolean succeeded;
37 if (specificTest != null) 36 if (specificTest != null)
38 succeeded = tester.runTest(specificTest); 37 tester.runTest(specificTest);
39 else 38 else
40 succeeded = tester.runTests(); 39 tester.runTests();
41 40
42 if (tester.verbosity >= VERBOSITY_ON || tester.failureCount > 0) { 41 System.out.println();
43 System.out.println(); 42 System.out.println("Ran " + tester.testCount + " tests, " + tester.failureCount + " failed");
44 System.out.println("Ran " + tester.testCount + " tests, " + tester.failureCount + " failed");
45 }
46 if (tester.failureCount > 0) { 43 if (tester.failureCount > 0) {
47 System.exit(1); 44 System.exit(1);
48 } 45 }
49 } 46 }
50 47
51 public OnClientTester(String jdbcUrl) { 48 public OnClientTester(String jdbcUrl) {
52 this.jdbcUrl = jdbcUrl; 49 this.jdbcUrl = jdbcUrl;
53 } 50 }
54 51
55 private boolean runTests() throws SQLException, NoSuchMethodException { 52 private void runTests() throws SQLException, NoSuchMethodException {
56 for (Method method: this.getClass().getDeclaredMethods()) { 53 for (Method method: this.getClass().getDeclaredMethods()) {
57 String methodName = method.getName(); 54 String methodName = method.getName();
58 if (methodName.startsWith("test_") && method.getParameterCount() == 0) { 55 if (methodName.startsWith("test_") && method.getParameterCount() == 0) {
59 String testName = methodName.substring(5); 56 String testName = methodName.substring(5);
60 runTest(testName, method); 57 runTest(testName, method);
61 } 58 }
62 } 59 }
63 60 }
64 return succeeded; 61
65 } 62 private void runTest(String testName) throws SQLException, NoSuchMethodException {
66
67 private boolean runTest(String testName) throws SQLException, NoSuchMethodException {
68 String methodName = "test_" + testName; 63 String methodName = "test_" + testName;
69 Method method = this.getClass().getDeclaredMethod(methodName); 64 Method method = this.getClass().getDeclaredMethod(methodName);
70 65 runTest(testName, method);
71 return runTest(testName, method); 66 }
72 } 67
73 68 private synchronized void runTest(String testName, Method method) throws SQLException {
74 private synchronized boolean runTest(String testName, Method method) throws SQLException {
75 outBuffer = new StringWriter(); 69 outBuffer = new StringWriter();
76 out = new PrintWriter(outBuffer); 70 out = new PrintWriter(outBuffer);
77 71
78 Connection genericConnection = DriverManager.getConnection(jdbcUrl); 72 Connection genericConnection = DriverManager.getConnection(jdbcUrl);
79 conn = genericConnection.unwrap(MonetConnection.class); 73 conn = genericConnection.unwrap(MonetConnection.class);
80 stmt = conn.createStatement(); 74 stmt = conn.createStatement();
81 75
82 System.out.println(); 76 System.out.println();
83 77
78 boolean failed = false;
84 try { 79 try {
85 try { 80 try {
86 method.invoke(this); 81 method.invoke(this);
87 } catch (InvocationTargetException e) { 82 } catch (InvocationTargetException e) {
88 Throwable cause = e.getCause(); 83 Throwable cause = e.getCause();
95 } 90 }
96 } 91 }
97 92
98 System.out.println("Test " + testName + " succeeded"); 93 System.out.println("Test " + testName + " succeeded");
99 if (verbose) 94 if (verbose)
100 dumpOutput(); 95 dumpOutput(testName);
101 } catch (Failure e) { 96 } catch (Failure e) {
102 succeeded = false; 97 failed = true;
103 System.out.println("Test " + testName + " failed"); 98 System.out.println("Test " + testName + " failed");
104 dumpOutput(); 99 dumpOutput(testName);
105 } catch (Exception e) { 100 } catch (Exception e) {
106 succeeded = false; 101 failed = true;
107 System.out.println("Test " + testName + " failed:"); 102 System.out.println("Test " + testName + " failed:");
108 e.printStackTrace(); 103 e.printStackTrace();
109 System.out.println(); 104 dumpOutput(testName);
110 dumpOutput();
111 // Show the inner bits of the exception again, they may have scrolled off screen 105 // Show the inner bits of the exception again, they may have scrolled off screen
112 Throwable t = e; 106 Throwable t = e;
113 while (t.getCause() != null) { 107 while (t.getCause() != null) {
114 t = t.getCause(); 108 t = t.getCause();
115 } 109 }
117 System.out.println("Innermost cause was " + t); 111 System.out.println("Innermost cause was " + t);
118 if (t.getStackTrace().length > 0) { 112 if (t.getStackTrace().length > 0) {
119 System.out.println(" at " + t.getStackTrace()[0]); 113 System.out.println(" at " + t.getStackTrace()[0]);
120 } 114 }
121 } finally { 115 } finally {
116 testCount++;
117 if (failed)
118 failureCount++;
122 stmt.close(); 119 stmt.close();
123 conn.close(); 120 conn.close();
124 } 121 }
125 122 }
126 return succeeded; 123
127 } 124 private void dumpOutput(String testName) {
128
129 private void dumpOutput() {
130 String output = outBuffer.getBuffer().toString(); 125 String output = outBuffer.getBuffer().toString();
131 if (output.isEmpty()) { 126 if (output.isEmpty()) {
132 System.out.println("(Test did not produce any output)"); 127 System.out.println("(Test did not produce any output)");
133 } else { 128 } else {
134 System.out.println("------ Accumulated output:"); 129 System.out.println("------ Accumulated output for " + testName + ":");
135 boolean terminated = output.endsWith(System.lineSeparator()); 130 boolean terminated = output.endsWith(System.lineSeparator());
136 if (terminated) { 131 if (terminated) {
137 System.out.print(output); 132 System.out.print(output);
138 } else { 133 } else {
139 System.out.println(output); 134 System.out.println(output);
143 } 138 }
144 139
145 private void fail(String message) throws Failure { 140 private void fail(String message) throws Failure {
146 out.println("FAILURE: " + message); 141 out.println("FAILURE: " + message);
147 throw new Failure(message); 142 throw new Failure(message);
143 }
144
145 private void assertEq(String quantity, Object expected, Object actual) throws Failure {
146 if (expected.equals(actual))
147 return;
148 fail("Expected '" + quantity + "' to be " + expected + ", got " + actual);
148 } 149 }
149 150
150 protected boolean execute(String query) throws SQLException { 151 protected boolean execute(String query) throws SQLException {
151 out.println("EXEC " + query); 152 out.println("EXEC " + query);
152 boolean result; 153 boolean result;
160 } 161 }
161 162
162 protected void update(String query, int expectedUpdateCount) throws SQLException, Failure { 163 protected void update(String query, int expectedUpdateCount) throws SQLException, Failure {
163 execute(query); 164 execute(query);
164 int updateCount = stmt.getUpdateCount(); 165 int updateCount = stmt.getUpdateCount();
165 if (updateCount != expectedUpdateCount) { 166 assertEq("Update count", expectedUpdateCount, updateCount);
166 fail("Query updated " + updateCount + "rows, expected " + expectedUpdateCount);
167 }
168 } 167 }
169 168
170 protected void expectError(String query, String expectedError) throws SQLException, Failure { 169 protected void expectError(String query, String expectedError) throws SQLException, Failure {
171 try { 170 try {
172 execute(query); 171 execute(query);
183 if (execute(query) == false) { 182 if (execute(query) == false) {
184 fail("Query does not return a result set"); 183 fail("Query does not return a result set");
185 } 184 }
186 ResultSet rs = stmt.getResultSet(); 185 ResultSet rs = stmt.getResultSet();
187 ResultSetMetaData metaData = rs.getMetaData(); 186 ResultSetMetaData metaData = rs.getMetaData();
188 if (metaData.getColumnCount() != 1) { 187 assertEq("column count", 1, metaData.getColumnCount());
189 fail("Result should have exactly one column");
190 }
191 if (!rs.next()) { 188 if (!rs.next()) {
192 fail("Result set is empty"); 189 fail("Result set is empty");
193 } 190 }
194 int result = rs.getInt(1); 191 int result = rs.getInt(1);
195 if (rs.next()) { 192 if (rs.next()) {
196 String message = "Result set has more than one row"; 193 String message = "Result set has more than one row";
197 fail(message); 194 fail(message);
198 } 195 }
199 rs.close(); 196 rs.close();
200 if (result != expected) 197 assertEq("query result", expected, result);
201 fail("Query returned " + result + ", expected " + expected);
202 } 198 }
203 199
204 protected void prepare() throws SQLException { 200 protected void prepare() throws SQLException {
205 execute("DROP TABLE IF EXISTS foo"); 201 execute("DROP TABLE IF EXISTS foo");
206 execute("CREATE TABLE foo (i INT, t TEXT)"); 202 execute("CREATE TABLE foo (i INT, t TEXT)");
223 219
224 MyUploadHandler(String errorMessage) { 220 MyUploadHandler(String errorMessage) {
225 this(0, -1, errorMessage); 221 this(0, -1, errorMessage);
226 } 222 }
227 223
228
229 @Override 224 @Override
230 public void handleUpload(MonetConnection.Upload handle, String name, boolean textMode, int offset) throws IOException { 225 public void handleUpload(MonetConnection.Upload handle, String name, boolean textMode, int offset) throws IOException {
231 int toSkip = offset > 0 ? offset - 1 : 0; 226 int toSkip = offset > 0 ? offset - 1 : 0;
232 if (errorAt == -1 && errorMessage != null) { 227 if (errorAt == -1 && errorMessage != null) {
233 handle.sendError(errorMessage); 228 handle.sendError(errorMessage);
241 stream.printf("%d|%d%n", i + 1, i + 1); 236 stream.printf("%d|%d%n", i + 1, i + 1);
242 } 237 }
243 } 238 }
244 239
245 } 240 }
241
242 static class MyDownloadHandler implements MonetDownloadHandler {
243 private final int errorAtByte;
244 private final String errorMessage;
245 private int attempts = 0;
246 private int bytesSeen = 0;
247 private int lineEndingsSeen = 0;
248 private int startOfLine = 0;
249
250 MyDownloadHandler(int errorAtByte, String errorMessage) {
251 this.errorAtByte = errorAtByte;
252 this.errorMessage = errorMessage;
253 }
254
255 MyDownloadHandler(String errorMessage) {
256 this(-1, errorMessage);
257 }
258
259 MyDownloadHandler() {
260 this(-1, null);
261 }
262
263 @Override
264 public void handleDownload(MonetConnection.Download handle, String name, boolean textMode) throws IOException {
265 attempts++;
266 bytesSeen = 0;
267 lineEndingsSeen = 0;
268 startOfLine = 0;
269
270 if (errorMessage != null && errorAtByte < 0) {
271 handle.sendError(errorMessage);
272 return;
273 }
274
275 InputStream stream = handle.getStream();
276 byte[] buffer = new byte[1024];
277 while (true) {
278 int toRead = buffer.length;
279 if (errorMessage != null && errorAtByte >= 0) {
280 if (bytesSeen == errorAtByte) {
281 throw new IOException(errorMessage);
282 }
283 toRead = Integer.min(toRead, errorAtByte - bytesSeen);
284 }
285 int nread = stream.read(buffer, 0, toRead);
286 if (nread < 0)
287 break;
288 for (int i = 0; i < nread; i++) {
289 if (buffer[i] == '\n') {
290 lineEndingsSeen += 1;
291 startOfLine = bytesSeen + i + 1;
292 }
293 }
294 bytesSeen += nread;
295 }
296 }
297
298 public int countAttempts() {
299 return attempts;
300 }
301 public int countBytes() {
302 return bytesSeen;
303 }
304
305 public int lineCount() {
306 int lines = lineEndingsSeen;
307 if (startOfLine != bytesSeen)
308 lines++;
309 return lines;
310 }
311 }
312
313
246 static class Failure extends Exception { 314 static class Failure extends Exception {
247 315
248 public Failure(String message) { 316 public Failure(String message) {
249 super(message); 317 super(message);
250 } 318 }
259 conn.setUploadHandler(new MyUploadHandler(100)); 327 conn.setUploadHandler(new MyUploadHandler(100));
260 update("COPY INTO foo FROM 'banana' ON CLIENT", 100); 328 update("COPY INTO foo FROM 'banana' ON CLIENT", 100);
261 queryInt("SELECT COUNT(*) FROM foo", 100); 329 queryInt("SELECT COUNT(*) FROM foo", 100);
262 } 330 }
263 331
264 private void test_ImmediateError() throws Exception { 332 private void test_ClientRefuses() throws Exception {
265 prepare(); 333 prepare();
266 conn.setUploadHandler(new MyUploadHandler("immediate error")); 334 conn.setUploadHandler(new MyUploadHandler("immediate error"));
267 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "immediate error"); 335 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "immediate error");
268 queryInt("SELECT COUNT(*) FROM foo", 0); 336 queryInt("SELECT COUNT(*) FROM foo", 0);
269 } 337 }
290 update("COPY OFFSET 5 INTO foo FROM 'banana' ON CLIENT", 96); 358 update("COPY OFFSET 5 INTO foo FROM 'banana' ON CLIENT", 96);
291 queryInt("SELECT MIN(i) FROM foo", 5); 359 queryInt("SELECT MIN(i) FROM foo", 5);
292 queryInt("SELECT MAX(i) FROM foo", 100); 360 queryInt("SELECT MAX(i) FROM foo", 100);
293 } 361 }
294 362
295 private void test_ServerCancels() throws SQLException, Failure { 363 private void testx_ServerCancels() throws SQLException, Failure {
296 prepare(); 364 prepare();
297 conn.setUploadHandler(new MyUploadHandler(100)); 365 conn.setUploadHandler(new MyUploadHandler(100));
298 update("COPY 10 RECORDS INTO foo FROM 'banana' ON CLIENT", 96); 366 update("COPY 10 RECORDS INTO foo FROM 'banana' ON CLIENT", 96);
299 queryInt("SELECT MIN(i) FROM foo", 5); 367 // Server stopped reading after 10 rows. Will we stay in sync?
300 queryInt("SELECT MAX(i) FROM foo", 100); 368 queryInt("SELECT COUNT(i) FROM foo", 10);
369 }
370
371 private void test_Download() throws SQLException, Failure {
372 prepare();
373 MyDownloadHandler handler = new MyDownloadHandler();
374 conn.setDownloadHandler(handler);
375 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)", 100);
376 update("COPY (SELECT * FROM foo) INTO 'banana' ON CLIENT", -1);
377 assertEq("download attempts", 1, handler.countAttempts());
378 assertEq("lines downloaded", 100, handler.lineCount());
379 }
380
381 private void test_CancelledDownload() throws SQLException, Failure {
382 prepare();
383 MyDownloadHandler handler = new MyDownloadHandler("download refused");
384 conn.setDownloadHandler(handler);
385 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)", 100);
386 expectError("COPY (SELECT * FROM foo) INTO 'banana' ON CLIENT", "download refused");
387 // check if the connection still works
388 queryInt("SELECT 42", 42);
301 } 389 }
302 390
303 391
304 } 392 }