1 /* 2 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.BufferedReader; 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.FileReader; 29 import java.io.IOException; 30 import java.io.PrintStream; 31 import java.lang.reflect.InvocationTargetException; 32 import java.nio.file.FileSystems; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.nio.file.StandardWatchEventKinds; 36 import java.nio.file.StandardCopyOption; 37 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 38 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 39 import java.nio.file.WatchEvent; 40 import java.nio.file.WatchKey; 41 import java.nio.file.WatchService; 42 import java.text.SimpleDateFormat; 43 import java.util.ArrayList; 44 import java.util.Collection; 45 import java.util.Collections; 46 import java.util.Comparator; 47 import java.util.Date; 48 import java.util.Iterator; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Optional; 52 import java.util.Set; 53 import java.util.concurrent.TimeUnit; 54 import java.util.concurrent.atomic.AtomicInteger; 55 import java.util.function.BiPredicate; 56 import java.util.function.Consumer; 57 import java.util.function.Predicate; 58 import java.util.function.Supplier; 59 import java.util.stream.Collectors; 60 import java.util.stream.Stream; 61 import jdk.jpackage.test.Functional.ExceptionBox; 62 import jdk.jpackage.test.Functional.ThrowingConsumer; 63 import jdk.jpackage.test.Functional.ThrowingRunnable; 64 import jdk.jpackage.test.Functional.ThrowingSupplier; 65 66 final public class TKit { 67 68 private static final String OS = System.getProperty("os.name").toLowerCase(); 69 70 public static final Path TEST_SRC_ROOT = Functional.identity(() -> { 71 Path root = Path.of(System.getProperty("test.src")); 72 73 for (int i = 0; i != 10; ++i) { 74 if (root.resolve("apps").toFile().isDirectory()) { 75 return root.normalize().toAbsolutePath(); 76 } 77 root = root.resolve(".."); 78 } 79 80 throw new RuntimeException("Failed to locate apps directory"); 81 }).get(); 82 83 public static final Path SRC_ROOT = Functional.identity(() -> { 84 return TEST_SRC_ROOT.resolve("../../../../src/jdk.incubator.jpackage").normalize().toAbsolutePath(); 85 }).get(); 86 87 public final static String ICON_SUFFIX = Functional.identity(() -> { 88 if (isOSX()) { 89 return ".icns"; 90 } 91 92 if (isLinux()) { 93 return ".png"; 94 } 95 96 if (isWindows()) { 97 return ".ico"; 98 } 99 100 throw throwUnknownPlatformError(); 101 }).get(); 102 103 public static void run(String args[], ThrowingRunnable testBody) { 104 if (currentTest != null) { 105 throw new IllegalStateException( 106 "Unexpeced nested or concurrent Test.run() call"); 107 } 108 109 TestInstance test = new TestInstance(testBody); 110 ThrowingRunnable.toRunnable(() -> runTests(List.of(test))).run(); 111 test.rethrowIfSkipped(); 112 if (!test.passed()) { 113 throw new RuntimeException(); 114 } 115 } 116 117 static void withExtraLogStream(ThrowingRunnable action) { 118 if (extraLogStream != null) { 119 ThrowingRunnable.toRunnable(action).run(); 120 } else { 121 try (PrintStream logStream = openLogStream()) { 122 extraLogStream = logStream; 123 ThrowingRunnable.toRunnable(action).run(); 124 } finally { 125 extraLogStream = null; 126 } 127 } 128 } 129 130 static void runTests(List<TestInstance> tests) { 131 if (currentTest != null) { 132 throw new IllegalStateException( 133 "Unexpeced nested or concurrent Test.run() call"); 134 } 135 136 withExtraLogStream(() -> { 137 tests.stream().forEach(test -> { 138 currentTest = test; 139 try { 140 ignoreExceptions(test).run(); 141 } finally { 142 currentTest = null; 143 if (extraLogStream != null) { 144 extraLogStream.flush(); 145 } 146 } 147 }); 148 }); 149 } 150 151 static Runnable ignoreExceptions(ThrowingRunnable action) { 152 return () -> { 153 try { 154 try { 155 action.run(); 156 } catch (Throwable ex) { 157 unbox(ex); 158 } 159 } catch (Throwable throwable) { 160 printStackTrace(throwable); 161 } 162 }; 163 } 164 165 static void unbox(Throwable throwable) throws Throwable { 166 try { 167 throw throwable; 168 } catch (ExceptionBox | InvocationTargetException ex) { 169 unbox(ex.getCause()); 170 } 171 } 172 173 public static Path workDir() { 174 return currentTest.workDir(); 175 } 176 177 static String getCurrentDefaultAppName() { 178 // Construct app name from swapping and joining test base name 179 // and test function name. 180 // Say the test name is `FooTest.testBasic`. Then app name would be `BasicFooTest`. 181 String appNamePrefix = currentTest.functionName(); 182 if (appNamePrefix != null && appNamePrefix.startsWith("test")) { 183 appNamePrefix = appNamePrefix.substring("test".length()); 184 } 185 return Stream.of(appNamePrefix, currentTest.baseName()).filter( 186 v -> v != null && !v.isEmpty()).collect(Collectors.joining()); 187 } 188 189 public static boolean isWindows() { 190 return (OS.contains("win")); 191 } 192 193 public static boolean isOSX() { 194 return (OS.contains("mac")); 195 } 196 197 public static boolean isLinux() { 198 return ((OS.contains("nix") || OS.contains("nux"))); 199 } 200 201 public static boolean isUbuntu() { 202 if (!isLinux()) { 203 return false; 204 } 205 File releaseFile = new File("/etc/os-release"); 206 if (releaseFile.exists()) { 207 try (BufferedReader lineReader = new BufferedReader(new FileReader(releaseFile))) { 208 String lineText = null; 209 while ((lineText = lineReader.readLine()) != null) { 210 if (lineText.indexOf("NAME=\"Ubuntu") != -1) { 211 lineReader.close(); 212 return true; 213 } 214 } 215 } catch (IOException e) { 216 e.printStackTrace(); 217 } 218 } 219 return false; 220 } 221 222 private static String addTimestamp(String msg) { 223 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); 224 Date time = new Date(System.currentTimeMillis()); 225 return String.format("[%s] %s", sdf.format(time), msg); 226 } 227 228 static void log(String v) { 229 v = addTimestamp(v); 230 System.out.println(v); 231 if (extraLogStream != null) { 232 extraLogStream.println(v); 233 } 234 } 235 236 static Path removeRootFromAbsolutePath(Path v) { 237 if (!v.isAbsolute()) { 238 throw new IllegalArgumentException(); 239 } 240 241 if (v.getNameCount() == 0) { 242 return Path.of(""); 243 } 244 return v.subpath(0, v.getNameCount()); 245 } 246 247 public static void createTextFile(Path propsFilename, Collection<String> lines) { 248 createTextFile(propsFilename, lines.stream()); 249 } 250 251 public static void createTextFile(Path propsFilename, Stream<String> lines) { 252 trace(String.format("Create [%s] text file...", 253 propsFilename.toAbsolutePath().normalize())); 254 ThrowingRunnable.toRunnable(() -> Files.write(propsFilename, 255 lines.peek(TKit::trace).collect(Collectors.toList()))).run(); 256 trace("Done"); 257 } 258 259 public static void createPropertiesFile(Path propsFilename, 260 Collection<Map.Entry<String, String>> props) { 261 trace(String.format("Create [%s] properties file...", 262 propsFilename.toAbsolutePath().normalize())); 263 ThrowingRunnable.toRunnable(() -> Files.write(propsFilename, 264 props.stream().map(e -> String.join("=", e.getKey(), 265 e.getValue())).peek(TKit::trace).collect(Collectors.toList()))).run(); 266 trace("Done"); 267 } 268 269 public static void createPropertiesFile(Path propsFilename, 270 Map.Entry<String, String>... props) { 271 createPropertiesFile(propsFilename, List.of(props)); 272 } 273 274 public static void createPropertiesFile(Path propsFilename, 275 Map<String, String> props) { 276 createPropertiesFile(propsFilename, props.entrySet()); 277 } 278 279 public static void trace(String v) { 280 if (TRACE) { 281 log("TRACE: " + v); 282 } 283 } 284 285 private static void traceAssert(String v) { 286 if (TRACE_ASSERTS) { 287 log("TRACE: " + v); 288 } 289 } 290 291 public static void error(String v) { 292 log("ERROR: " + v); 293 throw new AssertionError(v); 294 } 295 296 private final static String TEMP_FILE_PREFIX = null; 297 298 private static Path createUniqueFileName(String defaultName) { 299 final String[] nameComponents; 300 301 int separatorIdx = defaultName.lastIndexOf('.'); 302 final String baseName; 303 if (separatorIdx == -1) { 304 baseName = defaultName; 305 nameComponents = new String[]{baseName}; 306 } else { 307 baseName = defaultName.substring(0, separatorIdx); 308 nameComponents = new String[]{baseName, defaultName.substring( 309 separatorIdx + 1)}; 310 } 311 312 final Path basedir = workDir(); 313 int i = 0; 314 for (; i < 100; ++i) { 315 Path path = basedir.resolve(String.join(".", nameComponents)); 316 if (!path.toFile().exists()) { 317 return path; 318 } 319 nameComponents[0] = String.format("%s.%d", baseName, i); 320 } 321 throw new IllegalStateException(String.format( 322 "Failed to create unique file name from [%s] basename after %d attempts", 323 baseName, i)); 324 } 325 326 public static Path createTempDirectory(String role) throws IOException { 327 if (role == null) { 328 return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX); 329 } 330 return Files.createDirectory(createUniqueFileName(role)); 331 } 332 333 public static Path createTempFile(Path templateFile) throws 334 IOException { 335 return Files.createFile(createUniqueFileName( 336 templateFile.getFileName().toString())); 337 } 338 339 public static Path withTempFile(Path templateFile, 340 ThrowingConsumer<Path> action) { 341 final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile( 342 templateFile)).get(); 343 boolean keepIt = true; 344 try { 345 ThrowingConsumer.toConsumer(action).accept(tempFile); 346 keepIt = false; 347 return tempFile; 348 } finally { 349 if (tempFile != null && !keepIt) { 350 ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run(); 351 } 352 } 353 } 354 355 public static Path withTempDirectory(String role, 356 ThrowingConsumer<Path> action) { 357 final Path tempDir = ThrowingSupplier.toSupplier( 358 () -> createTempDirectory(role)).get(); 359 boolean keepIt = true; 360 try { 361 ThrowingConsumer.toConsumer(action).accept(tempDir); 362 keepIt = false; 363 return tempDir; 364 } finally { 365 if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { 366 deleteDirectoryRecursive(tempDir, ""); 367 } 368 } 369 } 370 371 private static class DirectoryCleaner implements Consumer<Path> { 372 DirectoryCleaner traceMessage(String v) { 373 msg = v; 374 return this; 375 } 376 377 DirectoryCleaner contentsOnly(boolean v) { 378 contentsOnly = v; 379 return this; 380 } 381 382 @Override 383 public void accept(Path root) { 384 if (msg == null) { 385 if (contentsOnly) { 386 msg = String.format("Cleaning [%s] directory recursively", 387 root); 388 } else { 389 msg = String.format("Deleting [%s] directory recursively", 390 root); 391 } 392 } 393 394 if (!msg.isEmpty()) { 395 trace(msg); 396 } 397 398 List<Throwable> errors = new ArrayList<>(); 399 try { 400 final List<Path> paths; 401 if (contentsOnly) { 402 try (var pathStream = Files.walk(root, 0)) { 403 paths = pathStream.collect(Collectors.toList()); 404 } 405 } else { 406 paths = List.of(root); 407 } 408 409 for (var path : paths) { 410 try (var pathStream = Files.walk(path)) { 411 pathStream 412 .sorted(Comparator.reverseOrder()) 413 .sequential() 414 .forEachOrdered(file -> { 415 try { 416 if (isWindows()) { 417 Files.setAttribute(file, "dos:readonly", false); 418 } 419 Files.delete(file); 420 } catch (IOException ex) { 421 errors.add(ex); 422 } 423 }); 424 } 425 } 426 427 } catch (IOException ex) { 428 errors.add(ex); 429 } 430 errors.forEach(error -> trace(error.toString())); 431 } 432 433 private String msg; 434 private boolean contentsOnly; 435 } 436 437 public static boolean deleteIfExists(Path path) throws IOException { 438 if (isWindows()) { 439 if (path.toFile().exists()) { 440 Files.setAttribute(path, "dos:readonly", false); 441 } 442 } 443 return Files.deleteIfExists(path); 444 } 445 446 /** 447 * Deletes contents of the given directory recursively. Shortcut for 448 * <code>deleteDirectoryContentsRecursive(path, null)</code> 449 * 450 * @param path path to directory to clean 451 */ 452 public static void deleteDirectoryContentsRecursive(Path path) { 453 deleteDirectoryContentsRecursive(path, null); 454 } 455 456 /** 457 * Deletes contents of the given directory recursively. If <code>path<code> is not a 458 * directory, request is silently ignored. 459 * 460 * @param path path to directory to clean 461 * @param msg log message. If null, the default log message is used. If 462 * empty string, no log message will be saved. 463 */ 464 public static void deleteDirectoryContentsRecursive(Path path, String msg) { 465 if (path.toFile().isDirectory()) { 466 new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept( 467 path); 468 } 469 } 470 471 /** 472 * Deletes the given directory recursively. Shortcut for 473 * <code>deleteDirectoryRecursive(path, null)</code> 474 * 475 * @param path path to directory to delete 476 */ 477 public static void deleteDirectoryRecursive(Path path) { 478 deleteDirectoryRecursive(path, null); 479 } 480 481 /** 482 * Deletes the given directory recursively. If <code>path<code> is not a 483 * directory, request is silently ignored. 484 * 485 * @param path path to directory to delete 486 * @param msg log message. If null, the default log message is used. If 487 * empty string, no log message will be saved. 488 */ 489 public static void deleteDirectoryRecursive(Path path, String msg) { 490 if (path.toFile().isDirectory()) { 491 new DirectoryCleaner().traceMessage(msg).accept(path); 492 } 493 } 494 495 public static RuntimeException throwUnknownPlatformError() { 496 if (isWindows() || isLinux() || isOSX()) { 497 throw new IllegalStateException( 498 "Platform is known. throwUnknownPlatformError() called by mistake"); 499 } 500 throw new IllegalStateException("Unknown platform"); 501 } 502 503 public static RuntimeException throwSkippedException(String reason) { 504 trace("Skip the test: " + reason); 505 RuntimeException ex = ThrowingSupplier.toSupplier( 506 () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor( 507 String.class).newInstance(reason)).get(); 508 509 currentTest.notifySkipped(ex); 510 throw ex; 511 } 512 513 public static Path createRelativePathCopy(final Path file) { 514 Path fileCopy = ThrowingSupplier.toSupplier(() -> { 515 Path localPath = createTempFile(file); 516 Files.copy(file, localPath, StandardCopyOption.REPLACE_EXISTING); 517 return localPath; 518 }).get().toAbsolutePath().normalize(); 519 520 final Path basePath = Path.of(".").toAbsolutePath().normalize(); 521 try { 522 return basePath.relativize(fileCopy); 523 } catch (IllegalArgumentException ex) { 524 // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root 525 trace(String.format("Failed to relativize [%s] at [%s]", fileCopy, 526 basePath)); 527 printStackTrace(ex); 528 } 529 return file; 530 } 531 532 static void waitForFileCreated(Path fileToWaitFor, 533 long timeoutSeconds) throws IOException { 534 535 trace(String.format("Wait for file [%s] to be available", 536 fileToWaitFor.toAbsolutePath())); 537 538 WatchService ws = FileSystems.getDefault().newWatchService(); 539 540 Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); 541 watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); 542 543 long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000; 544 for (;;) { 545 long timeout = waitUntil - System.currentTimeMillis(); 546 assertTrue(timeout > 0, String.format( 547 "Check timeout value %d is positive", timeout)); 548 549 WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout, 550 TimeUnit.MILLISECONDS)).get(); 551 if (key == null) { 552 if (fileToWaitFor.toFile().exists()) { 553 trace(String.format( 554 "File [%s] is available after poll timeout expired", 555 fileToWaitFor)); 556 return; 557 } 558 assertUnexpected(String.format("Timeout expired", timeout)); 559 } 560 561 for (WatchEvent<?> event : key.pollEvents()) { 562 if (event.kind() == StandardWatchEventKinds.OVERFLOW) { 563 continue; 564 } 565 Path contextPath = (Path) event.context(); 566 if (Files.isSameFile(watchDirectory.resolve(contextPath), 567 fileToWaitFor)) { 568 trace(String.format("File [%s] is available", fileToWaitFor)); 569 return; 570 } 571 } 572 573 if (!key.reset()) { 574 assertUnexpected("Watch key invalidated"); 575 } 576 } 577 } 578 579 static void printStackTrace(Throwable throwable) { 580 if (extraLogStream != null) { 581 throwable.printStackTrace(extraLogStream); 582 } 583 throwable.printStackTrace(); 584 } 585 586 private static String concatMessages(String msg, String msg2) { 587 if (msg2 != null && !msg2.isBlank()) { 588 return msg + ": " + msg2; 589 } 590 return msg; 591 } 592 593 public static void assertEquals(long expected, long actual, String msg) { 594 currentTest.notifyAssert(); 595 if (expected != actual) { 596 error(concatMessages(String.format( 597 "Expected [%d]. Actual [%d]", expected, actual), 598 msg)); 599 } 600 601 traceAssert(String.format("assertEquals(%d): %s", expected, msg)); 602 } 603 604 public static void assertNotEquals(long expected, long actual, String msg) { 605 currentTest.notifyAssert(); 606 if (expected == actual) { 607 error(concatMessages(String.format("Unexpected [%d] value", actual), 608 msg)); 609 } 610 611 traceAssert(String.format("assertNotEquals(%d, %d): %s", expected, 612 actual, msg)); 613 } 614 615 public static void assertEquals(String expected, String actual, String msg) { 616 currentTest.notifyAssert(); 617 if ((actual != null && !actual.equals(expected)) 618 || (expected != null && !expected.equals(actual))) { 619 error(concatMessages(String.format( 620 "Expected [%s]. Actual [%s]", expected, actual), 621 msg)); 622 } 623 624 traceAssert(String.format("assertEquals(%s): %s", expected, msg)); 625 } 626 627 public static void assertNotEquals(String expected, String actual, String msg) { 628 currentTest.notifyAssert(); 629 if ((actual != null && !actual.equals(expected)) 630 || (expected != null && !expected.equals(actual))) { 631 632 traceAssert(String.format("assertNotEquals(%s, %s): %s", expected, 633 actual, msg)); 634 return; 635 } 636 637 error(concatMessages(String.format("Unexpected [%s] value", actual), msg)); 638 } 639 640 public static void assertNull(Object value, String msg) { 641 currentTest.notifyAssert(); 642 if (value != null) { 643 error(concatMessages(String.format("Unexpected not null value [%s]", 644 value), msg)); 645 } 646 647 traceAssert(String.format("assertNull(): %s", msg)); 648 } 649 650 public static void assertNotNull(Object value, String msg) { 651 currentTest.notifyAssert(); 652 if (value == null) { 653 error(concatMessages("Unexpected null value", msg)); 654 } 655 656 traceAssert(String.format("assertNotNull(%s): %s", value, msg)); 657 } 658 659 public static void assertTrue(boolean actual, String msg) { 660 assertTrue(actual, msg, null); 661 } 662 663 public static void assertFalse(boolean actual, String msg) { 664 assertFalse(actual, msg, null); 665 } 666 667 public static void assertTrue(boolean actual, String msg, Runnable onFail) { 668 currentTest.notifyAssert(); 669 if (!actual) { 670 if (onFail != null) { 671 onFail.run(); 672 } 673 error(concatMessages("Failed", msg)); 674 } 675 676 traceAssert(String.format("assertTrue(): %s", msg)); 677 } 678 679 public static void assertFalse(boolean actual, String msg, Runnable onFail) { 680 currentTest.notifyAssert(); 681 if (actual) { 682 if (onFail != null) { 683 onFail.run(); 684 } 685 error(concatMessages("Failed", msg)); 686 } 687 688 traceAssert(String.format("assertFalse(): %s", msg)); 689 } 690 691 public static void assertPathExists(Path path, boolean exists) { 692 if (exists) { 693 assertTrue(path.toFile().exists(), String.format( 694 "Check [%s] path exists", path)); 695 } else { 696 assertFalse(path.toFile().exists(), String.format( 697 "Check [%s] path doesn't exist", path)); 698 } 699 } 700 701 public static void assertDirectoryExists(Path path) { 702 assertPathExists(path, true); 703 assertTrue(path.toFile().isDirectory(), String.format( 704 "Check [%s] is a directory", path)); 705 } 706 707 public static void assertFileExists(Path path) { 708 assertPathExists(path, true); 709 assertTrue(path.toFile().isFile(), String.format("Check [%s] is a file", 710 path)); 711 } 712 713 public static void assertExecutableFileExists(Path path) { 714 assertFileExists(path); 715 assertTrue(path.toFile().canExecute(), String.format( 716 "Check [%s] file is executable", path)); 717 } 718 719 public static void assertReadableFileExists(Path path) { 720 assertFileExists(path); 721 assertTrue(path.toFile().canRead(), String.format( 722 "Check [%s] file is readable", path)); 723 } 724 725 public static void assertUnexpected(String msg) { 726 currentTest.notifyAssert(); 727 error(concatMessages("Unexpected", msg)); 728 } 729 730 public static void assertStringListEquals(List<String> expected, 731 List<String> actual, String msg) { 732 currentTest.notifyAssert(); 733 734 traceAssert(String.format("assertStringListEquals(): %s", msg)); 735 736 String idxFieldFormat = Functional.identity(() -> { 737 int listSize = expected.size(); 738 int width = 0; 739 while (listSize != 0) { 740 listSize = listSize / 10; 741 width++; 742 } 743 return "%" + width + "d"; 744 }).get(); 745 746 AtomicInteger counter = new AtomicInteger(0); 747 Iterator<String> actualIt = actual.iterator(); 748 expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> { 749 int idx = counter.incrementAndGet(); 750 String actualStr = actualIt.next(); 751 752 if ((actualStr != null && !actualStr.equals(expectedStr)) 753 || (expectedStr != null && !expectedStr.equals(actualStr))) { 754 error(concatMessages(String.format( 755 "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]", 756 idx, expectedStr, actualStr), msg)); 757 } 758 759 traceAssert(String.format( 760 "assertStringListEquals(" + idxFieldFormat + ", %s)", idx, 761 expectedStr)); 762 }); 763 764 if (expected.size() < actual.size()) { 765 // Actual string list is longer than expected 766 error(concatMessages(String.format( 767 "Actual list is longer than expected by %d elements", 768 actual.size() - expected.size()), msg)); 769 } 770 771 if (actual.size() < expected.size()) { 772 // Actual string list is shorter than expected 773 error(concatMessages(String.format( 774 "Actual list is longer than expected by %d elements", 775 expected.size() - actual.size()), msg)); 776 } 777 } 778 779 public final static class TextStreamVerifier { 780 TextStreamVerifier(String value) { 781 this.value = value; 782 predicate(String::contains); 783 } 784 785 public TextStreamVerifier label(String v) { 786 label = v; 787 return this; 788 } 789 790 public TextStreamVerifier predicate(BiPredicate<String, String> v) { 791 predicate = v; 792 return this; 793 } 794 795 public TextStreamVerifier negate() { 796 negate = true; 797 return this; 798 } 799 800 public TextStreamVerifier orElseThrow(RuntimeException v) { 801 return orElseThrow(() -> v); 802 } 803 804 public TextStreamVerifier orElseThrow(Supplier<RuntimeException> v) { 805 createException = v; 806 return this; 807 } 808 809 public void apply(Stream<String> lines) { 810 String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse( 811 null); 812 String labelStr = Optional.ofNullable(label).orElse("output"); 813 if (negate) { 814 String msg = String.format( 815 "Check %s doesn't contain [%s] string", labelStr, value); 816 if (createException == null) { 817 assertNull(matchedStr, msg); 818 } else { 819 trace(msg); 820 if (matchedStr != null) { 821 throw createException.get(); 822 } 823 } 824 } else { 825 String msg = String.format("Check %s contains [%s] string", 826 labelStr, value); 827 if (createException == null) { 828 assertNotNull(matchedStr, msg); 829 } else { 830 trace(msg); 831 if (matchedStr == null) { 832 throw createException.get(); 833 } 834 } 835 } 836 } 837 838 private BiPredicate<String, String> predicate; 839 private String label; 840 private boolean negate; 841 private Supplier<RuntimeException> createException; 842 final private String value; 843 } 844 845 public static TextStreamVerifier assertTextStream(String what) { 846 return new TextStreamVerifier(what); 847 } 848 849 private static PrintStream openLogStream() { 850 if (LOG_FILE == null) { 851 return null; 852 } 853 854 return ThrowingSupplier.toSupplier(() -> new PrintStream( 855 new FileOutputStream(LOG_FILE.toFile(), true))).get(); 856 } 857 858 private static TestInstance currentTest; 859 private static PrintStream extraLogStream; 860 861 private static final boolean TRACE; 862 private static final boolean TRACE_ASSERTS; 863 864 static final boolean VERBOSE_JPACKAGE; 865 static final boolean VERBOSE_TEST_SETUP; 866 867 static String getConfigProperty(String propertyName) { 868 return System.getProperty(getConfigPropertyName(propertyName)); 869 } 870 871 static String getConfigPropertyName(String propertyName) { 872 return "jpackage.test." + propertyName; 873 } 874 875 static List<String> tokenizeConfigPropertyAsList(String propertyName) { 876 final String val = TKit.getConfigProperty(propertyName); 877 if (val == null) { 878 return null; 879 } 880 return Stream.of(val.toLowerCase().split(",")) 881 .map(String::strip) 882 .filter(Predicate.not(String::isEmpty)) 883 .collect(Collectors.toList()); 884 } 885 886 static Set<String> tokenizeConfigProperty(String propertyName) { 887 List<String> tokens = tokenizeConfigPropertyAsList(propertyName); 888 if (tokens == null) { 889 return null; 890 } 891 return tokens.stream().collect(Collectors.toSet()); 892 } 893 894 static final Path LOG_FILE = Functional.identity(() -> { 895 String val = getConfigProperty("logfile"); 896 if (val == null) { 897 return null; 898 } 899 return Path.of(val); 900 }).get(); 901 902 static { 903 Set<String> logOptions = tokenizeConfigProperty("suppress-logging"); 904 if (logOptions == null) { 905 TRACE = true; 906 TRACE_ASSERTS = true; 907 VERBOSE_JPACKAGE = true; 908 VERBOSE_TEST_SETUP = true; 909 } else if (logOptions.contains("all")) { 910 TRACE = false; 911 TRACE_ASSERTS = false; 912 VERBOSE_JPACKAGE = false; 913 VERBOSE_TEST_SETUP = false; 914 } else { 915 Predicate<Set<String>> isNonOf = options -> { 916 return Collections.disjoint(logOptions, options); 917 }; 918 919 TRACE = isNonOf.test(Set.of("trace", "t")); 920 TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a")); 921 VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp")); 922 VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i")); 923 } 924 } 925 }