Mercurial > hg > monetdb-java
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 } |