test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
branchJDK-8200758-branch
changeset 58648 3bf53ffa9ae7
parent 58464 d82489644b15
child 58696 61c44899b4eb
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Wed Oct 16 09:57:23 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Wed Oct 16 10:32:08 2019 -0400
@@ -22,28 +22,24 @@
  */
 package jdk.jpackage.test;
 
-import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.*;
 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import jdk.jpackage.test.Functional.ExceptionBox;
 import jdk.jpackage.test.Functional.ThrowingConsumer;
-import jdk.jpackage.test.Functional.ThrowingFunction;
 import jdk.jpackage.test.Functional.ThrowingRunnable;
 import jdk.jpackage.test.Functional.ThrowingSupplier;
 
@@ -76,14 +72,26 @@
         }
     }
 
+    static void withExtraLogStream(ThrowingRunnable action) {
+        if (extraLogStream != null) {
+            ThrowingRunnable.toRunnable(action).run();
+        } else {
+            try (PrintStream logStream = openLogStream()) {
+                extraLogStream = logStream;
+                ThrowingRunnable.toRunnable(action).run();
+            } finally {
+                extraLogStream = null;
+            }
+        }
+    }
+
     static void runTests(List<TestInstance> tests) {
         if (currentTest != null) {
             throw new IllegalStateException(
                     "Unexpeced nested or concurrent Test.run() call");
         }
 
-        try (PrintStream logStream = openLogStream()) {
-            extraLogStream = logStream;
+        withExtraLogStream(() -> {
             tests.stream().forEach(test -> {
                 currentTest = test;
                 try {
@@ -95,9 +103,7 @@
                     }
                 }
             });
-        } finally {
-            extraLogStream = null;
-        }
+        });
     }
 
     static Runnable ignoreExceptions(ThrowingRunnable action) {
@@ -109,10 +115,7 @@
                     unbox(ex);
                 }
             } catch (Throwable throwable) {
-                if (extraLogStream != null) {
-                    throwable.printStackTrace(extraLogStream);
-                }
-                throwable.printStackTrace();
+                printStackTrace(throwable);
             }
         };
     }
@@ -126,12 +129,7 @@
     }
 
     public static Path workDir() {
-        Path result = Path.of(".");
-        String testFunctionName = currentTest.functionName();
-        if (testFunctionName != null) {
-            result = result.resolve(testFunctionName);
-        }
-        return result;
+        return currentTest.workDir();
     }
 
     static Path defaultInputDir() {
@@ -174,10 +172,14 @@
     }
 
     public static void createTextFile(Path propsFilename, Collection<String> lines) {
+        createTextFile(propsFilename, lines.stream());
+    }
+
+    public static void createTextFile(Path propsFilename, Stream<String> lines) {
         trace(String.format("Create [%s] text file...",
                 propsFilename.toAbsolutePath().normalize()));
         ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
-                lines.stream().peek(TKit::trace).collect(Collectors.toList()))).run();
+                lines.peek(TKit::trace).collect(Collectors.toList()))).run();
         trace("Done");
     }
 
@@ -263,7 +265,7 @@
         return Files.createFile(createUniqueFileName(role));
     }
 
