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