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