-    public static void withTempFile(String role, String suffix,
+    public static Path withTempFile(String role, String suffix,
             ThrowingConsumer<Path> action) {
         final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile(
                 role, suffix)).get();
@@ -271,6 +273,7 @@
         try {
             ThrowingConsumer.toConsumer(action).accept(tempFile);
             keepIt = false;
+            return tempFile;
         } finally {
             if (tempFile != null && !keepIt) {
                 ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
@@ -278,7 +281,7 @@
         }
     }
 
-    public static void withTempDirectory(String role,
+    public static Path withTempDirectory(String role,
             ThrowingConsumer<Path> action) {
         final Path tempDir = ThrowingSupplier.toSupplier(
                 () -> createTempDirectory(role)).get();
@@ -286,6 +289,7 @@
         try {
             ThrowingConsumer.toConsumer(action).accept(tempDir);
             keepIt = false;
+            return tempDir;
         } finally {
             if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
                 deleteDirectoryRecursive(tempDir, "");
@@ -293,6 +297,72 @@
         }
     }
 
+    private static class DirectoryCleaner implements Consumer<Path> {
+        DirectoryCleaner traceMessage(String v) {
+            msg = v;
+            return this;
+        }
+
+        DirectoryCleaner contentsOnly(boolean v) {
+            contentsOnly = v;
+            return this;
+        }
+
+        @Override
+        public void accept(Path root) {
+            if (msg == null) {
+                if (contentsOnly) {
+                    msg = String.format("Cleaning [%s] directory recursively",
+                            root);
+                } else {
+                    msg = String.format("Deleting [%s] directory recursively",
+                            root);
+                }
+            }
+
+            if (!msg.isEmpty()) {
+                trace(msg);
+            }
+
+            List<Throwable> errors = new ArrayList<>();
+            try {
+                final List<Path> paths;
+                if (contentsOnly) {
+                    try (var pathStream = Files.walk(root, 0)) {
+                        paths = pathStream.collect(Collectors.toList());
+                    }
+                } else {
+                    paths = List.of(root);
+                }
+
+                for (var path : paths) {
+                    try (var pathStream = Files.walk(path)) {
+                        pathStream
+                        .sorted(Comparator.reverseOrder())
+                        .sequential()
+                        .forEachOrdered(file -> {
+                            try {
+                                if (isWindows()) {
+                                    Files.setAttribute(file, "dos:readonly", false);
+                                }
+                                Files.delete(file);
+                            } catch (IOException ex) {
+                                errors.add(ex);
+                            }
+                        });
+                    }
+                }
+
+            } catch (IOException ex) {
+                errors.add(ex);
+            }
+            errors.forEach(error -> trace(error.toString()));
+        }
+
+        private String msg;
+        private boolean contentsOnly;
+    }
+
     /**
      * Deletes contents of the given directory recursively. Shortcut for
      * <code>deleteDirectoryContentsRecursive(path, null)</code>
@@ -313,21 +383,8 @@
      */
     public static void deleteDirectoryContentsRecursive(Path path, String msg) {
         if (path.toFile().isDirectory()) {
-            if (msg == null) {
-                msg = String.format("Cleaning [%s] directory recursively", path);
-            }
-
-            if (!msg.isEmpty()) {
-                TKit.trace(msg);
-            }
-
-            // Walk all children of `path` in sorted order to hit files first
-            // and directories last and delete each item.
-            ThrowingRunnable.toRunnable(() -> Stream.of(
-                    path.toFile().listFiles()).map(File::toPath).map(
-                    ThrowingFunction.toFunction(Files::walk)).flatMap(x -> x).sorted(
-                    Comparator.reverseOrder()).map(Path::toFile).forEach(
-                    File::delete)).run();
+            new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept(
+                    path);
         }
     }
 
@@ -351,11 +408,7 @@
      */
     public static void deleteDirectoryRecursive(Path path, String msg) {
         if (path.toFile().isDirectory()) {
-            if (msg == null) {
-                msg = String.format("Deleting [%s] directory recursively", path);
-            }
-            deleteDirectoryContentsRecursive(path, msg);
-            ThrowingConsumer.toConsumer(Files::delete).accept(path);
+            new DirectoryCleaner().traceMessage(msg).accept(path);
         }
     }
 
@@ -377,6 +430,24 @@
         throw ex;
     }
 
+    public static Path createRelativePathCopy(final Path file) {
+        Path fileCopy = workDir().resolve(file.getFileName()).toAbsolutePath().normalize();
+
+        ThrowingRunnable.toRunnable(() -> Files.copy(file, fileCopy,
+                StandardCopyOption.REPLACE_EXISTING)).run();
+
+        final Path basePath = Path.of(".").toAbsolutePath().normalize();
+        try {
+            return basePath.relativize(fileCopy);
+        } catch (IllegalArgumentException ex) {
+            // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root
+            trace(String.format("Failed to relativize [%s] at [%s]", fileCopy,
+                    basePath));
+            printStackTrace(ex);
+        }
+        return file;
+    }
+
     static void waitForFileCreated(Path fileToWaitFor,
             long timeoutSeconds) throws IOException {
 
@@ -423,6 +494,13 @@
         }
     }
 
