20 * or visit www.oracle.com if you need additional information or have any |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 package jdk.jpackage.test; |
23 package jdk.jpackage.test; |
24 |
24 |
25 import java.io.File; |
|
26 import java.io.FileOutputStream; |
25 import java.io.FileOutputStream; |
27 import java.io.IOException; |
26 import java.io.IOException; |
28 import java.io.PrintStream; |
27 import java.io.PrintStream; |
29 import java.lang.reflect.InvocationTargetException; |
28 import java.lang.reflect.InvocationTargetException; |
30 import java.nio.file.FileSystems; |
29 import java.nio.file.*; |
31 import java.nio.file.Files; |
|
32 import java.nio.file.Path; |
|
33 import java.nio.file.StandardWatchEventKinds; |
|
34 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; |
30 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; |
35 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; |
31 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; |
36 import java.nio.file.WatchEvent; |
|
37 import java.nio.file.WatchKey; |
|
38 import java.nio.file.WatchService; |
|
39 import java.util.*; |
32 import java.util.*; |
40 import java.util.concurrent.TimeUnit; |
33 import java.util.concurrent.TimeUnit; |
41 import java.util.concurrent.atomic.AtomicInteger; |
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; |
42 import java.util.stream.Collectors; |
39 import java.util.stream.Collectors; |
43 import java.util.stream.Stream; |
40 import java.util.stream.Stream; |
44 import jdk.jpackage.test.Functional.ExceptionBox; |
41 import jdk.jpackage.test.Functional.ExceptionBox; |
45 import jdk.jpackage.test.Functional.ThrowingConsumer; |
42 import jdk.jpackage.test.Functional.ThrowingConsumer; |
46 import jdk.jpackage.test.Functional.ThrowingFunction; |
|
47 import jdk.jpackage.test.Functional.ThrowingRunnable; |
43 import jdk.jpackage.test.Functional.ThrowingRunnable; |
48 import jdk.jpackage.test.Functional.ThrowingSupplier; |
44 import jdk.jpackage.test.Functional.ThrowingSupplier; |
49 |
45 |
50 final public class TKit { |
46 final public class TKit { |
51 |
47 |
74 if (!test.passed()) { |
70 if (!test.passed()) { |
75 throw new RuntimeException(); |
71 throw new RuntimeException(); |
76 } |
72 } |
77 } |
73 } |
78 |
74 |
|
75 static void withExtraLogStream(ThrowingRunnable action) { |
|
76 if (extraLogStream != null) { |
|
77 ThrowingRunnable.toRunnable(action).run(); |
|
78 } else { |
|
79 try (PrintStream logStream = openLogStream()) { |
|
80 extraLogStream = logStream; |
|
81 ThrowingRunnable.toRunnable(action).run(); |
|
82 } finally { |
|
83 extraLogStream = null; |
|
84 } |
|
85 } |
|
86 } |
|
87 |
79 static void runTests(List<TestInstance> tests) { |
88 static void runTests(List<TestInstance> tests) { |
80 if (currentTest != null) { |
89 if (currentTest != null) { |
81 throw new IllegalStateException( |
90 throw new IllegalStateException( |
82 "Unexpeced nested or concurrent Test.run() call"); |
91 "Unexpeced nested or concurrent Test.run() call"); |
83 } |
92 } |
84 |
93 |
85 try (PrintStream logStream = openLogStream()) { |
94 withExtraLogStream(() -> { |
86 extraLogStream = logStream; |
|
87 tests.stream().forEach(test -> { |
95 tests.stream().forEach(test -> { |
88 currentTest = test; |
96 currentTest = test; |
89 try { |
97 try { |
90 ignoreExceptions(test).run(); |
98 ignoreExceptions(test).run(); |
91 } finally { |
99 } finally { |
172 extraLogStream.println(v); |
170 extraLogStream.println(v); |
173 } |
171 } |
174 } |
172 } |
175 |
173 |
176 public static void createTextFile(Path propsFilename, Collection<String> lines) { |
174 public static void createTextFile(Path propsFilename, Collection<String> lines) { |
|
175 createTextFile(propsFilename, lines.stream()); |
|
176 } |
|
177 |
|
178 public static void createTextFile(Path propsFilename, Stream<String> lines) { |
177 trace(String.format("Create [%s] text file...", |
179 trace(String.format("Create [%s] text file...", |
178 propsFilename.toAbsolutePath().normalize())); |
180 propsFilename.toAbsolutePath().normalize())); |
179 ThrowingRunnable.toRunnable(() -> Files.write(propsFilename, |
181 ThrowingRunnable.toRunnable(() -> Files.write(propsFilename, |
180 lines.stream().peek(TKit::trace).collect(Collectors.toList()))).run(); |
182 lines.peek(TKit::trace).collect(Collectors.toList()))).run(); |
181 trace("Done"); |
183 trace("Done"); |
182 } |
184 } |
183 |
185 |
184 public static void createPropertiesFile(Path propsFilename, |
186 public static void createPropertiesFile(Path propsFilename, |
185 Collection<Map.Entry<String, String>> props) { |
187 Collection<Map.Entry<String, String>> props) { |
261 return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix); |
263 return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix); |
262 } |
264 } |
263 return Files.createFile(createUniqueFileName(role)); |
265 return Files.createFile(createUniqueFileName(role)); |
264 } |
266 } |
265 |
267 |
266 public static void withTempFile(String role, String suffix, |
268 public static Path withTempFile(String role, String suffix, |
267 ThrowingConsumer<Path> action) { |
269 ThrowingConsumer<Path> action) { |
268 final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile( |
270 final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile( |
269 role, suffix)).get(); |
271 role, suffix)).get(); |
270 boolean keepIt = true; |
272 boolean keepIt = true; |
271 try { |
273 try { |
272 ThrowingConsumer.toConsumer(action).accept(tempFile); |
274 ThrowingConsumer.toConsumer(action).accept(tempFile); |
273 keepIt = false; |
275 keepIt = false; |
|
276 return tempFile; |
274 } finally { |
277 } finally { |
275 if (tempFile != null && !keepIt) { |
278 if (tempFile != null && !keepIt) { |
276 ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run(); |
279 ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run(); |
277 } |
280 } |
278 } |
281 } |
279 } |
282 } |
280 |
283 |
281 public static void withTempDirectory(String role, |
284 public static Path withTempDirectory(String role, |
282 ThrowingConsumer<Path> action) { |
285 ThrowingConsumer<Path> action) { |
283 final Path tempDir = ThrowingSupplier.toSupplier( |
286 final Path tempDir = ThrowingSupplier.toSupplier( |
284 () -> createTempDirectory(role)).get(); |
287 () -> createTempDirectory(role)).get(); |
285 boolean keepIt = true; |
288 boolean keepIt = true; |
286 try { |
289 try { |
287 ThrowingConsumer.toConsumer(action).accept(tempDir); |
290 ThrowingConsumer.toConsumer(action).accept(tempDir); |
288 keepIt = false; |
291 keepIt = false; |
|
292 return tempDir; |
289 } finally { |
293 } finally { |
290 if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { |
294 if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) { |
291 deleteDirectoryRecursive(tempDir, ""); |
295 deleteDirectoryRecursive(tempDir, ""); |
292 } |
296 } |
293 } |
297 } |
|
298 } |
|
299 |
|
300 private static class DirectoryCleaner implements Consumer<Path> { |
|
301 DirectoryCleaner traceMessage(String v) { |
|
302 msg = v; |
|
303 return this; |
|
304 } |
|
305 |
|
306 DirectoryCleaner contentsOnly(boolean v) { |
|
307 contentsOnly = v; |
|
308 return this; |
|
309 } |
|
310 |
|
311 @Override |
|
312 public void accept(Path root) { |
|
313 if (msg == null) { |
|
314 if (contentsOnly) { |
|
315 msg = String.format("Cleaning [%s] directory recursively", |
|
316 root); |
|
317 } else { |
|
318 msg = String.format("Deleting [%s] directory recursively", |
|
319 root); |
|
320 } |
|
321 } |
|
322 |
|
323 if (!msg.isEmpty()) { |
|
324 trace(msg); |
|
325 } |
|
326 |
|
327 List<Throwable> errors = new ArrayList<>(); |
|
328 try { |
|
329 final List<Path> paths; |
|
330 if (contentsOnly) { |
|
331 try (var pathStream = Files.walk(root, 0)) { |
|
332 paths = pathStream.collect(Collectors.toList()); |
|
333 } |
|
334 } else { |
|
335 paths = List.of(root); |
|
336 } |
|
337 |
|
338 for (var path : paths) { |
|
339 try (var pathStream = Files.walk(path)) { |
|
340 pathStream |
|
341 .sorted(Comparator.reverseOrder()) |
|
342 .sequential() |
|
343 .forEachOrdered(file -> { |
|
344 try { |
|
345 if (isWindows()) { |
|
346 Files.setAttribute(file, "dos:readonly", false); |
|
347 } |
|
348 Files.delete(file); |
|
349 } catch (IOException ex) { |
|
350 errors.add(ex); |
|
351 } |
|
352 }); |
|
353 } |
|
354 } |
|
355 |
|
356 } catch (IOException ex) { |
|
357 errors.add(ex); |
|
358 } |
|
359 errors.forEach(error -> trace(error.toString())); |
|
360 } |
|
361 |
|
362 private String msg; |
|
363 private boolean contentsOnly; |
294 } |
364 } |
295 |
365 |
296 /** |
366 /** |
297 * Deletes contents of the given directory recursively. Shortcut for |
367 * Deletes contents of the given directory recursively. Shortcut for |
298 * <code>deleteDirectoryContentsRecursive(path, null)</code> |
368 * <code>deleteDirectoryContentsRecursive(path, null)</code> |
311 * @param msg log message. If null, the default log message is used. If |
381 * @param msg log message. If null, the default log message is used. If |
312 * empty string, no log message will be saved. |
382 * empty string, no log message will be saved. |
313 */ |
383 */ |
314 public static void deleteDirectoryContentsRecursive(Path path, String msg) { |
384 public static void deleteDirectoryContentsRecursive(Path path, String msg) { |
315 if (path.toFile().isDirectory()) { |
385 if (path.toFile().isDirectory()) { |
316 if (msg == null) { |
386 new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept( |
317 msg = String.format("Cleaning [%s] directory recursively", path); |
387 path); |
318 } |
|
319 |
|
320 if (!msg.isEmpty()) { |
|
321 TKit.trace(msg); |
|
322 } |
|
323 |
|
324 // Walk all children of `path` in sorted order to hit files first |
|
325 // and directories last and delete each item. |
|
326 ThrowingRunnable.toRunnable(() -> Stream.of( |
|
327 path.toFile().listFiles()).map(File::toPath).map( |
|
328 ThrowingFunction.toFunction(Files::walk)).flatMap(x -> x).sorted( |
|
329 Comparator.reverseOrder()).map(Path::toFile).forEach( |
|
330 File::delete)).run(); |
|
331 } |
388 } |
332 } |
389 } |
333 |
390 |
334 /** |
391 /** |
335 * Deletes the given directory recursively. Shortcut for |
392 * Deletes the given directory recursively. Shortcut for |
349 * @param msg log message. If null, the default log message is used. If |
406 * @param msg log message. If null, the default log message is used. If |
350 * empty string, no log message will be saved. |
407 * empty string, no log message will be saved. |
351 */ |
408 */ |
352 public static void deleteDirectoryRecursive(Path path, String msg) { |
409 public static void deleteDirectoryRecursive(Path path, String msg) { |
353 if (path.toFile().isDirectory()) { |
410 if (path.toFile().isDirectory()) { |
354 if (msg == null) { |
411 new DirectoryCleaner().traceMessage(msg).accept(path); |
355 msg = String.format("Deleting [%s] directory recursively", path); |
|
356 } |
|
357 deleteDirectoryContentsRecursive(path, msg); |
|
358 ThrowingConsumer.toConsumer(Files::delete).accept(path); |
|
359 } |
412 } |
360 } |
413 } |
361 |
414 |
362 public static RuntimeException throwUnknownPlatformError() { |
415 public static RuntimeException throwUnknownPlatformError() { |
363 if (isWindows() || isLinux() || isOSX()) { |
416 if (isWindows() || isLinux() || isOSX()) { |
373 () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor( |
426 () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor( |
374 String.class).newInstance(reason)).get(); |
427 String.class).newInstance(reason)).get(); |
375 |
428 |
376 currentTest.notifySkipped(ex); |
429 currentTest.notifySkipped(ex); |
377 throw ex; |
430 throw ex; |
|
431 } |
|
432 |
|
433 public static Path createRelativePathCopy(final Path file) { |
|
434 Path fileCopy = workDir().resolve(file.getFileName()).toAbsolutePath().normalize(); |
|
435 |
|
436 ThrowingRunnable.toRunnable(() -> Files.copy(file, fileCopy, |
|
437 StandardCopyOption.REPLACE_EXISTING)).run(); |
|
438 |
|
439 final Path basePath = Path.of(".").toAbsolutePath().normalize(); |
|
440 try { |
|
441 return basePath.relativize(fileCopy); |
|
442 } catch (IllegalArgumentException ex) { |
|
443 // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root |
|
444 trace(String.format("Failed to relativize [%s] at [%s]", fileCopy, |
|
445 basePath)); |
|
446 printStackTrace(ex); |
|
447 } |
|
448 return file; |
378 } |
449 } |
379 |
450 |
380 static void waitForFileCreated(Path fileToWaitFor, |
451 static void waitForFileCreated(Path fileToWaitFor, |
381 long timeoutSeconds) throws IOException { |
452 long timeoutSeconds) throws IOException { |
382 |
453 |
600 "Actual list is longer than expected by %d elements", |
678 "Actual list is longer than expected by %d elements", |
601 expected.size() - actual.size()), msg)); |
679 expected.size() - actual.size()), msg)); |
602 } |
680 } |
603 } |
681 } |
604 |
682 |
|
683 public final static class TextStreamAsserter { |
|
684 TextStreamAsserter(String value) { |
|
685 this.value = value; |
|
686 predicate(String::contains); |
|
687 } |
|
688 |
|
689 public TextStreamAsserter label(String v) { |
|
690 label = v; |
|
691 return this; |
|
692 } |
|
693 |
|
694 public TextStreamAsserter predicate(BiPredicate<String, String> v) { |
|
695 predicate = v; |
|
696 return this; |
|
697 } |
|
698 |
|
699 public TextStreamAsserter negate() { |
|
700 negate = true; |
|
701 return this; |
|
702 } |
|
703 |
|
704 public TextStreamAsserter orElseThrow(RuntimeException v) { |
|
705 return orElseThrow(() -> v); |
|
706 } |
|
707 |
|
708 public TextStreamAsserter orElseThrow(Supplier<RuntimeException> v) { |
|
709 createException = v; |
|
710 return this; |
|
711 } |
|
712 |
|
713 public void apply(Stream<String> lines) { |
|
714 String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse( |
|
715 null); |
|
716 String labelStr = Optional.ofNullable(label).orElse("output"); |
|
717 if (negate) { |
|
718 String msg = String.format( |
|
719 "Check %s doesn't contain [%s] string", labelStr, value); |
|
720 if (createException == null) { |
|
721 assertNull(matchedStr, msg); |
|
722 } else { |
|
723 trace(msg); |
|
724 if (matchedStr != null) { |
|
725 throw createException.get(); |
|
726 } |
|
727 } |
|
728 } else { |
|
729 String msg = String.format("Check %s contains [%s] string", |
|
730 labelStr, value); |
|
731 if (createException == null) { |
|
732 assertNotNull(matchedStr, msg); |
|
733 } else { |
|
734 trace(msg); |
|
735 if (matchedStr == null) { |
|
736 throw createException.get(); |
|
737 } |
|
738 } |
|
739 } |
|
740 } |
|
741 |
|
742 private BiPredicate<String, String> predicate; |
|
743 private String label; |
|
744 private boolean negate; |
|
745 private Supplier<RuntimeException> createException; |
|
746 final private String value; |
|
747 } |
|
748 |
|
749 public static TextStreamAsserter assertTextStream(String what) { |
|
750 return new TextStreamAsserter(what); |
|
751 } |
|
752 |
605 private static PrintStream openLogStream() { |
753 private static PrintStream openLogStream() { |
606 if (LOG_FILE == null) { |
754 if (LOG_FILE == null) { |
607 return null; |
755 return null; |
608 } |
756 } |
609 |
757 |
624 return System.getProperty(getConfigPropertyName(propertyName)); |
772 return System.getProperty(getConfigPropertyName(propertyName)); |
625 } |
773 } |
626 |
774 |
627 static String getConfigPropertyName(String propertyName) { |
775 static String getConfigPropertyName(String propertyName) { |
628 return "jpackage.test." + propertyName; |
776 return "jpackage.test." + propertyName; |
|
777 } |
|
778 |
|
779 static Set<String> tokenizeConfigProperty(String propertyName) { |
|
780 final String val = TKit.getConfigProperty(propertyName); |
|
781 if (val == null) { |
|
782 return null; |
|
783 } |
|
784 return Stream.of(val.toLowerCase().split(",")).map(String::strip).filter( |
|
785 Predicate.not(String::isEmpty)).collect(Collectors.toSet()); |
629 } |
786 } |
630 |
787 |
631 static final Path LOG_FILE = Functional.identity(() -> { |
788 static final Path LOG_FILE = Functional.identity(() -> { |
632 String val = getConfigProperty("logfile"); |
789 String val = getConfigProperty("logfile"); |
633 if (val == null) { |
790 if (val == null) { |
635 } |
792 } |
636 return Path.of(val); |
793 return Path.of(val); |
637 }).get(); |
794 }).get(); |
638 |
795 |
639 static { |
796 static { |
640 String val = getConfigProperty("suppress-logging"); |
797 Set<String> logOptions = tokenizeConfigProperty("suppress-logging"); |
641 if (val == null) { |
798 if (logOptions == null) { |
642 TRACE = true; |
799 TRACE = true; |
643 TRACE_ASSERTS = true; |
800 TRACE_ASSERTS = true; |
644 VERBOSE_JPACKAGE = true; |
801 VERBOSE_JPACKAGE = true; |
645 VERBOSE_TEST_SETUP = true; |
802 VERBOSE_TEST_SETUP = true; |
646 } else if ("all".equals(val.toLowerCase())) { |
803 } else if (logOptions.contains("all")) { |
647 TRACE = false; |
804 TRACE = false; |
648 TRACE_ASSERTS = false; |
805 TRACE_ASSERTS = false; |
649 VERBOSE_JPACKAGE = false; |
806 VERBOSE_JPACKAGE = false; |
650 VERBOSE_TEST_SETUP = false; |
807 VERBOSE_TEST_SETUP = false; |
651 } else { |
808 } else { |
652 Set<String> logOptions = Set.of(val.toLowerCase().split(",")); |
809 Predicate<Set<String>> isNonOf = options -> { |
653 TRACE = !(logOptions.contains("trace") || logOptions.contains("t")); |
810 return Collections.disjoint(logOptions, options); |
654 TRACE_ASSERTS = !(logOptions.contains("assert") || logOptions.contains( |
811 }; |
655 "a")); |
812 |
656 VERBOSE_JPACKAGE = !(logOptions.contains("jpackage") || logOptions.contains( |
813 TRACE = isNonOf.test(Set.of("trace", "t")); |
657 "jp")); |
814 TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a")); |
658 VERBOSE_TEST_SETUP = !logOptions.contains("init"); |
815 VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp")); |
|
816 VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i")); |
659 } |
817 } |
660 } |
818 } |
661 |
819 |
662 private static final String OS = System.getProperty("os.name").toLowerCase(); |
820 private static final String OS = System.getProperty("os.name").toLowerCase(); |
663 } |
821 } |