comparison tests/OnClientTester.java @ 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 718492fb8714
children b58f6f26fab0
comparison
equal deleted inserted replaced
590:73f4f30e4c62 591:b2cd3b828967
12 import org.monetdb.util.FileTransferHandler; 12 import org.monetdb.util.FileTransferHandler;
13 13
14 import java.io.*; 14 import java.io.*;
15 import java.nio.charset.Charset; 15 import java.nio.charset.Charset;
16 import java.nio.charset.StandardCharsets; 16 import java.nio.charset.StandardCharsets;
17 import java.nio.file.FileVisitResult;
17 import java.nio.file.Files; 18 import java.nio.file.Files;
18 import java.nio.file.Path; 19 import java.nio.file.Path;
20 import java.nio.file.SimpleFileVisitor;
21 import java.nio.file.attribute.BasicFileAttributes;
22 import java.sql.Connection;
19 import java.sql.DriverManager; 23 import java.sql.DriverManager;
24 import java.sql.ResultSet;
25 import java.sql.ResultSetMetaData;
26 import java.sql.Statement;
20 import java.sql.SQLException; 27 import java.sql.SQLException;
21 import java.util.List; 28 import java.util.List;
22 29
23 import static java.nio.file.StandardOpenOption.CREATE_NEW; 30 import static java.nio.file.StandardOpenOption.CREATE_NEW;
24 31
25 public final class OnClientTester extends TestRunner { 32
26 33 /**
27 public OnClientTester(String jdbcUrl, int verbosity, boolean watchDogEnabled) { 34 * Program to test MonetDB JDBC Driver in combination with SQL:
28 super(jdbcUrl, verbosity, watchDogEnabled); 35 * COPY ... INTO ... ON CLIENT
29 } 36 * commands.
30 37 * This allows Java programmers to locally (so ON CLIENT) stream csv data
31 public static void main(String[] args) throws SQLException, NoSuchMethodException, ClassNotFoundException { 38 * to and from the MonetDB server for fast bulk data import / export.
39 *
40 * Specifically it tests the MonetDB specific extensions to register upload and download handlers
41 * see {@link org.monetdb.jdbc.MonetConnection#setUploadHandler(UploadHandler)}
42 * see {@link org.monetdb.jdbc.MonetConnection#setUploadHandler(DownloadHandler)}
43 * and streaming of csv data to and from the MonetDB server using MAPI protocol.
44 *
45 * It also tests reading / writing data from / to a local file using
46 * {@link org.monetdb.util.FileTransferHandler}
47 *
48 * @author JvR
49 * @version 0.1
50 */
51 public final class OnClientTester {
52 public static final int VERBOSITY_NONE = 0;
53 public static final int VERBOSITY_ON = 1;
54 public static final int VERBOSITY_SHOW_ALL = 2;
55
56 private final String jdbcUrl;
57 private final int verbosity;
58 private String currentTestName;
59 private long startTime;
60 private MonetConnection conn;
61 private Statement stmt;
62 private StringBuilder outBuffer;
63 private Path tmpDir;
64
65 public OnClientTester(String jdbcUrl, int verbosity) {
66 this.jdbcUrl = jdbcUrl;
67 this.verbosity = verbosity;
68 }
69
70 public static void main(String[] args) {
32 String jdbcUrl = null; 71 String jdbcUrl = null;
33 String requiredPrefix = null;
34 int verbosity = 0; 72 int verbosity = 0;
35 boolean watchDogEnabled = true;
36
37 // Don't know why I need this all of a sudden.. is it only on my system?
38 // Class.forName("org.monetdb.jdbc.MonetDriver"); // should not be needed if you add: import java.sql.DriverManager;
39 73
40 for (String arg : args) { 74 for (String arg : args) {
41 if (arg.equals("-v")) 75 if (arg.equals("-v"))
42 verbosity++; 76 verbosity++;
43 else if (arg.equals("-vv")) 77 else if (arg.equals("-vv"))
44 verbosity += 2; 78 verbosity += 2;
45 else if (arg.equals("-w"))
46 watchDogEnabled = false;
47 else if (jdbcUrl == null) 79 else if (jdbcUrl == null)
48 jdbcUrl = arg; 80 jdbcUrl = arg;
49 else if (requiredPrefix == null)
50 requiredPrefix = arg;
51 else { 81 else {
52 System.err.println("Unexpected argument " + arg); 82 System.err.println("Unexpected argument " + arg);
53 System.exit(2); 83 System.exit(2);
54 } 84 }
55 } 85 }
56 86 if (jdbcUrl == null || jdbcUrl.isEmpty()) {
57 OnClientTester tester = new OnClientTester(jdbcUrl, verbosity, watchDogEnabled); 87 System.err.println("Missing required startup argument: JDBC_connection_URL");
58 int failures = tester.runTests(requiredPrefix); 88 System.exit(1);
59 89 }
90
91 OnClientTester tester = new OnClientTester(jdbcUrl, verbosity);
92 int failures = tester.runTests();
60 if (failures > 0) 93 if (failures > 0)
61 System.exit(1); 94 System.exit(-1);
95 }
96
97 public int runTests() {
98 if (! openConnection())
99 return 1; // failed to open JDBC connection to MonetDB
100
101 outBuffer = new StringBuilder(1024);
102
103 int failures = 0;
104 try {
105 // all test methods start with test_ and have no arguments
106 test_BugFixLevel();
107 test_Upload();
108 test_ClientRefusesUpload();
109 test_Offset0();
110 test_Offset1();
111 test_Offset5();
112 test_ServerStopsReading();
113 test_Download();
114 test_ClientRefusesDownload();
115 test_LargeUpload();
116 test_LargeDownload();
117 test_UploadFromStream();
118 test_UploadFromReader();
119 test_UploadFromReaderOffset();
120 test_FailUploadLate();
121 test_FailUploadLate2();
122 test_FailDownloadLate();
123 test_FileTransferHandlerUploadUtf8();
124 test_FileTransferHandlerUploadLatin1();
125 test_FileTransferHandlerUploadNull();
126 test_FileTransferHandlerUploadRefused();
127 test_FileTransferHandlerDownloadUtf8();
128 test_FileTransferHandlerDownloadLatin1();
129 test_FileTransferHandlerDownloadNull();
130 test_FileTransferHandlerDownloadRefused();
131 } catch (Failure e) {
132 failures++;
133 System.err.println();
134 System.err.println("Test " + currentTestName + " failed");
135 dumpOutput();
136 } catch (Exception e) {
137 failures++;
138 System.err.println();
139 System.err.println("Test " + currentTestName + " failed:");
140 e.printStackTrace(System.err);
141 dumpOutput();
142 // Show the inner bits of the exception again, they may have scrolled off screen
143 Throwable t = e;
144 while (t.getCause() != null) {
145 t = t.getCause();
146 }
147 System.err.println("Innermost cause was " + t);
148 if (t.getStackTrace().length > 0) {
149 System.err.println(" at " + t.getStackTrace()[0]);
150 }
151 }
152 closeConnection();
153 return failures;
154 }
155
156 private boolean openConnection() {
157 try {
158 // make a connection to MonetDB, its reused for all tests
159 final Connection genericConnection = DriverManager.getConnection(jdbcUrl);
160 conn = genericConnection.unwrap(MonetConnection.class);
161 stmt = conn.createStatement();
162 return true;
163 } catch (SQLException e) {
164 System.err.println("Failed to connect using JDBC URL: " + jdbcUrl);
165 System.err.println(e);
166 }
167 return false;
168 }
169
170 private void closeConnection() {
171 if (stmt != null) {
172 try {
173 stmt.close();
174 } catch (SQLException e) { /* ignore */ }
175 stmt = null;
176 }
177 if (conn != null) {
178 try {
179 conn.close();
180 } catch (Exception e) { /* ignore */ }
181 conn = null;
182 }
183 }
184
185 private void initTest(final String name) {
186 currentTestName = name;
187 outBuffer.setLength(0); // clear the output log buffer
188 startTime = System.currentTimeMillis();
189 }
190
191 private void exitTest() {
192 if (verbosity > VERBOSITY_ON)
193 System.err.println();
194 if (verbosity >= VERBOSITY_ON) {
195 final long duration = System.currentTimeMillis() - startTime;
196 System.err.println("Test " + currentTestName + " succeeded in " + duration + "ms");
197 }
198 if (verbosity >= VERBOSITY_SHOW_ALL)
199 dumpOutput();
200
201 if (conn.isClosed())
202 openConnection(); // restore connection for next test
203 }
204
205 private void dumpOutput() {
206 final String output = outBuffer.toString();
207 if (output.isEmpty()) {
208 System.err.println("(Test " + currentTestName + " did not produce any output)");
209 } else {
210 System.err.println("------ Accumulated output for test " + currentTestName + ":");
211 System.err.println(output);
212 System.err.println("------ End of accumulated output");
213 }
62 } 214 }
63 215
64 /// Some tests have to work limitations of the protocol or bugs in the server. 216 /// Some tests have to work limitations of the protocol or bugs in the server.
65 /// This Enum is used to indicate the possibilities. 217 /// This Enum is used to indicate the possibilities.
66 public enum BugFixLevel { 218 private enum BugFixLevel {
67 /// Only those tests that work with older MonetDB versions 219 /// Only those tests that work with older MonetDB versions
68 Baseline(0, 0, 0), 220 Baseline(0, 0, 0),
69 221
70 /// Connection keeps working after download request has been refused by client 222 /// Connection keeps working after download request has been refused by client
71 CanRefuseDownload(11, 41, 12), 223 CanRefuseDownload(11, 41, 12),
114 } 266 }
115 return lastValid; 267 return lastValid;
116 } 268 }
117 } 269 }
118 270
119 void prepare() throws SQLException { 271 private void prepare() throws SQLException {
120 execute("DROP TABLE IF EXISTS foo"); 272 execute("DROP TABLE IF EXISTS foo");
121 execute("CREATE TABLE foo (i INT, t TEXT)"); 273 execute("CREATE TABLE foo (i INT, t CLOB)");
122 } 274 }
123 275
124 private BugFixLevel getLevel() throws SQLException, Failure { 276 private BugFixLevel getLevel() throws SQLException, Failure {
125 String version = queryString("SELECT value FROM environment WHERE name = 'monet_version'"); 277 String version = queryString("SELECT value FROM environment WHERE name = 'monet_version'");
126 BugFixLevel level = BugFixLevel.forVersion(version); 278 BugFixLevel level = BugFixLevel.forVersion(version);
127 out.println(" NOTE: version " + version + " means level = " + level); 279 outBuffer.append(" NOTE: version ").append(version).append(" means level = ").append(level).append("\n");
128 return level; 280 return level;
129 } 281 }
130 282
131 public void test_BugFixLevel() throws Failure { 283 private void test_BugFixLevel() throws Failure {
284 initTest("test_BugFixLevel");
285
132 assertEq("Baseline includes 0.0.0", true, BugFixLevel.Baseline.includesVersion(0, 0, 0)); 286 assertEq("Baseline includes 0.0.0", true, BugFixLevel.Baseline.includesVersion(0, 0, 0));
133 assertEq("Baseline includes 11.41.11", true, BugFixLevel.Baseline.includesVersion(11, 41, 11)); 287 assertEq("Baseline includes 11.41.11", true, BugFixLevel.Baseline.includesVersion(11, 41, 11));
134 assertEq("Baseline includes 11.41.12", true, BugFixLevel.Baseline.includesVersion(11, 41, 12)); 288 assertEq("Baseline includes 11.41.12", true, BugFixLevel.Baseline.includesVersion(11, 41, 12));
135 289
136 assertEq("CanRefuseDownload includes 0.0.0", false, BugFixLevel.CanRefuseDownload.includesVersion(0, 0, 0)); 290 assertEq("CanRefuseDownload includes 0.0.0", false, BugFixLevel.CanRefuseDownload.includesVersion(0, 0, 0));
152 assertEq("Level for 11.42.0", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion(11, 42, 0)); 306 assertEq("Level for 11.42.0", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion(11, 42, 0));
153 assertEq("Level for 12.0.0", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion(12, 0, 0)); 307 assertEq("Level for 12.0.0", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion(12, 0, 0));
154 308
155 assertEq("Level for \"11.41.11\"", BugFixLevel.Baseline, BugFixLevel.forVersion("11.41.11")); 309 assertEq("Level for \"11.41.11\"", BugFixLevel.Baseline, BugFixLevel.forVersion("11.41.11"));
156 assertEq("Level for \"11.41.12\"", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion("11.41.12")); 310 assertEq("Level for \"11.41.12\"", BugFixLevel.CanRefuseDownload, BugFixLevel.forVersion("11.41.12"));
157 } 311
158 312 exitTest();
159 public void test_Upload() throws SQLException, Failure { 313 }
314
315 private void test_Upload() throws SQLException, Failure {
316 initTest("test_Upload");
160 prepare(); 317 prepare();
161 MyUploadHandler handler = new MyUploadHandler(100); 318 MyUploadHandler handler = new MyUploadHandler(100);
162 conn.setUploadHandler(handler); 319 conn.setUploadHandler(handler);
163 update("COPY INTO foo FROM 'banana' ON CLIENT"); 320 update("COPY INTO foo FROM 'banana' ON CLIENT");
164 assertEq("cancellation callback called", false, handler.isCancelled()); 321 assertEq("cancellation callback called", false, handler.isCancelled());
165 assertQueryInt("SELECT COUNT(*) FROM foo", 100); 322 assertQueryInt("SELECT COUNT(*) FROM foo", 100);
166 } 323 exitTest();
167 324 }
168 public void test_ClientRefusesUpload() throws SQLException, Failure { 325
326 private void test_ClientRefusesUpload() throws SQLException, Failure {
327 initTest("test_ClientRefusesUpload");
169 prepare(); 328 prepare();
170 MyUploadHandler handler = new MyUploadHandler("immediate error"); 329 MyUploadHandler handler = new MyUploadHandler("immediate error");
171 conn.setUploadHandler(handler); 330 conn.setUploadHandler(handler);
172 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "immediate error"); 331 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "immediate error");
173 assertEq("cancellation callback called", false, handler.isCancelled()); 332 assertEq("cancellation callback called", false, handler.isCancelled());
174 assertQueryInt("SELECT COUNT(*) FROM foo", 0); 333 assertQueryInt("SELECT COUNT(*) FROM foo", 0);
175 } 334 exitTest();
176 335 }
177 public void test_Offset0() throws SQLException, Failure { 336
337 private void test_Offset0() throws SQLException, Failure {
338 initTest("test_Offset0");
178 prepare(); 339 prepare();
179 MyUploadHandler handler = new MyUploadHandler(100); 340 MyUploadHandler handler = new MyUploadHandler(100);
180 conn.setUploadHandler(handler); 341 conn.setUploadHandler(handler);
181 update("COPY OFFSET 0 INTO foo FROM 'banana' ON CLIENT"); 342 update("COPY OFFSET 0 INTO foo FROM 'banana' ON CLIENT");
182 assertEq("cancellation callback called", false, handler.isCancelled()); 343 assertEq("cancellation callback called", false, handler.isCancelled());
183 assertQueryInt("SELECT MIN(i) FROM foo", 1); 344 assertQueryInt("SELECT MIN(i) FROM foo", 1);
184 assertQueryInt("SELECT MAX(i) FROM foo", 100); 345 assertQueryInt("SELECT MAX(i) FROM foo", 100);
185 } 346 exitTest();
186 347 }
187 public void test_Offset1() throws SQLException, Failure { 348
349 private void test_Offset1() throws SQLException, Failure {
350 initTest("test_Offset1");
188 prepare(); 351 prepare();
189 MyUploadHandler handler = new MyUploadHandler(100); 352 MyUploadHandler handler = new MyUploadHandler(100);
190 conn.setUploadHandler(handler); 353 conn.setUploadHandler(handler);
191 update("COPY OFFSET 1 INTO foo FROM 'banana' ON CLIENT"); 354 update("COPY OFFSET 1 INTO foo FROM 'banana' ON CLIENT");
192 assertEq("cancellation callback called", false, handler.isCancelled()); 355 assertEq("cancellation callback called", false, handler.isCancelled());
193 assertQueryInt("SELECT MIN(i) FROM foo", 1); 356 assertQueryInt("SELECT MIN(i) FROM foo", 1);
194 assertQueryInt("SELECT MAX(i) FROM foo", 100); 357 assertQueryInt("SELECT MAX(i) FROM foo", 100);
195 } 358 exitTest();
196 359 }
197 public void test_Offset5() throws SQLException, Failure { 360
361 private void test_Offset5() throws SQLException, Failure {
362 initTest("test_Offset5");
198 prepare(); 363 prepare();
199 MyUploadHandler handler = new MyUploadHandler(100); 364 MyUploadHandler handler = new MyUploadHandler(100);
200 conn.setUploadHandler(handler); 365 conn.setUploadHandler(handler);
201 update("COPY OFFSET 5 INTO foo FROM 'banana' ON CLIENT"); 366 update("COPY OFFSET 5 INTO foo FROM 'banana' ON CLIENT");
202 assertEq("cancellation callback called", false, handler.isCancelled()); 367 assertEq("cancellation callback called", false, handler.isCancelled());
203 assertQueryInt("SELECT MIN(i) FROM foo", 5); 368 assertQueryInt("SELECT MIN(i) FROM foo", 5);
204 assertQueryInt("SELECT MAX(i) FROM foo", 100); 369 assertQueryInt("SELECT MAX(i) FROM foo", 100);
205 } 370 exitTest();
206 371 }
207 public void test_ServerStopsReading() throws SQLException, Failure { 372
373 private void test_ServerStopsReading() throws SQLException, Failure {
374 initTest("test_ServerStopsReading");
208 prepare(); 375 prepare();
209 MyUploadHandler handler = new MyUploadHandler(100); 376 MyUploadHandler handler = new MyUploadHandler(100);
210 conn.setUploadHandler(handler); 377 conn.setUploadHandler(handler);
211 update("COPY 10 RECORDS INTO foo FROM 'banana' ON CLIENT"); 378 update("COPY 10 RECORDS INTO foo FROM 'banana' ON CLIENT");
212 assertEq("cancellation callback called", true, handler.isCancelled()); 379 assertEq("cancellation callback called", true, handler.isCancelled());
213 assertEq("handler encountered write error", true, handler.encounteredWriteError()); 380 assertEq("handler encountered write error", true, handler.encounteredWriteError());
214 // connection is still alive 381 // connection is still alive
215 assertQueryInt("SELECT COUNT(i) FROM foo", 10); 382 assertQueryInt("SELECT COUNT(i) FROM foo", 10);
216 } 383 exitTest();
217 384 }
218 public void test_Download(int n) throws SQLException, Failure { 385
386 private void test_Download(int n) throws SQLException, Failure {
219 prepare(); 387 prepare();
220 MyDownloadHandler handler = new MyDownloadHandler(); 388 MyDownloadHandler handler = new MyDownloadHandler();
221 conn.setDownloadHandler(handler); 389 conn.setDownloadHandler(handler);
222 String q = "INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, " + n + ")"; 390 String q = "INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, " + n + ")";
223 update(q); 391 update(q);
226 assertEq("lines downloaded", n, handler.lineCount()); 394 assertEq("lines downloaded", n, handler.lineCount());
227 // connection is still alive 395 // connection is still alive
228 assertQueryInt("SELECT COUNT(*) FROM foo", n); 396 assertQueryInt("SELECT COUNT(*) FROM foo", n);
229 } 397 }
230 398
231 public void test_Download() throws SQLException, Failure { 399 private void test_Download() throws SQLException, Failure {
400 initTest("test_Download");
232 test_Download(100); 401 test_Download(100);
233 } 402 exitTest();
234 403 }
235 public void test_ClientRefusesDownload() throws SQLException, Failure { 404
405 private void test_ClientRefusesDownload() throws SQLException, Failure {
406 initTest("test_ClientRefusesDownload");
236 prepare(); 407 prepare();
237 BugFixLevel level = getLevel(); 408 BugFixLevel level = getLevel();
238 MyDownloadHandler handler = new MyDownloadHandler("download refused"); 409 MyDownloadHandler handler = new MyDownloadHandler("download refused");
239 conn.setDownloadHandler(handler); 410 conn.setDownloadHandler(handler);
240 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)"); 411 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)");
243 expectError("SELECT 42 -- check if the connection still works", "Connection to server lost!"); 414 expectError("SELECT 42 -- check if the connection still works", "Connection to server lost!");
244 if (level.compareTo(BugFixLevel.CanRefuseDownload) >= 0) { 415 if (level.compareTo(BugFixLevel.CanRefuseDownload) >= 0) {
245 // connection is still alive 416 // connection is still alive
246 assertQueryInt("SELECT COUNT(*) FROM foo", 100); 417 assertQueryInt("SELECT COUNT(*) FROM foo", 100);
247 } 418 }
248 } 419 exitTest();
249 420 }
250 public void test_LargeUpload() throws SQLException, Failure { 421
251 watchDog.setDuration(25_000); 422 private void test_LargeUpload() throws SQLException, Failure {
423 initTest("test_LargeUpload");
252 prepare(); 424 prepare();
253 int n = 4_000_000; 425 int n = 4_000_000;
254 MyUploadHandler handler = new MyUploadHandler(n); 426 MyUploadHandler handler = new MyUploadHandler(n);
255 conn.setUploadHandler(handler); 427 conn.setUploadHandler(handler);
256 handler.setChunkSize(1024 * 1024); 428 handler.setChunkSize(1024 * 1024);
257 update("COPY INTO foo FROM 'banana' ON CLIENT"); 429 update("COPY INTO foo FROM 'banana' ON CLIENT");
258 assertEq("cancellation callback called", false, handler.isCancelled()); 430 assertEq("cancellation callback called", false, handler.isCancelled());
259 // connection is still alive 431 // connection is still alive
260 assertQueryInt("SELECT COUNT(DISTINCT i) FROM foo", n); 432 assertQueryInt("SELECT COUNT(DISTINCT i) FROM foo", n);
261 } 433 exitTest();
262 434 }
263 public void test_LargeDownload() throws SQLException, Failure { 435
264 watchDog.setDuration(25_000); 436 private void test_LargeDownload() throws SQLException, Failure {
437 initTest("test_LargeDownload");
265 test_Download(4_000_000); 438 test_Download(4_000_000);
266 } 439 exitTest();
267 440 }
268 public void test_UploadFromStream() throws SQLException, Failure { 441
442 private void test_UploadFromStream() throws SQLException, Failure {
443 initTest("test_UploadFromStream");
269 prepare(); 444 prepare();
270 UploadHandler handler = new UploadHandler() { 445 UploadHandler handler = new UploadHandler() {
271 final String data = "1|one\n2|two\n3|three\n"; 446 final String data = "1|one\n2|two\n3|three\n";
272 447
273 @Override 448 @Override
279 }; 454 };
280 conn.setUploadHandler(handler); 455 conn.setUploadHandler(handler);
281 update("COPY INTO foo FROM 'banana' ON CLIENT"); 456 update("COPY INTO foo FROM 'banana' ON CLIENT");
282 // connection is still alive 457 // connection is still alive
283 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3); 458 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
284 } 459 exitTest();
285 460 }
286 public void test_UploadFromReader() throws SQLException, Failure { 461
462 private void test_UploadFromReader() throws SQLException, Failure {
463 initTest("test_UploadFromReader");
287 prepare(); 464 prepare();
288 UploadHandler handler = new UploadHandler() { 465 UploadHandler handler = new UploadHandler() {
289 final String data = "1|one\n2|two\n3|three\n"; 466 final String data = "1|one\n2|two\n3|three\n";
290 467
291 @Override 468 @Override
296 } 473 }
297 }; 474 };
298 conn.setUploadHandler(handler); 475 conn.setUploadHandler(handler);
299 update("COPY INTO foo FROM 'banana' ON CLIENT"); 476 update("COPY INTO foo FROM 'banana' ON CLIENT");
300 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3); 477 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
301 } 478 exitTest();
302 479 }
303 public void test_UploadFromReaderOffset() throws SQLException, Failure { 480
481 private void test_UploadFromReaderOffset() throws SQLException, Failure {
482 initTest("test_UploadFromReaderOffset");
304 prepare(); 483 prepare();
305 UploadHandler handler = new UploadHandler() { 484 UploadHandler handler = new UploadHandler() {
306 final String data = "1|one\n2|two\n3|three\n"; 485 final String data = "1|one\n2|two\n3|three\n";
307 486
308 @Override 487 @Override
312 } 491 }
313 }; 492 };
314 conn.setUploadHandler(handler); 493 conn.setUploadHandler(handler);
315 update("COPY OFFSET 2 INTO foo FROM 'banana' ON CLIENT"); 494 update("COPY OFFSET 2 INTO foo FROM 'banana' ON CLIENT");
316 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3); 495 assertQueryInt("SELECT i FROM foo WHERE t = 'three'", 3);
317 } 496 exitTest();
318 497 }
319 public void test_FailUploadLate() throws SQLException, Failure { 498
499 private void test_FailUploadLate() throws SQLException, Failure {
500 initTest("test_FailUploadLate");
320 prepare(); 501 prepare();
321 MyUploadHandler handler = new MyUploadHandler(100, 50, "i don't like line 50"); 502 MyUploadHandler handler = new MyUploadHandler(100, 50, "i don't like line 50");
322 conn.setUploadHandler(handler); 503 conn.setUploadHandler(handler);
323 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "i don't like"); 504 expectError("COPY INTO foo FROM 'banana' ON CLIENT", "i don't like");
324 assertEq("cancellation callback called", false, handler.isCancelled()); 505 assertEq("cancellation callback called", false, handler.isCancelled());
325 assertEq("connection is closed", true, conn.isClosed()); 506 assertEq("connection is closed", true, conn.isClosed());
326 } 507 exitTest();
327 508 }
328 public void test_FailUploadLate2() throws SQLException, Failure { 509
329 // Here we send empty lines only, to check if the server detects is properly instead 510 private void test_FailUploadLate2() throws SQLException, Failure {
511 initTest("test_FailUploadLate2");
512 // Here we send empty lines only, to check if the server detects it properly instead
330 // of simply complaining about an incomplete file. 513 // of simply complaining about an incomplete file.
331 prepare(); 514 prepare();
332 UploadHandler handler = new UploadHandler() { 515 UploadHandler handler = new UploadHandler() {
333 @Override 516 @Override
334 public void handleUpload(MonetConnection.Upload handle, String name, boolean textMode, long linesToSkip) throws IOException { 517 public void handleUpload(MonetConnection.Upload handle, String name, boolean textMode, long linesToSkip) throws IOException {
342 }; 525 };
343 conn.setUploadHandler(handler); 526 conn.setUploadHandler(handler);
344 expectError("COPY INTO foo(t) FROM 'banana'(t) ON CLIENT", "after all"); 527 expectError("COPY INTO foo(t) FROM 'banana'(t) ON CLIENT", "after all");
345 assertEq("connection is closed", true, conn.isClosed()); 528 assertEq("connection is closed", true, conn.isClosed());
346 // Cannot check the server log, but at the time I checked, it said "prematurely stopped client", which is fine. 529 // Cannot check the server log, but at the time I checked, it said "prematurely stopped client", which is fine.
347 } 530 exitTest();
348 531 }
349 public void test_FailDownloadLate() throws SQLException, Failure { 532
533 private void test_FailDownloadLate() throws SQLException, Failure {
534 initTest("test_FailDownloadLate");
350 prepare(); 535 prepare();
351 MyDownloadHandler handler = new MyDownloadHandler(200, "download refused"); 536 MyDownloadHandler handler = new MyDownloadHandler(200, "download refused");
352 conn.setDownloadHandler(handler); 537 conn.setDownloadHandler(handler);
353 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)"); 538 update("INSERT INTO foo SELECT value as i, 'number' || value AS t FROM sys.generate_series(0, 100)");
354 expectError("COPY (SELECT * FROM sys.generate_series(0,200)) INTO 'banana' ON CLIENT", "download refused"); 539 expectError("COPY (SELECT * FROM sys.generate_series(0,200)) INTO 'banana' ON CLIENT", "download refused");
355 // Exception closes the connection 540 // Exception closes the connection
356 assertEq("connection is closed", conn.isClosed(), true); 541 assertEq("connection is closed", conn.isClosed(), true);
357 } 542 exitTest();
358 543 }
359 public void test_FileTransferHandlerUploadUtf8() throws IOException, SQLException, Failure { 544
545 private void test_FileTransferHandlerUploadUtf8() throws IOException, SQLException, Failure {
546 initTest("test_FileTransferHandlerUploadUtf8");
360 testFileTransferHandlerUpload(StandardCharsets.UTF_8, "UTF-8"); 547 testFileTransferHandlerUpload(StandardCharsets.UTF_8, "UTF-8");
361 } 548 exitTest();
362 549 }
363 public void test_FileTransferHandlerUploadLatin1() throws IOException, SQLException, Failure { 550
551 private void test_FileTransferHandlerUploadLatin1() throws IOException, SQLException, Failure {
552 initTest("test_FileTransferHandlerUploadLatin1");
364 testFileTransferHandlerUpload(Charset.forName("latin1"), "latin1"); 553 testFileTransferHandlerUpload(Charset.forName("latin1"), "latin1");
365 } 554 exitTest();
366 555 }
367 public void test_FileTransferHandlerUploadNull() throws IOException, SQLException, Failure { 556
557 private void test_FileTransferHandlerUploadNull() throws IOException, SQLException, Failure {
558 initTest("test_FileTransferHandlerUploadNull");
368 testFileTransferHandlerUpload(null, Charset.defaultCharset().name()); 559 testFileTransferHandlerUpload(null, Charset.defaultCharset().name());
369 } 560 exitTest();
370 561 }
371 public void testFileTransferHandlerUpload(Charset handlerEncoding, String fileEncoding) throws IOException, SQLException, Failure { 562
563 private void testFileTransferHandlerUpload(Charset handlerEncoding, String fileEncoding) throws IOException, SQLException, Failure {
372 prepare(); 564 prepare();
373 Path d = getTmpDir(currentTestName); 565 Path d = getTmpDir(currentTestName);
374 Path f = d.resolve("data.txt"); 566 Path f = d.resolve("data.txt");
375 OutputStream s = Files.newOutputStream(f, CREATE_NEW); 567 OutputStream s = Files.newOutputStream(f, CREATE_NEW);
376 PrintStream ps = new PrintStream(s, false, fileEncoding); 568 PrintStream ps = new PrintStream(s, false, fileEncoding);
382 update("COPY INTO foo FROM 'data.txt' ON CLIENT"); 574 update("COPY INTO foo FROM 'data.txt' ON CLIENT");
383 assertQueryInt("SELECT SUM(i) FROM foo", 6); 575 assertQueryInt("SELECT SUM(i) FROM foo", 6);
384 assertQueryString("SELECT t FROM foo WHERE i = 2", "twø"); 576 assertQueryString("SELECT t FROM foo WHERE i = 2", "twø");
385 } 577 }
386 578
387 public void test_FileTransferHandlerUploadRefused() throws IOException, SQLException, Failure { 579 private void test_FileTransferHandlerUploadRefused() throws IOException, SQLException, Failure {
580 initTest("test_FileTransferHandlerUploadRefused");
388 prepare(); 581 prepare();
389 Path d = getTmpDir(currentTestName); 582 Path d = getTmpDir(currentTestName);
390 Path f = d.resolve("data.txt"); 583 Path f = d.resolve("data.txt");
391 OutputStream s = Files.newOutputStream(f, CREATE_NEW); 584 OutputStream s = Files.newOutputStream(f, CREATE_NEW);
392 PrintStream ps = new PrintStream(s, false, "UTF-8"); 585 PrintStream ps = new PrintStream(s, false, "UTF-8");
399 conn.setUploadHandler(new FileTransferHandler(d2, StandardCharsets.UTF_8)); 592 conn.setUploadHandler(new FileTransferHandler(d2, StandardCharsets.UTF_8));
400 String quoted = f.toAbsolutePath().toString().replaceAll("'", "''"); 593 String quoted = f.toAbsolutePath().toString().replaceAll("'", "''");
401 expectError("COPY INTO foo FROM R'"+ quoted + "' ON CLIENT", "not in upload directory"); 594 expectError("COPY INTO foo FROM R'"+ quoted + "' ON CLIENT", "not in upload directory");
402 // connection is still alive 595 // connection is still alive
403 assertQueryInt("SELECT SUM(i) FROM foo", 0); 596 assertQueryInt("SELECT SUM(i) FROM foo", 0);
404 } 597 exitTest();
405 598 }
406 public void test_FileTransferHandlerDownloadUtf8() throws SQLException, Failure, IOException { 599
600 private void test_FileTransferHandlerDownloadUtf8() throws SQLException, Failure, IOException {
601 initTest("test_FileTransferHandlerDownloadUtf8");
407 testFileTransferHandlerDownload(StandardCharsets.UTF_8, StandardCharsets.UTF_8); 602 testFileTransferHandlerDownload(StandardCharsets.UTF_8, StandardCharsets.UTF_8);
408 } 603 exitTest();
409 604 }
410 public void test_FileTransferHandlerDownloadLatin1() throws SQLException, Failure, IOException { 605
606 private void test_FileTransferHandlerDownloadLatin1() throws SQLException, Failure, IOException {
607 initTest("test_FileTransferHandlerDownloadLatin1");
411 Charset latin1 = Charset.forName("latin1"); 608 Charset latin1 = Charset.forName("latin1");
412 testFileTransferHandlerDownload(latin1, latin1); 609 testFileTransferHandlerDownload(latin1, latin1);
413 } 610 exitTest();
414 611 }
415 public void test_FileTransferHandlerDownloadNull() throws SQLException, Failure, IOException { 612
613 private void test_FileTransferHandlerDownloadNull() throws SQLException, Failure, IOException {
614 initTest("test_FileTransferHandlerDownloadNull");
416 testFileTransferHandlerDownload(null, Charset.defaultCharset()); 615 testFileTransferHandlerDownload(null, Charset.defaultCharset());
417 } 616 exitTest();
418 617 }
419 public void testFileTransferHandlerDownload(Charset handlerEncoding, Charset fileEncoding) throws SQLException, Failure, IOException { 618
619 private void testFileTransferHandlerDownload(Charset handlerEncoding, Charset fileEncoding) throws SQLException, Failure, IOException {
420 prepare(); 620 prepare();
421 update("INSERT INTO foo VALUES (42, 'forty-twø')"); 621 update("INSERT INTO foo VALUES (42, 'forty-twø')");
422 Path d = getTmpDir(currentTestName); 622 Path d = getTmpDir(currentTestName);
423 conn.setDownloadHandler(new FileTransferHandler(d, handlerEncoding)); 623 conn.setDownloadHandler(new FileTransferHandler(d, handlerEncoding));
424 update("COPY SELECT * FROM foo INTO 'data.txt' ON CLIENT"); 624 update("COPY SELECT * FROM foo INTO 'data.txt' ON CLIENT");
427 assertEq("line content", lines.get(0), "42|\"forty-twø\""); 627 assertEq("line content", lines.get(0), "42|\"forty-twø\"");
428 // connection is still alive 628 // connection is still alive
429 assertQueryInt("SELECT SUM(i) FROM foo", 42); 629 assertQueryInt("SELECT SUM(i) FROM foo", 42);
430 } 630 }
431 631
432 public void test_FileTransferHandlerDownloadRefused() throws SQLException, Failure, IOException { 632 private void test_FileTransferHandlerDownloadRefused() throws SQLException, Failure, IOException {
633 initTest("test_FileTransferHandlerDownloadRefused");
433 prepare(); 634 prepare();
434 BugFixLevel level = getLevel(); 635 BugFixLevel level = getLevel();
435 update("INSERT INTO foo VALUES (42, 'forty-two')"); 636 update("INSERT INTO foo VALUES (42, 'forty-two')");
436 Path d = getTmpDir(currentTestName); 637 Path d = getTmpDir(currentTestName);
437 Path d2 = getTmpDir(currentTestName + "2"); 638 Path d2 = getTmpDir(currentTestName + "2");
440 expectError("COPY SELECT * FROM foo INTO R'" + quoted + "' ON CLIENT", "not in download directory"); 641 expectError("COPY SELECT * FROM foo INTO R'" + quoted + "' ON CLIENT", "not in download directory");
441 if (level.compareTo(BugFixLevel.CanRefuseDownload) >= 0) { 642 if (level.compareTo(BugFixLevel.CanRefuseDownload) >= 0) {
442 // connection is still alive 643 // connection is still alive
443 assertQueryInt("SELECT SUM(i) FROM foo", 42); 644 assertQueryInt("SELECT SUM(i) FROM foo", 42);
444 } 645 }
445 } 646 exitTest();
446 647 }
648
649
650 /* utility methods */
651 private void fail(String message) throws Failure {
652 outBuffer.append("FAILURE: ").append(message).append("\n");
653 throw new Failure(message);
654 }
655
656 private void checked(String quantity, Object actual) {
657 outBuffer.append(" CHECKED: <").append(quantity).append("> is ").append(actual).append(" as expected").append("\n");
658 }
659
660 private void assertEq(String quantity, Object expected, Object actual) throws Failure {
661 if (expected.equals(actual)) {
662 checked(quantity, actual);
663 } else {
664 fail("Expected <" + quantity + "> to be " + expected + " got " + actual);
665 }
666 }
667
668 private boolean execute(String query) throws SQLException {
669 outBuffer.append("EXECUTE: ").append(query).append("\n");
670 final boolean result = stmt.execute(query);
671 if (result) {
672 outBuffer.append(" OK").append("\n");
673 } else {
674 outBuffer.append(" OK, updated ").append(stmt.getUpdateCount()).append(" rows").append("\n");
675 }
676 return result;
677 }
678
679 private void update(String query) throws SQLException, Failure {
680 execute(query);
681 }
682
683 private void expectError(String query, String expectedError) throws SQLException {
684 try {
685 execute(query);
686 } catch (SQLException e) {
687 if (e.getMessage().contains(expectedError)) {
688 outBuffer.append(" GOT EXPECTED EXCEPTION: ").append(e.getMessage()).append("\n");
689 } else {
690 throw e;
691 }
692 }
693 }
694
695 private void assertQueryInt(String query, int expected) throws SQLException, Failure {
696 if (execute(query) == false) {
697 fail("Query does not return a result set");
698 }
699 final ResultSet rs = stmt.getResultSet();
700 final ResultSetMetaData metaData = rs.getMetaData();
701 assertEq("column count", 1, metaData.getColumnCount());
702 if (!rs.next()) {
703 fail("Result set is empty");
704 }
705 final int result = rs.getInt(1);
706 if (rs.next()) {
707 fail("Result set has more than one row");
708 }
709 rs.close();
710 checked("row count", 1);
711 assertEq("query result", expected, result);
712 }
713
714 private void assertQueryString(String query, String expected) throws SQLException, Failure {
715 if (execute(query) == false) {
716 fail("Query does not return a result set");
717 }
718 final ResultSet rs = stmt.getResultSet();
719 final ResultSetMetaData metaData = rs.getMetaData();
720 assertEq("column count", 1, metaData.getColumnCount());
721 if (!rs.next()) {
722 fail("Result set is empty");
723 }
724 final String result = rs.getString(1);
725 if (rs.next()) {
726 fail("Result set has more than one row");
727 }
728 rs.close();
729 checked("row count", 1);
730 assertEq("query result", expected, result);
731 }
732
733 private String queryString(String query) throws SQLException, Failure {
734 if (execute(query) == false) {
735 fail("Query does not return a result set");
736 }
737 final ResultSet rs = stmt.getResultSet();
738 final ResultSetMetaData metaData = rs.getMetaData();
739 assertEq("column count", 1, metaData.getColumnCount());
740 if (!rs.next()) {
741 fail("Result set is empty");
742 }
743 final String result = rs.getString(1);
744 if (rs.next()) {
745 fail("Result set has more than one row");
746 }
747 rs.close();
748 checked("row count", 1);
749 return result;
750 }
751
752 private synchronized Path getTmpDir(String name) throws IOException {
753 if (tmpDir == null) {
754 tmpDir = Files.createTempDirectory("testMonetDB");
755 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
756 try {
757 Files.walkFileTree(tmpDir, new SimpleFileVisitor<Path>() {
758 @Override
759 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
760 Files.delete(file);
761 return FileVisitResult.CONTINUE;
762 }
763
764 @Override
765 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
766 Files.delete(dir);
767 return FileVisitResult.CONTINUE;
768 }
769 });
770 } catch (IOException e) {
771 // we do this on a best effort basis
772 }
773 }));
774 }
775 final Path p = tmpDir.resolve(name);
776 Files.createDirectory(p);
777 return p;
778 }
779
780 /**
781 * Implementation of an UploadHandler
782 */
447 static class MyUploadHandler implements UploadHandler { 783 static class MyUploadHandler implements UploadHandler {
448 private final long rows; 784 private final long rows;
449 private final long errorAt; 785 private final long errorAt;
450 private final String errorMessage; 786 private final String errorMessage;
451 private boolean encounteredWriteError = false; 787 private boolean encounteredWriteError = false;
503 public boolean isCancelled() { 839 public boolean isCancelled() {
504 return cancelled; 840 return cancelled;
505 } 841 }
506 } 842 }
507 843
844 /**
845 * Implementation of a DownloadHandler
846 */
508 static class MyDownloadHandler implements DownloadHandler { 847 static class MyDownloadHandler implements DownloadHandler {
509 private final int errorAtByte; 848 private final int errorAtByte;
510 private final String errorMessage; 849 private final String errorMessage;
511 private int attempts = 0; 850 private int attempts = 0;
512 private int bytesSeen = 0; 851 private int bytesSeen = 0;
570 if (startOfLine != bytesSeen) 909 if (startOfLine != bytesSeen)
571 lines++; 910 lines++;
572 return lines; 911 return lines;
573 } 912 }
574 } 913 }
914
915 static class Failure extends Exception {
916 static final long serialVersionUID = 3387516993124229948L;
917
918 public Failure(String message) {
919 super(message);
920 }
921
922 public Failure(String message, Throwable cause) {
923 super(message, cause);
924 }
925 }
575 } 926 }