+    static void printStackTrace(Throwable throwable) {
+        if (extraLogStream != null) {
+            throwable.printStackTrace(extraLogStream);
+        }
+        throwable.printStackTrace();
+    }
+
     private static String concatMessages(String msg, String msg2) {
         if (msg2 != null && !msg2.isBlank()) {
             return msg + ": " + msg2;
@@ -602,6 +680,76 @@
         }
     }
 
+    public final static class TextStreamAsserter {
+        TextStreamAsserter(String value) {
+            this.value = value;
+            predicate(String::contains);
+        }
+
+        public TextStreamAsserter label(String v) {
+            label = v;
+            return this;
+        }
+
+        public TextStreamAsserter predicate(BiPredicate<String, String> v) {
+            predicate = v;
+            return this;
+        }
+
+        public TextStreamAsserter negate() {
+            negate = true;
+            return this;
+        }
+
+        public TextStreamAsserter orElseThrow(RuntimeException v) {
+            return orElseThrow(() -> v);
+        }
+
+        public TextStreamAsserter orElseThrow(Supplier<RuntimeException> v) {
+            createException = v;
+            return this;
+        }
+
+        public void apply(Stream<String> lines) {
+            String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse(
+                    null);
+            String labelStr = Optional.ofNullable(label).orElse("output");
+            if (negate) {
+                String msg = String.format(
+                        "Check %s doesn't contain [%s] string", labelStr, value);
+                if (createException == null) {
+                    assertNull(matchedStr, msg);
+                } else {
+                    trace(msg);
+                    if (matchedStr != null) {
+                        throw createException.get();
+                    }
+                }
+            } else {
+                String msg = String.format("Check %s contains [%s] string",
+                        labelStr, value);
+                if (createException == null) {
+                    assertNotNull(matchedStr, msg);
+                } else {
+                    trace(msg);
+                    if (matchedStr == null) {
+                        throw createException.get();
+                    }
+                }
+            }
+        }
+
+        private BiPredicate<String, String> predicate;
+        private String label;
+        private boolean negate;
+        private Supplier<RuntimeException> createException;
+        final private String value;
+    }
+
+    public static TextStreamAsserter assertTextStream(String what) {
+        return new TextStreamAsserter(what);
+    }
+
     private static PrintStream openLogStream() {
         if (LOG_FILE == null) {
             return null;
@@ -628,6 +776,15 @@
         return "jpackage.test." + propertyName;
     }
 
+    static Set<String> tokenizeConfigProperty(String propertyName) {
+        final String val = TKit.getConfigProperty(propertyName);
+        if (val == null) {
+            return null;
+        }
+        return Stream.of(val.toLowerCase().split(",")).map(String::strip).filter(
+                Predicate.not(String::isEmpty)).collect(Collectors.toSet());
+    }
+
     static final Path LOG_FILE = Functional.identity(() -> {
         String val = getConfigProperty("logfile");
         if (val == null) {
@@ -637,25 +794,26 @@
     }).get();
 
     static {
-        String val = getConfigProperty("suppress-logging");
-        if (val == null) {
+        Set<String> logOptions = tokenizeConfigProperty("suppress-logging");
+        if (logOptions == null) {
             TRACE = true;
             TRACE_ASSERTS = true;
             VERBOSE_JPACKAGE = true;
             VERBOSE_TEST_SETUP = true;
-        } else if ("all".equals(val.toLowerCase())) {
+        } else if (logOptions.contains("all")) {
             TRACE = false;
             TRACE_ASSERTS = false;
             VERBOSE_JPACKAGE = false;
             VERBOSE_TEST_SETUP = false;
         } else {
-            Set<String> logOptions = Set.of(val.toLowerCase().split(","));
-            TRACE = !(logOptions.contains("trace") || logOptions.contains("t"));
-            TRACE_ASSERTS = !(logOptions.contains("assert") || logOptions.contains(
-                    "a"));
-            VERBOSE_JPACKAGE = !(logOptions.contains("jpackage") || logOptions.contains(
-                    "jp"));
-            VERBOSE_TEST_SETUP = !logOptions.contains("init");
+            Predicate<Set<String>> isNonOf = options -> {
+                return Collections.disjoint(logOptions, options);
+            };
+
+            TRACE = isNonOf.test(Set.of("trace", "t"));
+            TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a"));
+            VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp"));
+            VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i"));
         }
     }