8231605: Improve test helpers JDK-8200758-branch
authorherrick
Mon, 30 Sep 2019 19:11:19 -0400
branchJDK-8200758-branch
changeset 58416 f09bf58c1f17
parent 58415 73f8e557549a
child 58417 67ffaf3a2b75
8231605: Improve test helpers Submitted-by: asemenyuk Reviewed-by: herrick
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java
src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java
src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Test.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java
test/jdk/tools/jpackage/junit/jdk/jpackage/internal/ApplicationLayoutTest.java
test/jdk/tools/jpackage/linux/AppCategoryTest.java
test/jdk/tools/jpackage/linux/LicenseTypeTest.java
test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java
test/jdk/tools/jpackage/linux/MaintainerTest.java
test/jdk/tools/jpackage/linux/PackageDepsTest.java
test/jdk/tools/jpackage/linux/ReleaseTest.java
test/jdk/tools/jpackage/linux/ShortcutHintTest.java
test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java
test/jdk/tools/jpackage/share/AppImagePackageTest.java
test/jdk/tools/jpackage/share/ArgumentsBase.java
test/jdk/tools/jpackage/share/ArgumentsModuleTest.java
test/jdk/tools/jpackage/share/ArgumentsTest.java
test/jdk/tools/jpackage/share/AtFilenameTest.java
test/jdk/tools/jpackage/share/FileAssociationsTest.java
test/jdk/tools/jpackage/share/IconTest.java
test/jdk/tools/jpackage/share/InstallDirTest.java
test/jdk/tools/jpackage/share/JLinkModuleTest.java
test/jdk/tools/jpackage/share/LicenseTest.java
test/jdk/tools/jpackage/share/ModularJarTest.java
test/jdk/tools/jpackage/share/ModuleTest.java
test/jdk/tools/jpackage/share/NoArgTest.java
test/jdk/tools/jpackage/share/NoNameTest.java
test/jdk/tools/jpackage/share/RuntimeBase.java
test/jdk/tools/jpackage/share/RuntimeModuleTest.java
test/jdk/tools/jpackage/share/RuntimePackageTest.java
test/jdk/tools/jpackage/share/RuntimeTest.java
test/jdk/tools/jpackage/share/SimplePackageTest.java
test/jdk/tools/jpackage/share/TempRootTest.java
test/jdk/tools/jpackage/share/Test.java
test/jdk/tools/jpackage/share/VersionTest.java
test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java
test/jdk/tools/jpackage/windows/WinConsoleTest.java
test/jdk/tools/jpackage/windows/WinDirChooserTest.java
test/jdk/tools/jpackage/windows/WinMenuGroupTest.java
test/jdk/tools/jpackage/windows/WinMenuTest.java
test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java
test/jdk/tools/jpackage/windows/WinShortcutTest.java
test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java	Mon Sep 30 19:11:19 2019 -0400
@@ -67,7 +67,7 @@
 
     private static ApplicationLayout createAppLayout(Map<String, Object> params,
             Path imageOutDir) {
-        return ApplicationLayout.linuxApp().resolveAt(
+        return ApplicationLayout.linuxAppImage().resolveAt(
                 imageOutDir.resolve(APP_NAME.fetchFrom(params)));
     }
 
@@ -105,13 +105,13 @@
     @Override
     protected String getCfgAppDir() {
         return Path.of("$APPDIR").resolve(
-                ApplicationLayout.linuxApp().appDirectory()).toString() + File.separator;
+                ApplicationLayout.linuxAppImage().appDirectory()).toString() + File.separator;
     }
 
     @Override
     protected String getCfgRuntimeDir() {
         return Path.of("$APPDIR").resolve(
-                ApplicationLayout.linuxApp().runtimeDirectory()).toString();
+                ApplicationLayout.linuxAppImage().runtimeDirectory()).toString();
     }
 
     @Override
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Mon Sep 30 19:11:19 2019 -0400
@@ -254,7 +254,7 @@
         if (StandardBundlerParam.isRuntimeInstaller(params)) {
             return ApplicationLayout.javaRuntime();
         }
-        return ApplicationLayout.linuxApp();
+        return ApplicationLayout.linuxAppImage();
     }
 
     private static void validateFileAssociations(
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java	Mon Sep 30 19:11:19 2019 -0400
@@ -31,7 +31,7 @@
 /**
  * Application directory layout.
  */
-final class ApplicationLayout implements PathGroup.Facade<ApplicationLayout> {
+public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayout> {
     enum PathRole {
         RUNTIME, APP, LAUNCHERS, DESKTOP, APP_MODS, DLLS
     }
@@ -57,46 +57,46 @@
     /**
      * Path to launchers directory.
      */
-    Path launchersDirectory() {
+    public Path launchersDirectory() {
         return pathGroup().getPath(PathRole.LAUNCHERS);
     }
 
     /**
      * Path to directory with dynamic libraries.
      */
-    Path dllDirectory() {
+    public Path dllDirectory() {
         return pathGroup().getPath(PathRole.DLLS);
     }
 
     /**
      * Path to application data directory.
      */
-    Path appDirectory() {
+    public Path appDirectory() {
         return pathGroup().getPath(PathRole.APP);
     }
 
     /**
      * Path to Java runtime directory.
      */
-    Path runtimeDirectory() {
+    public Path runtimeDirectory() {
         return pathGroup().getPath(PathRole.RUNTIME);
     }
 
     /**
      * Path to application mods directory.
      */
-    Path appModsDirectory() {
+    public Path appModsDirectory() {
         return pathGroup().getPath(PathRole.APP_MODS);
     }
 
     /**
      * Path to directory with application's desktop integration files.
      */
-    Path destktopIntegrationDirectory() {
+    public Path destktopIntegrationDirectory() {
         return pathGroup().getPath(PathRole.DESKTOP);
     }
 
-    static ApplicationLayout linuxApp() {
+    static ApplicationLayout linuxAppImage() {
         return new ApplicationLayout(Map.of(
                 PathRole.LAUNCHERS, Path.of("bin"),
                 PathRole.APP, Path.of("lib/app"),
@@ -107,21 +107,44 @@
         ));
     }
 
-    static ApplicationLayout windowsApp() {
+    static ApplicationLayout windowsAppImage() {
         return new ApplicationLayout(Map.of(
                 PathRole.LAUNCHERS, Path.of(""),
                 PathRole.APP, Path.of("app"),
                 PathRole.RUNTIME, Path.of("runtime"),
-                PathRole.DESKTOP, Path.of("")
+                PathRole.DESKTOP, Path.of(""),
+                PathRole.DLLS, Path.of(""),
+                PathRole.APP_MODS, Path.of("app/mods")
+        ));
+    }
+
+    static ApplicationLayout macAppImage() {
+        return new ApplicationLayout(Map.of(
+                PathRole.LAUNCHERS, Path.of("Contents/MacOS"),
+                PathRole.APP, Path.of("Contents/Java"),
+                PathRole.RUNTIME, Path.of("Contents/runtime"),
+                PathRole.DESKTOP, Path.of("Contents/Resources"),
+                PathRole.DLLS, Path.of("Contents/MacOS"),
+                PathRole.APP_MODS, Path.of("Contents/Java/mods")
         ));
     }
 
-    static ApplicationLayout platformApp() {
-        if (Platform.getPlatform() == Platform.WINDOWS) {
-            return windowsApp();
+
+
+    public static ApplicationLayout platformAppImage() {
+        if (Platform.isWindows()) {
+            return windowsAppImage();
         }
 
-        return linuxApp();
+        if (Platform.isLinux()) {
+            return linuxAppImage();
+        }
+
+        if (Platform.isMac()) {
+            return macAppImage();
+        }
+
+        throw new IllegalArgumentException("Unknown platform");
     }
 
     static ApplicationLayout javaRuntime() {
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java	Mon Sep 30 19:11:19 2019 -0400
@@ -102,4 +102,16 @@
     static int getMinorVersion() {
         return minorVersion;
     }
+
+    static boolean isWindows() {
+        return getPlatform() == WINDOWS;
+    }
+
+    static boolean isMac() {
+        return getPlatform() == MAC;
+    }
+
+    static boolean isLinux() {
+        return getPlatform() == LINUX;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+public class Annotations {
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface Test {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    @Repeatable(Parameters.class)
+    public @interface Parameter {
+
+        String value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface Parameters {
+
+        Parameter[] value();
+    }
+}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java	Mon Sep 30 19:11:19 2019 -0400
@@ -28,12 +28,11 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
-class CommandArguments<T> {
+public class CommandArguments<T> {
 
     CommandArguments() {
         args = new ArrayList<>();
     }
-    List<String> args;
 
     final public T addArgument(String v) {
         args.add(v);
@@ -58,6 +57,10 @@
                 Collectors.toList()));
     }
 
+    final public List<String> getAllArguments() {
+        return List.copyOf(args);
+    }
+
     protected void verifyMutable() {
         if (!isMutable()) {
             throw new UnsupportedOperationException(
@@ -68,4 +71,6 @@
     protected boolean isMutable() {
         return true;
     }
+
+    protected List<String> args;
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java	Mon Sep 30 19:11:19 2019 -0400
@@ -22,28 +22,37 @@
  */
 package jdk.jpackage.test;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.Files;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
-import java.util.regex.Matcher;
+import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.spi.ToolProvider;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
 
 public final class Executor extends CommandArguments<Executor> {
 
     public Executor() {
-        saveOutputType = SaveOutputType.NONE;
+        saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE));
     }
 
     public Executor setExecutable(String v) {
+        return setExecutable(Path.of(v));
+    }
+
+    public Executor setExecutable(Path v) {
         executable = v;
         if (executable != null) {
             toolProvider = null;
@@ -53,6 +62,7 @@
 
     public Executor setToolProvider(ToolProvider v) {
         toolProvider = v;
+        filterOutJcovOutput = true;
         if (toolProvider != null) {
             executable = null;
         }
@@ -65,21 +75,72 @@
     }
 
     public Executor setExecutable(JavaTool v) {
-        return setExecutable(v.getPath().getAbsolutePath());
+        filterOutJcovOutput = true;
+        return setExecutable(v.getPath());
     }
 
+    /**
+     * Configures this instance to save full output that command will produce.
+     * This function is mutual exclusive with
+     * saveFirstLineOfOutput() function.
+     *
+     * @return this
+     */
     public Executor saveOutput() {
-        saveOutputType = SaveOutputType.FULL;
+        saveOutputType.remove(SaveOutputType.FIRST_LINE);
+        saveOutputType.add(SaveOutputType.FULL);
         return this;
     }
 
-    public Executor saveFirstLineOfOutput() {
-        saveOutputType = SaveOutputType.FIRST_LINE;
+    /**
+     * Configures how to save output that command will produce. If
+     * <code>v</code> is <code>true</code>, the function call is equivalent to
+     * <code>saveOutput()</code> call. If <code>v</code> is <code>false</code>,
+     * the function will result in not preserving command output.
+     *
+     * @return this
+     */
+    public Executor saveOutput(boolean v) {
+        if (v) {
+            saveOutput();
+        } else {
+            saveOutputType.remove(SaveOutputType.FIRST_LINE);
+            saveOutputType.remove(SaveOutputType.FULL);
+        }
         return this;
     }
 
-    public Executor dumpOtput() {
-        saveOutputType = SaveOutputType.DUMP;
+    /**
+     * Configures this instance to save only the first line out output that
+     * command will produce. This function is mutual exclusive with
+     * saveOutput() function.
+     *
+     * @return this
+     */
+    public Executor saveFirstLineOfOutput() {
+        saveOutputType.add(SaveOutputType.FIRST_LINE);
+        saveOutputType.remove(SaveOutputType.FULL);
+        return this;
+    }
+
+    /**
+     * Configures this instance to dump all output that command will produce to
+     * System.out and System.err. Can be used together with saveOutput() and
+     * saveFirstLineOfOutput() to save command output and also copy it in the
+     * default output streams.
+     *
+     * @return this
+     */
+    public Executor dumpOutput() {
+        return dumpOutput(true);
+    }
+
+    public Executor dumpOutput(boolean v) {
+        if (v) {
+            saveOutputType.add(SaveOutputType.DUMP);
+        } else {
+            saveOutputType.remove(SaveOutputType.DUMP);
+        }
         return this;
     }
 
@@ -102,7 +163,7 @@
         }
 
         public Result assertExitCodeIs(int expectedExitCode) {
-            Test.assertEquals(expectedExitCode, exitCode, String.format(
+            TKit.assertEquals(expectedExitCode, exitCode, String.format(
                     "Check command %s exited with %d code",
                     getPrintableCommandLine(), expectedExitCode));
             return this;
@@ -117,21 +178,17 @@
     }
 
     public Result execute() {
-        if (toolProvider != null) {
-            return runToolProvider();
-        }
+        return ThrowingSupplier.toSupplier(() -> {
+            if (toolProvider != null) {
+                return runToolProvider();
+            }
 
-        try {
             if (executable != null) {
                 return runExecutable();
             }
-        } catch (RuntimeException e) {
-            throw e;
-        } catch (IOException | InterruptedException e) {
-            throw new RuntimeException(e);
-        }
 
-        throw new IllegalStateException("No command to execute");
+            throw new IllegalStateException("No command to execute");
+        }).get();
     }
 
     public String executeAndGetFirstLineOfOutput() {
@@ -142,69 +199,119 @@
         return saveOutput().execute().assertExitCodeIsZero().getOutput();
     }
 
+    private boolean withSavedOutput() {
+        return saveOutputType.contains(SaveOutputType.FULL) || saveOutputType.contains(
+                SaveOutputType.FIRST_LINE);
+    }
+
     private Result runExecutable() throws IOException, InterruptedException {
         List<String> command = new ArrayList<>();
-        command.add(executable);
+        command.add(executable.toString());
         command.addAll(args);
-        Path outputFile = null;
         ProcessBuilder builder = new ProcessBuilder(command);
         StringBuilder sb = new StringBuilder(getPrintableCommandLine());
-        if (saveOutputType != SaveOutputType.NONE) {
+        if (withSavedOutput()) {
             builder.redirectErrorStream(true);
-
-            if (saveOutputType == SaveOutputType.DUMP) {
-                builder.inheritIO();
-                sb.append("; redirect output to stdout");
-            } else {
-                outputFile = Test.createTempFile(".out");
-                builder.redirectOutput(outputFile.toFile());
-                sb.append(String.format("; redirect output to [%s]", outputFile));
-            }
+            sb.append("; save output");
+        } else if (saveOutputType.contains(SaveOutputType.DUMP)) {
+            builder.inheritIO();
+            sb.append("; inherit I/O");
         }
         if (directory != null) {
             builder.directory(directory.toFile());
             sb.append(String.format("; in directory [%s]", directory));
         }
 
-        try {
-            Test.trace("Execute " + sb.toString() + "...");
-            Process process = builder.start();
-            Result reply = new Result(process.waitFor());
-            Test.trace("Done. Exit code: " + reply.exitCode);
-            if (saveOutputType == SaveOutputType.FIRST_LINE) {
-                // If the command produced no ouput, save null in 'result.output' list.
-                reply.output = Arrays.asList(
-                        Files.readAllLines(outputFile).stream().findFirst().orElse(
-                                null));
-            } else if (saveOutputType == SaveOutputType.FULL) {
-                reply.output = Collections.unmodifiableList(Files.readAllLines(
-                        outputFile));
-            }
-            return reply;
-        } finally {
-            if (outputFile != null) {
-                Files.deleteIfExists(outputFile);
+        TKit.trace("Execute " + sb.toString() + "...");
+        Process process = builder.start();
+
+        List<String> outputLines = null;
+        if (withSavedOutput()) {
+            try (BufferedReader outReader = new BufferedReader(
+                    new InputStreamReader(process.getInputStream()))) {
+                if (saveOutputType.contains(SaveOutputType.DUMP) || saveOutputType.contains(
+                        SaveOutputType.FULL)) {
+                    outputLines = outReader.lines().collect(Collectors.toList());
+                } else {
+                    outputLines = Arrays.asList(
+                            outReader.lines().findFirst().orElse(null));
+                }
+            } finally {
+                if (saveOutputType.contains(SaveOutputType.DUMP) && outputLines != null) {
+                    outputLines.stream().forEach(System.out::println);
+                    if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+                        // Pick the first line of saved output if there is one
+                        for (String line: outputLines) {
+                            outputLines = List.of(line);
+                            break;
+                        }
+                    }
+                }
             }
         }
+
+        Result reply = new Result(process.waitFor());
+        TKit.trace("Done. Exit code: " + reply.exitCode);
+
+        if (outputLines != null) {
+            reply.output = Collections.unmodifiableList(outputLines);
+        }
+        return reply;
+    }
+
+    private Result runToolProvider(PrintStream out, PrintStream err) {
+        TKit.trace("Execute " + getPrintableCommandLine() + "...");
+        Result reply = new Result(toolProvider.run(out, err, args.toArray(
+                String[]::new)));
+        TKit.trace("Done. Exit code: " + reply.exitCode);
+        return reply;
     }
 
-    private Result runToolProvider() {
-        StringWriter writer = new StringWriter();
-        PrintWriter pw = new PrintWriter(writer);
+
+    private Result runToolProvider() throws IOException {
+        if (!withSavedOutput()) {
+            if (saveOutputType.contains(SaveOutputType.DUMP)) {
+                return runToolProvider(System.out, System.err);
+            }
 
-        Test.trace("Execute " + getPrintableCommandLine() + "...");
-        Result reply = new Result(toolProvider.run(pw, pw, args.toArray(
-                String[]::new)));
-        Test.trace("Done. Exit code: " + reply.exitCode);
+            PrintStream nullPrintStream = new PrintStream(new OutputStream() {
+                @Override
+                public void write(int b) {
+                    // Nop
+                }
+            });
+            return runToolProvider(nullPrintStream, nullPrintStream);
+        }
 
-        List lines = List.of(writer.toString().split("\\R", -1));
+        try (ByteArrayOutputStream buf = new ByteArrayOutputStream();
+                PrintStream ps = new PrintStream(buf)) {
+            Result reply = runToolProvider(ps, ps);
+            ps.flush();
+            try (BufferedReader bufReader = new BufferedReader(new StringReader(
+                    buf.toString()))) {
+                if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+                    String firstLine = filterJcovOutput(bufReader.lines()).findFirst().orElse(
+                            null);
+                    if (firstLine != null) {
+                        reply.output = List.of(firstLine);
+                    }
+                } else if (saveOutputType.contains(SaveOutputType.FULL)) {
+                    reply.output = filterJcovOutput(bufReader.lines()).collect(
+                            Collectors.toUnmodifiableList());
+                }
 
-        if (saveOutputType == SaveOutputType.FIRST_LINE) {
-            reply.output = Stream.of(lines).findFirst().get();
-        } else if (saveOutputType == SaveOutputType.FULL) {
-            reply.output = Collections.unmodifiableList(lines);
+                if (saveOutputType.contains(SaveOutputType.DUMP)) {
+                    Stream<String> lines;
+                    if (saveOutputType.contains(SaveOutputType.FULL)) {
+                        lines = reply.output.stream();
+                    } else {
+                        lines = bufReader.lines();
+                    }
+                    lines.forEach(System.out::println);
+                }
+            }
+            return reply;
         }
-        return reply;
     }
 
     public String getPrintableCommandLine() {
@@ -216,7 +323,7 @@
             format = "tool provider " + format;
             exec = toolProvider.name();
         } else {
-            exec = executable;
+            exec = executable.toString();
         }
 
         return String.format(format, printCommandLine(exec, args),
@@ -232,10 +339,18 @@
                         Collectors.joining(" "));
     }
 
+    private Stream<String> filterJcovOutput(Stream<String> lines) {
+        if (filterOutJcovOutput) {
+            return lines.filter(line -> !line.startsWith("Picked up"));
+        }
+        return lines;
+    }
+
     private ToolProvider toolProvider;
-    private String executable;
-    private SaveOutputType saveOutputType;
+    private Path executable;
+    private Set<SaveOutputType> saveOutputType;
     private Path directory;
+    private boolean filterOutJcovOutput;
 
     private static enum SaveOutputType {
         NONE, FULL, FIRST_LINE, DUMP
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java	Mon Sep 30 19:11:19 2019 -0400
@@ -34,14 +34,14 @@
     }
 
     public void createFile() {
-        Test.createPropertiesFile(file,
+        TKit.createPropertiesFile(file,
                 Map.entry("extension", suffixName),
                 Map.entry("mime-type", getMime()),
                 Map.entry("description", description));
     }
 
     final public FileAssociations setFilename(String v) {
-        file = Test.workDir().resolve(v + ".properties");
+        file = TKit.workDir().resolve(v + ".properties");
         return this;
     }
 
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java	Mon Sep 30 19:11:19 2019 -0400
@@ -23,9 +23,9 @@
 package jdk.jpackage.test;
 
 import java.lang.reflect.InvocationTargetException;
-import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 
@@ -110,6 +110,18 @@
         return v;
     }
 
+    public static <T, R> Function<T, R> identityFunction(Function<T, R> v) {
+        return v;
+    }
+
+    public static <T> Predicate<T> identity(Predicate<T> v) {
+        return v;
+    }
+
+    public static <T> Predicate<T> identityPredicate(Predicate<T> v) {
+        return v;
+    }
+
     public static class ExceptionBox extends RuntimeException {
         public ExceptionBox(Throwable throwable) {
             super(throwable);
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java	Mon Sep 30 19:11:19 2019 -0400
@@ -26,70 +26,227 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
-import java.util.Enumeration;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
 
 public class HelloApp {
 
-    private static final String MAIN_CLASS = "Hello";
-    private static final String JAR_FILENAME = "hello.jar";
-    private static final Consumer<JPackageCommand> CREATE_JAR_ACTION = (cmd) -> {
-        new JarBuilder()
-                .setOutputJar(cmd.inputDir().resolve(JAR_FILENAME).toFile())
-                .setMainClass(MAIN_CLASS)
-                .addSourceFile(Test.TEST_SRC_ROOT.resolve(
-                        Path.of("apps", "image", MAIN_CLASS + ".java")))
-                .create();
-    };
+    private HelloApp() {
+        setClassName(CLASS_NAME).setJarFileName("hello.jar");
+    }
+
+    /**
+     * Set fully qualified class name. E.g: foo.bar.Buzz.
+     */
+    private HelloApp setClassName(String v) {
+        qualifiedClassName = v;
+        return this;
+    }
+
+    private HelloApp setModuleName(String v) {
+        moduleName = v;
+        return this;
+    }
+
+    private HelloApp setJarFileName(String v) {
+        jarFileName = v;
+        return this;
+    }
+
+    private HelloApp setModuleVersion(String v) {
+        moduleVersion = v;
+        return this;
+    }
+
+    private JarBuilder prepareSources(Path srcDir) throws IOException {
+        final String className = qualifiedClassName.substring(
+                qualifiedClassName.lastIndexOf('.') + 1);
+        final String packageName = packageName();
+
+        final Path srcFile = srcDir.resolve(Path.of(String.join(
+                File.separator, qualifiedClassName.split("\\.")) + ".java"));
+        Files.createDirectories(srcFile.getParent());
+
+        JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile);
+        if (moduleName != null) {
+            Path moduleInfoFile = srcDir.resolve("module-info.java");
+            TKit.createTextFile(moduleInfoFile, List.of(
+                    String.format("module %s {", moduleName),
+                    String.format("    exports %s;", packageName),
+                    "}"
+            ));
+            jarBuilder.addSourceFile(moduleInfoFile);
+            if (moduleVersion != null) {
+                jarBuilder.setModuleVersion(moduleVersion);
+            }
+        }
+
+        // Add package directive and replace class name in java source file.
+        // Works with simple test Hello.java.
+        // Don't expect too much from these regexps!
+        Pattern classDeclaration = Pattern.compile("(^.*\\bclass\\s+)Hello(.*$)");
+        Pattern importDirective = Pattern.compile(
+                "(?<=import (?:static )?+)[^;]+");
+        AtomicBoolean classDeclared = new AtomicBoolean();
+        AtomicBoolean packageInserted = new AtomicBoolean(packageName == null);
+
+        var packageInserter = Functional.identityFunction((line) -> {
+            packageInserted.setPlain(true);
+            return String.format("package %s;%s%s", packageName,
+                    System.lineSeparator(), line);
+        });
 
-    static void addTo(JPackageCommand cmd) {
-        cmd.addAction(CREATE_JAR_ACTION);
-        cmd.addArguments("--main-jar", JAR_FILENAME);
-        cmd.addArguments("--main-class", MAIN_CLASS);
-        if (PackageType.WINDOWS.contains(cmd.packageType())) {
+        Files.write(srcFile, Files.readAllLines(HELLO_JAVA).stream().map(line -> {
+            if (classDeclared.getPlain()) {
+                return line;
+            }
+
+            Matcher m;
+            if (!packageInserted.getPlain() && importDirective.matcher(line).find()) {
+                line = packageInserter.apply(line);
+            } else if ((m = classDeclaration.matcher(line)).find()) {
+                classDeclared.setPlain(true);
+                line = m.group(1) + className + m.group(2);
+                if (!packageInserted.getPlain()) {
+                    line = packageInserter.apply(line);
+                }
+            }
+            return line;
+        }).collect(Collectors.toList()));
+
+        return jarBuilder;
+    }
+
+    private JarBuilder createJarBuilder() {
+        return new JarBuilder().setMainClass(qualifiedClassName);
+    }
+
+    private void addTo(JPackageCommand cmd) {
+        if (moduleName != null && packageName() == null) {
+            throw new IllegalArgumentException(String.format(
+                    "Module [%s] with default package", moduleName));
+        }
+
+        if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
+            // Use Hello.java as is.
+            cmd.addAction((self) -> {
+                File jarFile = self.inputDir().resolve(jarFileName).toFile();
+                createJarBuilder().setOutputJar(jarFile).addSourceFile(
+                        HELLO_JAVA).create();
+            });
+        } else {
+            cmd.addAction((self) -> {
+                final Path jarFile;
+                if (moduleName == null) {
+                    jarFile = self.inputDir().resolve(jarFileName);
+                } else {
+                    // `--module-path` option should be set by the moment
+                    // when this action is being executed.
+                    jarFile = Path.of(self.getArgumentValue("--module-path",
+                            () -> self.inputDir().toString()), jarFileName);
+                    Files.createDirectories(jarFile.getParent());
+                }
+
+                TKit.withTempDirectory("src",
+                        workDir -> prepareSources(workDir).setOutputJar(
+                                jarFile.toFile()).create());
+            });
+        }
+
+        if (moduleName == null) {
+            cmd.addArguments("--main-jar", jarFileName);
+            cmd.addArguments("--main-class", qualifiedClassName);
+        } else {
+            cmd.addArguments("--module-path", TKit.workDir().resolve(
+                    "input-modules"));
+            cmd.addArguments("--module", String.join("/", moduleName,
+                    qualifiedClassName));
+            // For modular app assume nothing will go in input directory and thus
+            // nobody will create input directory, so remove corresponding option
+            // from jpackage command line.
+            cmd.removeArgumentWithValue("--input");
+        }
+        if (TKit.isWindows()) {
             cmd.addArguments("--win-console");
         }
     }
 
-    static void verifyOutputFile(Path outputFile, String... args) {
+    private String packageName() {
+        int lastDotIdx = qualifiedClassName.lastIndexOf('.');
+        if (lastDotIdx == -1) {
+            return null;
+        }
+        return qualifiedClassName.substring(0, lastDotIdx);
+    }
+
+    /**
+     * Configures Java application to be used with the given jpackage command.
+     * Syntax of encoded Java application description is
+     * [jar_file:][module_name/]qualified_class_name[@module_version].
+     *
+     * E.g.: duke.jar:com.other/com.other.foo.bar.Buz@3.7 encodes modular
+     * application. Module name is `com.other`. Main class is
+     * `com.other.foo.bar.Buz`. Module version is `3.7`. Application will be
+     * compiled and packed in `duke.jar` jar file.
+     *
+     * @param cmd jpackage command to configure
+     * @param javaAppDesc encoded Java application description
+     */
+    static void addTo(JPackageCommand cmd, String javaAppDesc) {
+        HelloApp helloApp = new HelloApp();
+        if (javaAppDesc != null) {
+            String moduleNameAndOther = Functional.identity(() -> {
+                String[] components = javaAppDesc.split(":", 2);
+                if (components.length == 2) {
+                    helloApp.setJarFileName(components[0]);
+                }
+                return components[components.length - 1];
+            }).get();
+
+            String classNameAndOther = Functional.identity(() -> {
+                String[] components = moduleNameAndOther.split("/", 2);
+                if (components.length == 2) {
+                    helloApp.setModuleName(components[0]);
+                }
+                return components[components.length - 1];
+            }).get();
+
+            Functional.identity(() -> {
+                String[] components = classNameAndOther.split("@", 2);
+                helloApp.setClassName(components[0]);
+                if (components.length == 2) {
+                    helloApp.setModuleVersion(components[1]);
+                }
+            }).run();
+        }
+        helloApp.addTo(cmd);
+    }
+
+    static void verifyOutputFile(Path outputFile, List<String> args) {
         if (!outputFile.isAbsolute()) {
             verifyOutputFile(outputFile.toAbsolutePath().normalize(), args);
             return;
         }
 
-        Test.assertFileExists(outputFile, true);
+        TKit.assertFileExists(outputFile);
 
-        List<String> output = null;
-        try {
-            output = Files.readAllLines(outputFile);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-
-        final int expectedNumberOfLines = 2 + args.length;
-        Test.assertEquals(expectedNumberOfLines, output.size(), String.format(
-                "Check file [%s] contains %d text lines", outputFile,
-                expectedNumberOfLines));
+        List<String> contents = ThrowingSupplier.toSupplier(
+                () -> Files.readAllLines(outputFile)).get();
 
-        Test.assertEquals("jpackage test application", output.get(0),
-                String.format(
-                        "Check contents of the first text line in [%s] file",
-                        outputFile));
+        List<String> expected = new ArrayList<>(List.of(
+                "jpackage test application",
+                String.format("args.length: %d", args.size())
+        ));
+        expected.addAll(args);
 
-        Test.assertEquals(String.format("args.length: %d", args.length),
-                output.get(1), String.format(
-                "Check contents of the second text line in [%s] file",
-                outputFile));
-
-        Enumeration<String> argsEnum = Collections.enumeration(List.of(args));
-        AtomicInteger counter = new AtomicInteger(2);
-        output.stream().skip(2).sequential().forEach(line -> Test.assertEquals(
-                argsEnum.nextElement(), line, String.format(
-                "Check contents of %d text line in [%s] file",
-                counter.incrementAndGet(), outputFile)));
+        TKit.assertStringListEquals(expected, contents, String.format(
+                "Check contents of [%s] file", outputFile));
     }
 
     public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) {
@@ -115,20 +272,33 @@
 
     public static void executeAndVerifyOutput(Path helloAppLauncher,
             String... defaultLauncherArgs) {
-        File outputFile = Test.workDir().resolve(OUTPUT_FILENAME).toFile();
-        try {
-            Files.deleteIfExists(outputFile.toPath());
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
+        executeAndVerifyOutput(helloAppLauncher, List.of(defaultLauncherArgs));
+    }
+
+    public static void executeAndVerifyOutput(Path helloAppLauncher,
+            List<String> defaultLauncherArgs) {
+        // Output file will be created in the current directory.
+        Path outputFile = Path.of(OUTPUT_FILENAME);
+        ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
         new Executor()
-                .setDirectory(outputFile.getParentFile().toPath())
-                .setExecutable(helloAppLauncher.toString())
+                .setDirectory(outputFile.getParent())
+                .setExecutable(helloAppLauncher)
                 .execute()
                 .assertExitCodeIsZero();
 
-        verifyOutputFile(outputFile.toPath(), defaultLauncherArgs);
+        verifyOutputFile(outputFile, defaultLauncherArgs);
     }
 
     final static String OUTPUT_FILENAME = "appOutput.txt";
+
+    private String qualifiedClassName;
+    private String moduleName;
+    private String jarFileName;
+    private String moduleVersion;
+
+    private static final Path HELLO_JAVA = TKit.TEST_SRC_ROOT.resolve(
+            "apps/image/Hello.java");
+
+    private final static String CLASS_NAME = HELLO_JAVA.getFileName().toString().split(
+            "\\.", 2)[0];
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Mon Sep 30 19:11:19 2019 -0400
@@ -27,16 +27,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
+import java.util.*;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
 
 /**
  * jpackage command line with prerequisite actions. Prerequisite actions can be
@@ -87,8 +85,14 @@
         return setArgumentValue(argName, newValue.toString());
     }
 
+    public JPackageCommand removeArgumentWithValue(String argName) {
+        return setArgumentValue(argName, (String)null);
+    }
+
     public JPackageCommand removeArgument(String argName) {
-        return setArgumentValue(argName, (String)null);
+        args = args.stream().filter(arg -> !arg.equals(argName)).collect(
+                Collectors.toList());
+        return this;
     }
 
     public boolean hasArgument(String argName) {
@@ -149,17 +153,21 @@
     }
 
     public PackageType packageType() {
+        // Don't try to be in sync with jpackage defaults. Keep it simple:
+        // if no `--package-type` explicitely set on the command line, consider
+        // this is operator's fault.
         return getArgumentValue("--package-type",
-                () -> PackageType.DEFAULT,
-                (v) -> PACKAGE_TYPES.get(v));
+                () -> {
+                    throw new IllegalStateException("Package type not set");
+                }, PACKAGE_TYPES::get);
     }
 
     public Path outputDir() {
-        return getArgumentValue("--dest", () -> Test.defaultOutputDir(), Path::of);
+        return getArgumentValue("--dest", () -> Path.of("."), Path::of);
     }
 
     public Path inputDir() {
-        return getArgumentValue("--input", () -> Test.defaultInputDir(),Path::of);
+        return getArgumentValue("--input", () -> null, Path::of);
     }
 
     public String version() {
@@ -178,8 +186,8 @@
     }
 
     public JPackageCommand setDefaultInputOutput() {
-        addArguments("--input", Test.defaultInputDir());
-        addArguments("--dest", Test.defaultOutputDir());
+        addArguments("--input", TKit.defaultInputDir());
+        addArguments("--dest", TKit.defaultOutputDir());
         return this;
     }
 
@@ -187,10 +195,10 @@
         verifyMutable();
 
         try {
-            Path fakeRuntimeDir = Test.workDir().resolve("fake_runtime");
+            Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
             Files.createDirectories(fakeRuntimeDir);
 
-            if (Test.isWindows() || Test.isLinux()) {
+            if (TKit.isWindows() || TKit.isLinux()) {
                 // Needed to make WindowsAppBundler happy as it copies MSVC dlls
                 // from `bin` directory.
                 // Need to make the code in rpm spec happy as it assumes there is
@@ -218,17 +226,29 @@
         return this;
     }
 
-    JPackageCommand addAction(Consumer<JPackageCommand> action) {
+    JPackageCommand addAction(ThrowingConsumer<JPackageCommand> action) {
         verifyMutable();
-        actions.add(action);
+        actions.add(ThrowingConsumer.toConsumer(action));
         return this;
     }
 
     public static JPackageCommand helloAppImage() {
+        return helloAppImage(null);
+    }
+
+    /**
+     * Creates new JPackageCommand instance configured with the test Java app.
+     * For the explanation of `javaAppDesc` parameter, see documentation for
+     * HelloApp.addTo() method.
+     *
+     * @param javaAppDesc Java application description
+     * @return this
+     */
+    public static JPackageCommand helloAppImage(String javaAppDesc) {
         JPackageCommand cmd = new JPackageCommand();
         cmd.setDefaultInputOutput().setDefaultAppName();
         PackageType.IMAGE.applyTo(cmd);
-        HelloApp.addTo(cmd);
+        HelloApp.addTo(cmd, javaAppDesc);
         return cmd;
     }
 
@@ -239,8 +259,7 @@
     }
 
     JPackageCommand setDefaultAppName() {
-        addArguments("--name", Test.enclosingMainMethodClass().getSimpleName());
-        return this;
+        return addArguments("--name", TKit.getCurrentDefaultAppName());
     }
 
     public Path outputBundle() {
@@ -292,7 +311,7 @@
             return MacHelper.getInstallationDirectory(this);
         }
 
-        throw new IllegalArgumentException("Unexpected package type");
+        throw throwUnexpectedPackageTypeError();
     }
 
     /**
@@ -336,24 +355,24 @@
             return appInstallationDirectory().resolve(Path.of("Contents", "MacOS", name()));
         }
 
-        throw new IllegalArgumentException("Unexpected package type");
+        throw throwUnexpectedPackageTypeError();
     }
 
     /**
      * Returns path to application image directory.
      *
      * E.g. if --dest is set to `foo` and --name is set to `bar` the function
-     * will return `foo/bar` path.
+     * will return `foo/bar` path on Linux and Windows and `foo/bar.app` on macOS.
      *
      * @throws IllegalArgumentException is command is doing platform packaging
      */
     public Path appImage() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE != type) {
-            throw new IllegalArgumentException("Unexpected package type");
+        verifyIsOfType(PackageType.IMAGE);
+        String dirName = name();
+        if (TKit.isOSX()) {
+            dirName = dirName + ".app";
         }
-
-        return outputDir().resolve(name());
+        return outputDir().resolve(dirName);
     }
 
     /**
@@ -365,24 +384,21 @@
      * @throws IllegalArgumentException is command is doing platform packaging
      */
     public Path launcherPathInAppImage() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE != type) {
-            throw new IllegalArgumentException("Unexpected package type");
-        }
+        verifyIsOfType(PackageType.IMAGE);
 
-        if (Test.isLinux()) {
+        if (TKit.isLinux()) {
             return Path.of("bin", name());
         }
 
-        if (Test.isOSX()) {
+        if (TKit.isOSX()) {
             return Path.of("Contents", "MacOS", name());
         }
 
-        if (Test.isWindows()) {
+        if (TKit.isWindows()) {
             return Path.of(name() + ".exe");
         }
 
-        throw new IllegalArgumentException("Unexpected package type");
+        throw TKit.throwUnknownPlatformError();
     }
 
     /**
@@ -392,18 +408,18 @@
      * packaging
      */
     public Path appRuntimeDirectoryInAppImage() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE != type) {
-            throw new IllegalArgumentException("Unexpected package type");
-        }
-
-        return appRuntimePath(type);
+        verifyIsOfType(PackageType.IMAGE);
+        return appRuntimePath(packageType());
     }
 
     private static Path appRuntimePath(PackageType type) {
-        if (PackageType.LINUX.contains(type)) {
+        if (TKit.isLinux()) {
             return Path.of("lib/runtime");
         }
+        if (TKit.isOSX()) {
+            return Path.of("Contents/runtime");
+        }
+
         return Path.of("runtime");
     }
 
@@ -417,22 +433,22 @@
     }
 
     private static boolean isFakeRuntime(Path runtimeDir, String msg) {
-        final List<Path> criticalRuntimeFiles;
-        if (Test.isWindows()) {
-            criticalRuntimeFiles = List.of(Path.of("bin\\server\\jvm.dll"));
-        } else if (Test.isLinux()) {
-            criticalRuntimeFiles = List.of(Path.of("lib/server/libjvm.so"));
-        } else if (Test.isOSX()) {
-            criticalRuntimeFiles = List.of(Path.of("lib/server/libjvm.dylib"));
+        final Collection<Path> criticalRuntimeFiles;
+        if (TKit.isWindows()) {
+            criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES;
+        } else if (TKit.isLinux()) {
+            criticalRuntimeFiles = LinuxHelper.CRITICAL_RUNTIME_FILES;
+        } else if (TKit.isOSX()) {
+            criticalRuntimeFiles = MacHelper.CRITICAL_RUNTIME_FILES;
         } else {
-            throw new IllegalArgumentException("Unknwon platform");
+            throw TKit.throwUnknownPlatformError();
         }
 
         if (criticalRuntimeFiles.stream().filter(
                 v -> runtimeDir.resolve(v).toFile().exists()).findFirst().orElse(
                         null) == null) {
             // Fake runtime
-            Test.trace(String.format(
+            TKit.trace(String.format(
                     "%s because application runtime directory [%s] is incomplete",
                     msg, runtimeDir));
             return true;
@@ -440,18 +456,85 @@
         return false;
     }
 
-    public Executor.Result execute() {
+    public static void useToolProviderByDefault() {
+        defaultWithToolProvider = true;
+    }
+
+    public static void useExecutableByDefault() {
+        defaultWithToolProvider = false;
+    }
+
+    public JPackageCommand useToolProvider(boolean v) {
+        verifyMutable();
+        withToolProvider = v;
+        return this;
+    }
+
+    public JPackageCommand saveConsoleOutput(boolean v) {
+        verifyMutable();
+        saveConsoleOutput = v;
+        return this;
+    }
+
+    public JPackageCommand dumpOutput(boolean v) {
         verifyMutable();
-        if (actions != null) {
-            actions.stream().forEach(r -> r.accept(this));
+        suppressOutput = !v;
+        return this;
+    }
+
+    public boolean isWithToolProvider() {
+        return Optional.ofNullable(withToolProvider).orElse(
+                defaultWithToolProvider);
+    }
+
+    public void executePrerequisiteActions() {
+        verifyMutable();
+        if (!actionsExecuted) {
+            actionsExecuted = true;
+            if (actions != null) {
+                actions.stream().forEach(r -> r.accept(this));
+            }
+        }
+    }
+
+    public Executor.Result execute() {
+        executePrerequisiteActions();
+
+        if (packageType() == PackageType.IMAGE) {
+            TKit.deleteDirectoryContentsRecursive(outputDir());
         }
 
-        return new Executor()
-                .setExecutable(JavaTool.JPACKAGE)
-                .dumpOtput()
+        Executor exec = new Executor()
+                .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
                 .addArguments(new JPackageCommand().addArguments(
-                                args).adjustArgumentsBeforeExecution().args)
-                .execute();
+                                args).adjustArgumentsBeforeExecution().args);
+
+        if (isWithToolProvider()) {
+            exec.setToolProvider(JavaTool.JPACKAGE.asToolProvider());
+        } else {
+            exec.setExecutable(JavaTool.JPACKAGE);
+        }
+        return exec.execute();
+    }
+
+    public JPackageCommand executeAndAssertHelloAppImageCreated() {
+        executeAndAssertImageCreated();
+        HelloApp.executeLauncherAndVerifyOutput(this);
+        return this;
+    }
+
+    public JPackageCommand executeAndAssertImageCreated() {
+        execute().assertExitCodeIsZero();
+        return assertImageCreated();
+    }
+
+    public JPackageCommand assertImageCreated() {
+        verifyIsOfType(PackageType.IMAGE);
+        TKit.assertExecutableFileExists(appImage().resolve(
+                launcherPathInAppImage()));
+        TKit.assertDirectoryExists(appImage().resolve(
+                appRuntimeDirectoryInAppImage()));
+        return this;
     }
 
     private JPackageCommand adjustArgumentsBeforeExecution() {
@@ -459,13 +542,17 @@
             addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
         }
 
-        if (!hasArgument("--verbose") && Test.VERBOSE_JPACKAGE) {
+        if (!hasArgument("--verbose") && TKit.VERBOSE_JPACKAGE) {
             addArgument("--verbose");
         }
 
         return this;
     }
 
+    private static RuntimeException throwUnexpectedPackageTypeError() {
+        throw new IllegalArgumentException("Unexpected package type");
+    }
+
     String getPrintableCommandLine() {
         return new Executor()
                 .setExecutable(JavaTool.JPACKAGE)
@@ -479,43 +566,80 @@
 
     public void verifyIsOfType(PackageType ... types) {
         if (!Arrays.asList(types).contains(packageType())) {
-            throw new IllegalArgumentException("Unexpected package type");
+            throwUnexpectedPackageTypeError();
         }
     }
 
+    public static String escapeAndJoin(String... args) {
+        return escapeAndJoin(List.of(args));
+    }
+
+    public static String escapeAndJoin(List<String> args) {
+        Pattern whitespaceRegexp = Pattern.compile("\\s");
+
+        return args.stream().map(v -> {
+            String str = v;
+            // Escape quotes.
+            str = str.replace("\"", "\\\"");
+            // Escape backslashes.
+            str = str.replace("\\", "\\\\");
+            // If value contains whitespace characters, put the value in quotes
+            if (whitespaceRegexp.matcher(str).find()) {
+                str = "\"" + str + "\"";
+            }
+            return str;
+        }).collect(Collectors.joining(" "));
+    }
+
+    public static Path relativePathInRuntime(JavaTool tool) {
+        Path path = tool.relativePathInJavaHome();
+        if (TKit.isOSX()) {
+            path = Path.of("Contents/Home").resolve(path);
+        }
+        return path;
+    }
+
+    public static Stream<String> filterOutput(Stream<String> jpackageOutput) {
+        // Skip "WARNING: Using experimental tool jpackage" first line of output
+        return jpackageOutput.skip(1);
+    }
+
+    public static List<String> filterOutput(List<String> jpackageOutput) {
+        return filterOutput(jpackageOutput.stream()).collect(Collectors.toList());
+    }
+
     @Override
     protected boolean isMutable() {
         return !immutable;
     }
 
+    private Boolean withToolProvider;
+    private boolean saveConsoleOutput;
+    private boolean suppressOutput;
+    private boolean immutable;
+    private boolean actionsExecuted;
     private final List<Consumer<JPackageCommand>> actions;
-    private boolean immutable;
+    private static boolean defaultWithToolProvider;
 
-    private final static Map<String, PackageType> PACKAGE_TYPES
-            = new Supplier<Map<String, PackageType>>() {
-                @Override
-                public Map<String, PackageType> get() {
-                    Map<String, PackageType> reply = new HashMap<>();
-                    for (PackageType type : PackageType.values()) {
-                        reply.put(type.getName(), type);
-                    }
-                    return reply;
+    private final static Map<String, PackageType> PACKAGE_TYPES = Functional.identity(
+            () -> {
+                Map<String, PackageType> reply = new HashMap<>();
+                for (PackageType type : PackageType.values()) {
+                    reply.put(type.getName(), type);
                 }
-            }.get();
+                return reply;
+            }).get();
 
-    public final static Path DEFAULT_RUNTIME_IMAGE;
-
-    static {
+    public final static Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> {
         // Set the property to the path of run-time image to speed up
         // building app images and platform bundles by avoiding running jlink
         // The value of the property will be automativcally appended to
         // jpackage command line if the command line doesn't have
         // `--runtime-image` parameter set.
-        String val = Test.getConfigProperty("runtime-image");
+        String val = TKit.getConfigProperty("runtime-image");
         if (val != null) {
-            DEFAULT_RUNTIME_IMAGE = Path.of(val);
-        } else {
-            DEFAULT_RUNTIME_IMAGE = null;
+            return Path.of(val);
         }
-    }
+        return null;
+    }).get();
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java	Mon Sep 30 19:11:19 2019 -0400
@@ -29,7 +29,6 @@
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import java.util.ArrayList;
 import java.util.List;
-import jdk.jpackage.internal.IOUtils;
 
 
 /**
@@ -56,19 +55,13 @@
         return this;
     }
 
-    public void create() {
-        try {
-            createImpl();
-        } catch (RuntimeException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+    public JarBuilder setModuleVersion(String v) {
+        moduleVersion = v;
+        return this;
     }
 
-    private void createImpl() throws Exception {
-        Path workDir = Test.createTempDirectory();
-        try {
+    public void create() {
+        TKit.withTempDirectory("jar-workdir", workDir -> {
             Executor.Result javacReply = new Executor()
                     .setExecutable(JavaTool.JAVAC)
                     .addArguments("-d", workDir.toString())
@@ -78,6 +71,10 @@
             Executor jarExe = new Executor();
             jarExe.setExecutable(JavaTool.JAR).addArguments("-c", "-v", "-f",
                     tmpJar.toString());
+            if (moduleVersion != null) {
+                jarExe.addArguments(String.format("--module-version=%s",
+                        moduleVersion));
+            }
             if (mainClass != null) {
                 jarExe.addArguments("-e", mainClass);
             }
@@ -86,11 +83,10 @@
             javacReply.assertExitCodeIsZero();
             outputJar.getParentFile().mkdirs();
             Files.copy(tmpJar, outputJar.toPath(), REPLACE_EXISTING);
-        } finally {
-            IOUtils.deleteRecursive(workDir.toFile());
-        }
+        });
     }
     private List<Path> sourceFiles;
     private File outputJar;
     private String mainClass;
+    private String moduleVersion;
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java	Mon Sep 30 19:11:19 2019 -0400
@@ -25,33 +25,38 @@
 package jdk.jpackage.test;
 
 
-import java.io.File;
 import java.nio.file.Path;
 import java.util.spi.ToolProvider;
 
 public enum JavaTool {
-    JAVAC("javac"), JPACKAGE("jpackage"), JAR("jar");
+    JAVA("java"), JAVAC("javac"), JPACKAGE("jpackage"), JAR("jar"), JLINK("jlink");
 
     JavaTool(String name) {
         this.name = name;
-        path = Path.of(System.getProperty("java.home"), "bin", name).toFile();
-        if (Test.isWindows()) {
-            path = new File(path.toString() + ".exe");
-        }
-        if (!path.exists()) {
-            throw new RuntimeException("Unable to find tool ["
-                    + name + "] at path=[" + path.getAbsolutePath() + "]");
+        this.path = Path.of(System.getProperty("java.home")).resolve(
+                relativePathInJavaHome()).toAbsolutePath().normalize();
+        if (!path.toFile().exists()) {
+            throw new RuntimeException(String.format(
+                    "Unable to find tool [%s] at path=[%s]", name, path));
         }
     }
 
-    File getPath() {
+    Path getPath() {
         return path;
     }
 
-    ToolProvider asToolProvider() {
+    public ToolProvider asToolProvider() {
         return ToolProvider.findFirst(name).orElse(null);
     }
 
-    private File path;
+    Path relativePathInJavaHome() {
+        Path path = Path.of("bin", name);
+        if (TKit.isWindows()) {
+            path = path.getParent().resolve(path.getFileName().toString() + ".exe");
+        }
+        return path;
+    }
+
+    private Path path;
     private String name;
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Mon Sep 30 19:11:19 2019 -0400
@@ -31,6 +31,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public class LinuxHelper {
@@ -99,6 +100,48 @@
         return lines.map(Path::of);
     }
 
+    public static List<String> getPrerequisitePackages(JPackageCommand cmd) {
+        cmd.verifyIsOfType(PackageType.LINUX);
+        var packageType = cmd.packageType();
+        switch (packageType) {
+            case LINUX_DEB:
+                return Stream.of(getDebBundleProperty(cmd.outputBundle(),
+                        "Depends").split(",")).map(String::strip).collect(
+                        Collectors.toList());
+
+            case LINUX_RPM:
+                return new Executor().setExecutable("rpm")
+                .addArguments("-qp", "-R", cmd.outputBundle().toString())
+                .executeAndGetOutput();
+        }
+        // Unreachable
+        return null;
+    }
+
+    public static String getBundleProperty(JPackageCommand cmd,
+            String propertyName) {
+        return getBundleProperty(cmd,
+                Map.of(PackageType.LINUX_DEB, propertyName,
+                        PackageType.LINUX_RPM, propertyName));
+    }
+
+    public static String getBundleProperty(JPackageCommand cmd,
+            Map<PackageType, String> propertyName) {
+        cmd.verifyIsOfType(PackageType.LINUX);
+        var packageType = cmd.packageType();
+        switch (packageType) {
+            case LINUX_DEB:
+                return getDebBundleProperty(cmd.outputBundle(), propertyName.get(
+                        packageType));
+
+            case LINUX_RPM:
+                return getRpmBundleProperty(cmd.outputBundle(), propertyName.get(
+                        packageType));
+        }
+        // Unrechable
+        return null;
+    }
+
     static Path getLauncherPath(JPackageCommand cmd) {
         cmd.verifyIsOfType(PackageType.LINUX);
 
@@ -107,7 +150,7 @@
 
         return getPackageFiles(cmd).filter(path -> path.toString().endsWith(
                 launcherRelativePath)).findFirst().or(() -> {
-            Test.assertUnexpected(String.format(
+            TKit.assertUnexpected(String.format(
                     "Failed to find %s in %s package", launcherName,
                     getPackageName(cmd)));
             return null;
@@ -160,32 +203,28 @@
         };
 
         test.addBundleVerifier(cmd -> {
-            Test.withTempDirectory(tempDir -> {
-                try {
-                    // Extract control Debian package files into temporary directory
-                    new Executor()
-                    .setExecutable("dpkg")
-                    .addArguments(
-                            "-e",
-                            cmd.outputBundle().toString(),
-                            tempDir.toString()
-                    ).execute().assertExitCodeIsZero();
-
-                    Path controlFile = Path.of("postinst");
+            TKit.withTempDirectory("dpkg-control-files", tempDir -> {
+                // Extract control Debian package files into temporary directory
+                new Executor()
+                .setExecutable("dpkg")
+                .addArguments(
+                        "-e",
+                        cmd.outputBundle().toString(),
+                        tempDir.toString()
+                ).execute().assertExitCodeIsZero();
 
-                    // Lookup for xdg commands in postinstall script
-                    String lineWithXsdCommand = verifier.apply(
-                            Files.readAllLines(tempDir.resolve(controlFile)));
-                    String assertMsg = String.format(
-                            "Check if %s@%s control file uses xdg commands",
-                            cmd.outputBundle(), controlFile);
-                    if (integrated) {
-                        Test.assertNotNull(lineWithXsdCommand, assertMsg);
-                    } else {
-                        Test.assertNull(lineWithXsdCommand, assertMsg);
-                    }
-                } catch (IOException ex) {
-                    throw new RuntimeException(ex);
+                Path controlFile = Path.of("postinst");
+
+                // Lookup for xdg commands in postinstall script
+                String lineWithXsdCommand = verifier.apply(
+                        Files.readAllLines(tempDir.resolve(controlFile)));
+                String assertMsg = String.format(
+                        "Check if %s@%s control file uses xdg commands",
+                        cmd.outputBundle(), controlFile);
+                if (integrated) {
+                    TKit.assertNotNull(lineWithXsdCommand, assertMsg);
+                } else {
+                    TKit.assertNull(lineWithXsdCommand, assertMsg);
                 }
             });
         });
@@ -220,12 +259,10 @@
 
     static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) {
         test.addInstallVerifier(cmd -> {
-            Test.withTempFile(fa.getSuffix(), testFile -> {
-                initFileAssociationsTestFile(testFile);
-
+            PackageTest.withTestFileAssociationsFile(fa, testFile -> {
                 String mimeType = queryFileMimeType(testFile);
 
-                Test.assertEquals(fa.getMime(), mimeType, String.format(
+                TKit.assertEquals(fa.getMime(), mimeType, String.format(
                         "Check mime type of [%s] file", testFile));
 
                 String desktopFileName = queryMimeTypeDefaultHandler(mimeType);
@@ -233,21 +270,17 @@
                 Path desktopFile = getSystemDesktopFilesFolder().resolve(
                         desktopFileName);
 
-                Test.assertFileExists(desktopFile, true);
+                TKit.assertFileExists(desktopFile);
 
-                Test.trace(String.format("Reading [%s] file...", desktopFile));
-                String mimeHandler = null;
-                try {
-                    mimeHandler = Files.readAllLines(desktopFile).stream().peek(
-                            v -> Test.trace(v)).filter(
-                                    v -> v.startsWith("Exec=")).map(
-                                    v -> v.split("=", 2)[1]).findFirst().orElseThrow();
-                } catch (IOException ex) {
-                    throw new RuntimeException(ex);
-                }
-                Test.trace(String.format("Done"));
+                TKit.trace(String.format("Reading [%s] file...", desktopFile));
+                String mimeHandler = Files.readAllLines(desktopFile).stream().peek(
+                        v -> TKit.trace(v)).filter(
+                                v -> v.startsWith("Exec=")).map(
+                                v -> v.split("=", 2)[1]).findFirst().orElseThrow();
 
-                Test.assertEquals(cmd.launcherInstallationPath().toString(),
+                TKit.trace(String.format("Done"));
+
+                TKit.assertEquals(cmd.launcherInstallationPath().toString(),
                         mimeHandler, String.format(
                                 "Check mime type handler is the main application launcher"));
 
@@ -255,17 +288,15 @@
         });
 
         test.addUninstallVerifier(cmd -> {
-            Test.withTempFile(fa.getSuffix(), testFile -> {
-                initFileAssociationsTestFile(testFile);
-
+            PackageTest.withTestFileAssociationsFile(fa, testFile -> {
                 String mimeType = queryFileMimeType(testFile);
 
-                Test.assertNotEquals(fa.getMime(), mimeType, String.format(
+                TKit.assertNotEquals(fa.getMime(), mimeType, String.format(
                         "Check mime type of [%s] file", testFile));
 
                 String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime());
 
-                Test.assertNull(desktopFileName, String.format(
+                TKit.assertNull(desktopFileName, String.format(
                         "Check there is no default handler for [%s] mime type",
                         fa.getMime()));
             });
@@ -311,5 +342,8 @@
         return arch;
     }
 
+    static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+            "lib/server/libjvm.so"));
+
     static private Map<PackageType, String> archs;
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java	Mon Sep 30 19:11:19 2019 -0400
@@ -23,6 +23,7 @@
 package jdk.jpackage.test;
 
 import java.nio.file.Path;
+import java.util.Set;
 
 public class MacHelper {
 
@@ -41,6 +42,10 @@
 
     private static String getPackageName(JPackageCommand cmd) {
         return cmd.getArgumentValue("--mac-package-name",
-                () -> cmd.name().toLowerCase());
+                () -> cmd.name());
     }
+
+    static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+            "Contents/Home/lib/server/libjvm.dylib"));
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
+
+
+public final class Main {
+    public static void main(String args[]) throws Throwable {
+        boolean listTests = false;
+        List<TestInstance> tests = new ArrayList<>();
+        try (TestBuilder testBuilder = new TestBuilder(tests::add)) {
+            for (var arg : args) {
+                if (TKit.VERBOSE_TEST_SETUP) {
+                    TKit.log(String.format("Parsing [%s]...", arg));
+                }
+
+                if ((CMDLINE_ARG_PREFIX + "list").equals(arg)) {
+                    listTests = true;
+                    continue;
+                }
+
+                boolean success = false;
+                try {
+                    testBuilder.processCmdLineArg(arg);
+                    success = true;
+                } catch (Throwable throwable) {
+                    TKit.unbox(throwable);
+                } finally {
+                    if (!success) {
+                        TKit.log(
+                                String.format("Error processing parameter=[%s]",
+                                        arg));
+                    }
+                }
+            }
+        }
+
+        // Order tests by their full names to have stable test sequence.
+        tests = tests.stream().sorted((a, b) -> a.fullName().compareTo(
+                b.fullName())).collect(Collectors.toList());
+
+        if (listTests) {
+            // Just list the tests
+            tests.stream().forEach(test -> System.out.println(test.fullName()));
+            return;
+        }
+
+        TKit.runTests(tests);
+
+        final long failedCount = tests.stream().filter(Functional.identity(
+                TestInstance::passed).negate()).count();
+        final long passedCount = tests.size() - failedCount;
+
+        TKit.log(String.format("[==========] %d tests ran", tests.size()));
+        TKit.log(String.format("[  PASSED  ] %d %s", passedCount,
+                passedCount == 1 ? "test" : "tests"));
+        if (failedCount != 0) {
+            TKit.log(String.format("[  FAILED  ] %d %s, listed below",
+                    failedCount,
+                    failedCount == 1 ? "test" : "tests"));
+            tests.stream().filter(
+                    Functional.identity(TestInstance::passed).negate()).forEach(
+                    test -> TKit.log(String.format("[  FAILED  ] %s",
+                            test.fullName())));
+
+            final String errorMessage = String.format("%d FAILED %s",
+                    failedCount, failedCount == 1 ? "TEST" : "TESTS");
+            TKit.log(errorMessage);
+            throw new RuntimeException(errorMessage);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+import jdk.jpackage.test.TestInstance.TestDesc;
+
+class MethodCall implements ThrowingConsumer {
+
+    MethodCall(Method method, Object... args) {
+        this.method = method;
+        this.args = args;
+    }
+
+    TestDesc createDescription() {
+        return TestDesc.create(method, args);
+    }
+
+    Constructor getRequiredConstructor() throws NoSuchMethodException {
+        return MethodCall.getRequiredConstructor(method);
+    }
+
+    static Constructor getRequiredConstructor(Method method) throws
+            NoSuchMethodException {
+        if ((method.getModifiers() & Modifier.STATIC) == 0) {
+            return method.getDeclaringClass().getConstructor();
+        }
+        return null;
+    }
+
+    @Override
+    public void accept(Object thiz) throws Throwable {
+        method.invoke(thiz, args);
+    }
+
+    private final Object[] args;
+    private final Method method;
+}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -26,22 +26,14 @@
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import static jdk.jpackage.test.PackageType.LINUX_DEB;
-import static jdk.jpackage.test.PackageType.LINUX_RPM;
 import jdk.jpackage.test.Functional.ThrowingConsumer;
+import static jdk.jpackage.test.PackageType.*;
 
 /**
  * Instance of PackageTest is for configuring and running a single jpackage
@@ -123,30 +115,15 @@
     public PackageTest addBundlePropertyVerifier(String propertyName,
             BiConsumer<String, String> pred) {
         return addBundleVerifier(cmd -> {
-            String propertyValue = null;
-            switch (cmd.packageType()) {
-                case LINUX_DEB:
-                    propertyValue = LinuxHelper.getDebBundleProperty(
-                            cmd.outputBundle(), propertyName);
-                    break;
-
-                case LINUX_RPM:
-                    propertyValue = LinuxHelper.getRpmBundleProperty(
-                            cmd.outputBundle(), propertyName);
-                    break;
-
-                default:
-                    throw new UnsupportedOperationException();
-            }
-
-            pred.accept(propertyName, propertyValue);
+            pred.accept(propertyName,
+                    LinuxHelper.getBundleProperty(cmd, propertyName));
         });
     }
 
     public PackageTest addBundlePropertyVerifier(String propertyName,
             String expectedPropertyValue) {
         return addBundlePropertyVerifier(propertyName, (unused, v) -> {
-            Test.assertEquals(expectedPropertyValue, v, String.format(
+            TKit.assertEquals(expectedPropertyValue, v, String.format(
                     "Check value of %s property is [%s]", propertyName, v));
         });
     }
@@ -172,29 +149,36 @@
         return this;
     }
 
+    static void withTestFileAssociationsFile(FileAssociations fa, ThrowingConsumer<Path> consumer) {
+        final String testFileDefaultName = String.join(".", "test", fa.getSuffix());
+        TKit.withTempFile(testFileDefaultName, fa.getSuffix(), testFile -> {
+            if (TKit.isLinux()) {
+                LinuxHelper.initFileAssociationsTestFile(testFile);
+            }
+            consumer.accept(testFile);
+        });
+    }
+
     public PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
             String... faLauncherDefaultArgs) {
 
-        addInitializer(cmd -> HelloApp.addTo(cmd), "HelloApp");
+        addInitializer(cmd -> HelloApp.addTo(cmd, null), "HelloApp");
         addInstallVerifier(cmd -> {
             if (cmd.isFakeRuntimeInstalled(
                     "Not running file associations test")) {
                 return;
             }
 
-            Test.withTempFile(fa.getSuffix(), testFile -> {
+            withTestFileAssociationsFile(fa, testFile -> {
                 testFile = testFile.toAbsolutePath().normalize();
-                if (PackageType.LINUX.contains(cmd.packageType())) {
-                    LinuxHelper.initFileAssociationsTestFile(testFile);
-                }
 
                 final Path appOutput = Path.of(HelloApp.OUTPUT_FILENAME);
                 Files.deleteIfExists(appOutput);
 
-                Test.trace(String.format("Use desktop to open [%s] file",
+                TKit.trace(String.format("Use desktop to open [%s] file",
                         testFile));
                 Desktop.getDesktop().open(testFile.toFile());
-                Test.waitForFileCreated(appOutput, 7);
+                TKit.waitForFileCreated(appOutput, 7);
 
                 List<String> expectedArgs = new ArrayList<>(List.of(
                         faLauncherDefaultArgs));
@@ -203,7 +187,7 @@
                 // Wait a little bit after file has been created to
                 // make sure there are no pending writes into it.
                 Thread.sleep(3000);
-                HelloApp.verifyOutputFile(appOutput, expectedArgs.toArray(String[]::new));
+                HelloApp.verifyOutputFile(appOutput, expectedArgs);
             });
         });
 
@@ -214,7 +198,7 @@
         return this;
     }
 
-    private void forTypes(Collection<PackageType> types, Runnable action) {
+    PackageTest forTypes(Collection<PackageType> types, Runnable action) {
         Set<PackageType> oldTypes = Set.of(currentTypes.toArray(
                 PackageType[]::new));
         try {
@@ -223,14 +207,19 @@
         } finally {
             forTypes(oldTypes);
         }
+        return this;
     }
 
-    private void forTypes(PackageType type, Runnable action) {
-        forTypes(List.of(type), action);
+    PackageTest forTypes(PackageType type, Runnable action) {
+        return forTypes(List.of(type), action);
     }
 
     public PackageTest configureHelloApp() {
-        addInitializer(cmd -> HelloApp.addTo(cmd), "HelloApp");
+        return configureHelloApp(null);
+    }
+
+    public PackageTest configureHelloApp(String encodedName) {
+        addInitializer(cmd -> HelloApp.addTo(cmd, encodedName));
         addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput);
         return this;
     }
@@ -312,8 +301,11 @@
                 case CREATE:
                     Executor.Result result = cmd.execute();
                     result.assertExitCodeIs(expectedJPackageExitCode);
-                    Test.assertFileExists(cmd.outputBundle(),
-                            expectedJPackageExitCode == 0);
+                    if (expectedJPackageExitCode == 0) {
+                        TKit.assertFileExists(cmd.outputBundle());
+                    } else {
+                        TKit.assertPathExists(cmd.outputBundle(), false);
+                    }
                     verifyPackageBundle(cmd.createImmutableCopy(), result);
                     break;
 
@@ -330,7 +322,7 @@
         private void verifyPackageBundle(JPackageCommand cmd,
                 Executor.Result result) {
             if (PackageType.LINUX.contains(cmd.packageType())) {
-                Test.assertNotEquals(0L, LinuxHelper.getInstalledPackageSizeKB(
+                TKit.assertNotEquals(0L, LinuxHelper.getInstalledPackageSizeKB(
                         cmd), String.format(
                                 "Check installed size of [%s] package in KB is not zero",
                                 LinuxHelper.getPackageName(cmd)));
@@ -339,13 +331,13 @@
         }
 
         private void verifyPackageInstalled(JPackageCommand cmd) {
-            Test.trace(String.format("Verify installed: %s",
+            TKit.trace(String.format("Verify installed: %s",
                     cmd.getPrintableCommandLine()));
             if (cmd.isRuntime()) {
-                Test.assertDirectoryExists(
+                TKit.assertPathExists(
                         cmd.appRuntimeInstallationDirectory(), false);
             } else {
-                Test.assertExecutableFileExists(cmd.launcherInstallationPath(), true);
+                TKit.assertExecutableFileExists(cmd.launcherInstallationPath());
             }
 
             if (PackageType.WINDOWS.contains(cmd.packageType())) {
@@ -356,11 +348,11 @@
         }
 
         private void verifyPackageUninstalled(JPackageCommand cmd) {
-            Test.trace(String.format("Verify uninstalled: %s",
+            TKit.trace(String.format("Verify uninstalled: %s",
                     cmd.getPrintableCommandLine()));
             if (!cmd.isRuntime()) {
-                Test.assertPathExists(cmd.launcherInstallationPath(), false);
-                Test.assertPathExists(cmd.appInstallationDirectory(), false);
+                TKit.assertPathExists(cmd.launcherInstallationPath(), false);
+                TKit.assertPathExists(cmd.appInstallationDirectory(), false);
             }
 
             if (PackageType.WINDOWS.contains(cmd.packageType())) {
@@ -410,16 +402,15 @@
 
     static {
         final String propertyName = "output";
-        String val = Test.getConfigProperty(propertyName);
+        String val = TKit.getConfigProperty(propertyName);
         if (val == null) {
             bundleOutputDir = null;
         } else {
             bundleOutputDir = new File(val).getAbsoluteFile();
 
             if (!bundleOutputDir.isDirectory()) {
-                throw new IllegalArgumentException(String.format(
-                        "Invalid value of %s sytem property: [%s]. Should be existing directory",
-                        Test.getConfigPropertyName(propertyName),
+                throw new IllegalArgumentException(String.format("Invalid value of %s sytem property: [%s]. Should be existing directory",
+                        TKit.getConfigPropertyName(propertyName),
                         bundleOutputDir));
             }
         }
@@ -427,12 +418,10 @@
 
     static {
         final String propertyName = "action";
-        String action = Optional.ofNullable(Test.getConfigProperty(propertyName)).orElse(
+        String action = Optional.ofNullable(TKit.getConfigProperty(propertyName)).orElse(
                 Action.CREATE.toString()).toLowerCase();
         DEFAULT_ACTION = Stream.of(Action.values()).filter(
-                a -> a.toString().equals(action)).findFirst().orElseThrow(
-                        () -> new IllegalArgumentException(String.format(
-                                "Unrecognized value of %s property: [%s]",
-                                Test.getConfigPropertyName(propertyName), action)));
+                a -> a.toString().equals(action)).findFirst().orElseThrow(() -> new IllegalArgumentException(String.format("Unrecognized value of %s property: [%s]",
+                                TKit.getConfigPropertyName(propertyName), action)));
     }
 }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java	Mon Sep 30 19:11:19 2019 -0400
@@ -26,7 +26,6 @@
 import java.lang.reflect.Method;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -35,15 +34,15 @@
  */
 public enum PackageType {
     WIN_MSI(".msi",
-            Test.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
+            TKit.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
     WIN_EXE(".exe",
-            Test.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
+            TKit.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
     LINUX_DEB(".deb",
-            Test.isLinux() ? "jdk.jpackage.internal.LinuxDebBundler" : null),
+            TKit.isLinux() ? "jdk.jpackage.internal.LinuxDebBundler" : null),
     LINUX_RPM(".rpm",
-            Test.isLinux() ? "jdk.jpackage.internal.LinuxRpmBundler" : null),
-    MAC_DMG(".dmg", Test.isOSX() ? "jdk.jpackage.internal.MacDmgBundler" : null),
-    MAC_PKG(".pkg", Test.isOSX() ? "jdk.jpackage.internal.MacPkgBundler" : null),
+            TKit.isLinux() ? "jdk.jpackage.internal.LinuxRpmBundler" : null),
+    MAC_DMG(".dmg", TKit.isOSX() ? "jdk.jpackage.internal.MacDmgBundler" : null),
+    MAC_PKG(".pkg", TKit.isOSX() ? "jdk.jpackage.internal.MacPkgBundler" : null),
     IMAGE("app-image", null, null);
 
     PackageType(String packageName, String bundleSuffix, String bundlerClass) {
@@ -56,7 +55,7 @@
         }
 
         if (suffix != null && supported) {
-            Test.trace(String.format("Bundler %s supported", getName()));
+            TKit.trace(String.format("Bundler %s supported", getName()));
         }
     }
 
@@ -95,16 +94,17 @@
         try {
             Class clazz = Class.forName(bundlerClass);
             Method supported = clazz.getMethod("supported", boolean.class);
-            return ((Boolean) supported.invoke(clazz.newInstance(), true));
-        } catch (ClassNotFoundException ex) {
-            return false;
+            return ((Boolean) supported.invoke(
+                    clazz.getConstructor().newInstance(), true));
+        } catch (ClassNotFoundException | IllegalAccessException ex) {
         } catch (InstantiationException | NoSuchMethodException
-                | IllegalAccessException | InvocationTargetException ex) {
-            throw new RuntimeException(ex);
+                | InvocationTargetException ex) {
+            Functional.rethrowUnchecked(ex);
         }
+        return false;
     }
 
-    private String name;
+    private final String name;
     private final String suffix;
     private final boolean supported;
 
@@ -115,27 +115,11 @@
             Stream.concat(LINUX.stream(), WINDOWS.stream()),
             MAC.stream()).collect(Collectors.toUnmodifiableSet());
 
-    public final static PackageType DEFAULT = ((Supplier<PackageType>) () -> {
-        if (Test.isLinux()) {
-            return LINUX.stream().filter(v -> v.isSupported()).findFirst().orElseThrow();
-        }
-
-        if (Test.isWindows()) {
-            return WIN_EXE;
-        }
-
-        if (Test.isOSX()) {
-            return MAC_DMG;
-        }
-
-        throw new IllegalStateException("Unknwon platform");
-    }).get();
-
     private final static class Inner {
 
         private final static Set<String> DISABLED_PACKAGERS = Stream.of(
                 Optional.ofNullable(
-                        Test.getConfigProperty("disabledPackagers")).orElse(
+                        TKit.getConfigProperty("disabledPackagers")).orElse(
                         "").split(",")).collect(Collectors.toUnmodifiableSet());
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+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 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.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;
+
+final public class TKit {
+
+    public static final Path TEST_SRC_ROOT = Functional.identity(() -> {
+        Path root = Path.of(System.getProperty("test.src"));
+
+        for (int i = 0; i != 10; ++i) {
+            if (root.resolve("apps").toFile().isDirectory()) {
+                return root.toAbsolutePath();
+            }
+            root = root.resolve("..");
+        }
+
+        throw new RuntimeException("Failed to locate apps directory");
+    }).get();
+
+    public static void run(String args[], ThrowingRunnable testBody) {
+        if (currentTest != null) {
+            throw new IllegalStateException(
+                    "Unexpeced nested or concurrent Test.run() call");
+        }
+
+        TestInstance test = new TestInstance(testBody);
+        ThrowingRunnable.toRunnable(() -> runTests(List.of(test))).run();
+        if (!test.passed()) {
+            throw new RuntimeException();
+        }
+    }
+
+    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;
+            tests.stream().forEach(test -> {
+                currentTest = test;
+                try {
+                    ignoreExceptions(test).run();
+                } finally {
+                    currentTest = null;
+                    if (extraLogStream != null) {
+                        extraLogStream.flush();
+                    }
+                }
+            });
+        } finally {
+            extraLogStream = null;
+        }
+    }
+
+    static Runnable ignoreExceptions(ThrowingRunnable action) {
+        return () -> {
+            try {
+                try {
+                    action.run();
+                } catch (Throwable ex) {
+                    unbox(ex);
+                }
+            } catch (Throwable throwable) {
+                if (extraLogStream != null) {
+                    throwable.printStackTrace(extraLogStream);
+                }
+                throwable.printStackTrace();
+            }
+        };
+    }
+
+    static void unbox(Throwable throwable) throws Throwable {
+        try {
+            throw throwable;
+        } catch (ExceptionBox | InvocationTargetException ex) {
+            unbox(ex.getCause());
+        }
+    }
+
+    public static Path workDir() {
+        Path result = Path.of(".");
+        String testFunctionName = currentTest.functionName();
+        if (testFunctionName != null) {
+            result = result.resolve(testFunctionName);
+        }
+        return result;
+    }
+
+    static Path defaultInputDir() {
+        return workDir().resolve("input");
+    }
+
+    static Path defaultOutputDir() {
+        return workDir().resolve("output");
+    }
+
+    static String getCurrentDefaultAppName() {
+        // Construct app name from swapping and joining test base name
+        // and test function name.
+        // Say the test name is `FooTest.testBasic`. Then app name would be `BasicFooTest`.
+        String appNamePrefix = currentTest.functionName();
+        if (appNamePrefix != null && appNamePrefix.startsWith("test")) {
+            appNamePrefix = appNamePrefix.substring("test".length());
+        }
+        return Stream.of(appNamePrefix, currentTest.baseName()).filter(
+                v -> v != null && !v.isEmpty()).collect(Collectors.joining());
+    }
+
+    public static boolean isWindows() {
+        return (OS.contains("win"));
+    }
+
+    public static boolean isOSX() {
+        return (OS.contains("mac"));
+    }
+
+    public static boolean isLinux() {
+        return ((OS.contains("nix") || OS.contains("nux")));
+    }
+
+    static void log(String v) {
+        System.out.println(v);
+        if (extraLogStream != null) {
+            extraLogStream.println(v);
+        }
+    }
+
+    public static void createTextFile(Path propsFilename, Collection<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();
+        trace("Done");
+    }
+
+    public static void createPropertiesFile(Path propsFilename,
+            Collection<Map.Entry<String, String>> props) {
+        trace(String.format("Create [%s] properties file...",
+                propsFilename.toAbsolutePath().normalize()));
+        ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+                props.stream().map(e -> String.join("=", e.getKey(),
+                e.getValue())).peek(TKit::trace).collect(Collectors.toList()))).run();
+        trace("Done");
+    }
+
+    public static void createPropertiesFile(Path propsFilename,
+            Map.Entry<String, String>... props) {
+        createPropertiesFile(propsFilename, List.of(props));
+    }
+
+    public static void createPropertiesFile(Path propsFilename,
+            Map<String, String> props) {
+        createPropertiesFile(propsFilename, props.entrySet());
+    }
+
+    public static void trace(String v) {
+        if (TRACE) {
+            log("TRACE: " + v);
+        }
+    }
+
+    private static void traceAssert(String v) {
+        if (TRACE_ASSERTS) {
+            log("TRACE: " + v);
+        }
+    }
+
+    public static void error(String v) {
+        log("ERROR: " + v);
+        throw new AssertionError(v);
+    }
+
+    private final static String TEMP_FILE_PREFIX = null;
+
+    private static Path createUniqueFileName(String defaultName) {
+        final String[] nameComponents;
+
+        int separatorIdx = defaultName.lastIndexOf('.');
+        final String baseName;
+        if (separatorIdx == -1) {
+            baseName = defaultName;
+            nameComponents = new String[]{baseName};
+        } else {
+            baseName = defaultName.substring(0, separatorIdx);
+            nameComponents = new String[]{baseName, defaultName.substring(
+                separatorIdx + 1)};
+        }
+
+        final Path basedir = workDir();
+        int i = 0;
+        for (; i < 100; ++i) {
+            Path path = basedir.resolve(String.join(".", nameComponents));
+            if (!path.toFile().exists()) {
+                return path;
+            }
+            nameComponents[0] = String.format("%s.%d", baseName, i);
+        }
+        throw new IllegalStateException(String.format(
+                "Failed to create unique file name from [%s] basename after %d attempts",
+                baseName, i));
+    }
+
+    public static Path createTempDirectory(String role) throws IOException {
+        if (role == null) {
+            return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX);
+        }
+        return Files.createDirectory(createUniqueFileName(role));
+    }
+
+    public static Path createTempFile(String role, String suffix) throws
+            IOException {
+        if (role == null) {
+            return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix);
+        }
+        return Files.createFile(createUniqueFileName(role));
+    }
+
+    public static void withTempFile(String role, String suffix,
+            ThrowingConsumer<Path> action) {
+        final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile(
+                role, suffix)).get();
+        boolean keepIt = true;
+        try {
+            ThrowingConsumer.toConsumer(action).accept(tempFile);
+            keepIt = false;
+        } finally {
+            if (tempFile != null && !keepIt) {
+                ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
+            }
+        }
+    }
+
+    public static void withTempDirectory(String role,
+            ThrowingConsumer<Path> action) {
+        final Path tempDir = ThrowingSupplier.toSupplier(
+                () -> createTempDirectory(role)).get();
+        boolean keepIt = true;
+        try {
+            ThrowingConsumer.toConsumer(action).accept(tempDir);
+            keepIt = false;
+        } finally {
+            if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
+                deleteDirectoryRecursive(tempDir, "");
+            }
+        }
+    }
+
+    /**
+     * Deletes contents of the given directory recursively. Shortcut for
+     * <code>deleteDirectoryContentsRecursive(path, null)</code>
+     *
+     * @param path path to directory to clean
+     */
+    public static void deleteDirectoryContentsRecursive(Path path) {
+        deleteDirectoryContentsRecursive(path, null);
+    }
+
+    /**
+     * Deletes contents of the given directory recursively. If <code>path<code> is not a
+     * directory, request is silently ignored.
+     *
+     * @param path path to directory to clean
+     * @param msg log message. If null, the default log message is used. If
+     * empty string, no log message will be saved.
+     */
+    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();
+        }
+    }
+
+    /**
+     * Deletes the given directory recursively. Shortcut for
+     * <code>deleteDirectoryRecursive(path, null)</code>
+     *
+     * @param path path to directory to delete
+     */
+    public static void deleteDirectoryRecursive(Path path) {
+        deleteDirectoryRecursive(path, null);
+    }
+
+    /**
+     * Deletes the given directory recursively. If <code>path<code> is not a
+     * directory, request is silently ignored.
+     *
+     * @param path path to directory to delete
+     * @param msg log message. If null, the default log message is used. If
+     * empty string, no log message will be saved.
+     */
+    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);
+        }
+    }
+
+    public static RuntimeException throwUnknownPlatformError() {
+        if (isWindows() || isLinux() || isOSX()) {
+            throw new IllegalStateException(
+                    "Platform is known. throwUnknownPlatformError() called by mistake");
+        }
+        throw new IllegalStateException("Unknown platform");
+    }
+
+    static void waitForFileCreated(Path fileToWaitFor,
+            long timeoutSeconds) throws IOException {
+
+        trace(String.format("Wait for file [%s] to be available", fileToWaitFor));
+
+        WatchService ws = FileSystems.getDefault().newWatchService();
+
+        Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
+        watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);
+
+        long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000;
+        for (;;) {
+            long timeout = waitUntil - System.currentTimeMillis();
+            assertTrue(timeout > 0, String.format(
+                    "Check timeout value %d is positive", timeout));
+
+            WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout,
+                    TimeUnit.MILLISECONDS)).get();
+            if (key == null) {
+                if (fileToWaitFor.toFile().exists()) {
+                    trace(String.format(
+                            "File [%s] is available after poll timeout expired",
+                            fileToWaitFor));
+                    return;
+                }
+                assertUnexpected(String.format("Timeout expired", timeout));
+            }
+
+            for (WatchEvent<?> event : key.pollEvents()) {
+                if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
+                    continue;
+                }
+                Path contextPath = (Path) event.context();
+                if (Files.isSameFile(watchDirectory.resolve(contextPath),
+                        fileToWaitFor)) {
+                    trace(String.format("File [%s] is available", fileToWaitFor));
+                    return;
+                }
+            }
+
+            if (!key.reset()) {
+                assertUnexpected("Watch key invalidated");
+            }
+        }
+    }
+
+    private static String concatMessages(String msg, String msg2) {
+        if (msg2 != null && !msg2.isBlank()) {
+            return msg + ": " + msg2;
+        }
+        return msg;
+    }
+
+    public static void assertEquals(long expected, long actual, String msg) {
+        currentTest.notifyAssert();
+        if (expected != actual) {
+            error(concatMessages(String.format(
+                    "Expected [%d]. Actual [%d]", expected, actual),
+                    msg));
+        }
+
+        traceAssert(String.format("assertEquals(%d): %s", expected, msg));
+    }
+
+    public static void assertNotEquals(long expected, long actual, String msg) {
+        currentTest.notifyAssert();
+        if (expected == actual) {
+            error(concatMessages(String.format("Unexpected [%d] value", actual),
+                    msg));
+        }
+
+        traceAssert(String.format("assertNotEquals(%d, %d): %s", expected,
+                actual, msg));
+    }
+
+    public static void assertEquals(String expected, String actual, String msg) {
+        currentTest.notifyAssert();
+        if ((actual != null && !actual.equals(expected))
+                || (expected != null && !expected.equals(actual))) {
+            error(concatMessages(String.format(
+                    "Expected [%s]. Actual [%s]", expected, actual),
+                    msg));
+        }
+
+        traceAssert(String.format("assertEquals(%s): %s", expected, msg));
+    }
+
+    public static void assertNotEquals(String expected, String actual, String msg) {
+        currentTest.notifyAssert();
+        if ((actual != null && !actual.equals(expected))
+                || (expected != null && !expected.equals(actual))) {
+
+            traceAssert(String.format("assertNotEquals(%s, %s): %s", expected,
+                actual, msg));
+            return;
+        }
+
+        error(concatMessages(String.format("Unexpected [%s] value", actual), msg));
+    }
+
+    public static void assertNull(Object value, String msg) {
+        currentTest.notifyAssert();
+        if (value != null) {
+            error(concatMessages(String.format("Unexpected not null value [%s]",
+                    value), msg));
+        }
+
+        traceAssert(String.format("assertNull(): %s", msg));
+    }
+
+    public static void assertNotNull(Object value, String msg) {
+        currentTest.notifyAssert();
+        if (value == null) {
+            error(concatMessages("Unexpected null value", msg));
+        }
+
+        traceAssert(String.format("assertNotNull(%s): %s", value, msg));
+    }
+
+    public static void assertTrue(boolean actual, String msg) {
+        currentTest.notifyAssert();
+        if (!actual) {
+            error(concatMessages("Unexpected FALSE", msg));
+        }
+
+        traceAssert(String.format("assertTrue(): %s", msg));
+    }
+
+    public static void assertFalse(boolean actual, String msg) {
+        currentTest.notifyAssert();
+        if (actual) {
+            error(concatMessages("Unexpected TRUE", msg));
+        }
+
+        traceAssert(String.format("assertFalse(): %s", msg));
+    }
+
+    public static void assertPathExists(Path path, boolean exists) {
+        if (exists) {
+            assertTrue(path.toFile().exists(), String.format(
+                    "Check [%s] path exists", path));
+        } else {
+            assertFalse(path.toFile().exists(), String.format(
+                    "Check [%s] path doesn't exist", path));
+        }
+    }
+
+     public static void assertDirectoryExists(Path path) {
+        assertPathExists(path, true);
+        assertTrue(path.toFile().isDirectory(), String.format(
+                "Check [%s] is a directory", path));
+    }
+
+    public static void assertFileExists(Path path) {
+        assertPathExists(path, true);
+        assertTrue(path.toFile().isFile(), String.format("Check [%s] is a file",
+                path));
+    }
+
+    public static void assertExecutableFileExists(Path path) {
+        assertFileExists(path);
+        assertTrue(path.toFile().canExecute(), String.format(
+                "Check [%s] file is executable", path));
+    }
+
+    public static void assertReadableFileExists(Path path) {
+        assertFileExists(path);
+        assertTrue(path.toFile().canRead(), String.format(
+                "Check [%s] file is readable", path));
+    }
+
+    public static void assertUnexpected(String msg) {
+        currentTest.notifyAssert();
+        error(concatMessages("Unexpected", msg));
+    }
+
+    public static void assertStringListEquals(List<String> expected,
+            List<String> actual, String msg) {
+        currentTest.notifyAssert();
+
+        traceAssert(String.format("assertStringListEquals(): %s", msg));
+
+        String idxFieldFormat = Functional.identity(() -> {
+            int listSize = expected.size();
+            int width = 0;
+            while (listSize != 0) {
+                listSize = listSize / 10;
+                width++;
+            }
+            return "%" + width + "d";
+        }).get();
+
+        AtomicInteger counter = new AtomicInteger(0);
+        Iterator<String> actualIt = actual.iterator();
+        expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> {
+            int idx = counter.incrementAndGet();
+            String actualStr = actualIt.next();
+
+            if ((actualStr != null && !actualStr.equals(expectedStr))
+                    || (expectedStr != null && !expectedStr.equals(actualStr))) {
+                error(concatMessages(String.format(
+                        "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]",
+                        idx, expectedStr, actualStr), msg));
+            }
+
+            traceAssert(String.format(
+                    "assertStringListEquals(" + idxFieldFormat + ", %s)", idx,
+                    expectedStr));
+        });
+
+        if (expected.size() < actual.size()) {
+            // Actual string list is longer than expected
+            error(concatMessages(String.format(
+                    "Actual list is longer than expected by %d elements",
+                    actual.size() - expected.size()), msg));
+        }
+
+        if (actual.size() < expected.size()) {
+            // Actual string list is shorter than expected
+            error(concatMessages(String.format(
+                    "Actual list is longer than expected by %d elements",
+                    expected.size() - actual.size()), msg));
+        }
+    }
+
+    private static PrintStream openLogStream() {
+        if (LOG_FILE == null) {
+            return null;
+        }
+
+        return ThrowingSupplier.toSupplier(() -> new PrintStream(
+                new FileOutputStream(LOG_FILE.toFile(), true))).get();
+    }
+
+    private static TestInstance currentTest;
+    private static PrintStream extraLogStream;
+
+    private static final boolean TRACE;
+    private static final boolean TRACE_ASSERTS;
+
+    static final boolean VERBOSE_JPACKAGE;
+    static final boolean VERBOSE_TEST_SETUP;
+
+    static String getConfigProperty(String propertyName) {
+        return System.getProperty(getConfigPropertyName(propertyName));
+    }
+
+    static String getConfigPropertyName(String propertyName) {
+        return "jpackage.test." + propertyName;
+    }
+
+    static final Path LOG_FILE = Functional.identity(() -> {
+        String val = getConfigProperty("logfile");
+        if (val == null) {
+            return null;
+        }
+        return Path.of(val);
+    }).get();
+
+    static {
+        String val = getConfigProperty("suppress-logging");
+        if (val == null) {
+            TRACE = true;
+            TRACE_ASSERTS = true;
+            VERBOSE_JPACKAGE = true;
+            VERBOSE_TEST_SETUP = true;
+        } else if ("all".equals(val.toLowerCase())) {
+            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");
+        }
+    }
+
+    private static final String OS = System.getProperty("os.name").toLowerCase();
+}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Test.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,554 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.jpackage.test;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
-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.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import jdk.jpackage.test.Functional.ThrowingConsumer;
-import jdk.jpackage.test.Functional.ThrowingRunnable;
-import jdk.jpackage.test.Functional.ThrowingSupplier;
-
-final public class Test {
-
-    public static final Path TEST_SRC_ROOT = new Supplier<Path>() {
-        @Override
-        public Path get() {
-            Path root = Path.of(System.getProperty("test.src"));
-
-            for (int i = 0; i != 10; ++i) {
-                if (root.resolve("apps").toFile().isDirectory()) {
-                    return root.toAbsolutePath();
-                }
-                root = root.resolve("..");
-            }
-
-            throw new RuntimeException("Failed to locate apps directory");
-        }
-    }.get();
-
-    private static class Instance implements AutoCloseable {
-        Instance(String args[]) {
-            assertCount = 0;
-
-            name = enclosingMainMethodClass().getSimpleName();
-            extraLogStream = openLogStream();
-
-            currentTest = this;
-
-            log(String.format("[ RUN      ] %s", name));
-        }
-
-        @Override
-        public void close() {
-            log(String.format("%s %s; checks=%d",
-                    success ? "[       OK ]" : "[  FAILED  ]", name, assertCount));
-
-            if (extraLogStream != null) {
-                extraLogStream.close();
-            }
-        }
-
-        void notifyAssert() {
-            assertCount++;
-        }
-
-        void notifySuccess() {
-            success = true;
-        }
-
-        private int assertCount;
-        private boolean success;
-        private final String name;
-        private final PrintStream extraLogStream;
-    }
-
-    public static void run(String args[], TestBody action) {
-        if (currentTest != null) {
-            throw new IllegalStateException(
-                    "Unexpeced nested or concurrent Test.run() call");
-        }
-
-        try (Instance instance = new Instance(args)) {
-            action.run();
-            instance.notifySuccess();
-        } catch (Exception ex) {
-            throw new RuntimeException(ex);
-        } finally {
-            currentTest = null;
-        }
-    }
-
-    public static interface TestBody {
-        public void run() throws Exception;
-    }
-
-    public static Path workDir() {
-        return Path.of(".");
-    }
-
-    static Path defaultInputDir() {
-        return workDir().resolve("input");
-    }
-
-    static Path defaultOutputDir() {
-        return workDir().resolve("output");
-    }
-
-    static Class enclosingMainMethodClass() {
-        StackTraceElement st[] = Thread.currentThread().getStackTrace();
-        for (StackTraceElement ste : st) {
-            if ("main".equals(ste.getMethodName())) {
-                try {
-                    return Class.forName(ste.getClassName());
-                } catch (ClassNotFoundException ex) {
-                    throw new RuntimeException(ex);
-                }
-            }
-        }
-        return null;
-    }
-
-    static boolean isWindows() {
-        return (OS.contains("win"));
-    }
-
-    static boolean isOSX() {
-        return (OS.contains("mac"));
-    }
-
-    static boolean isLinux() {
-        return ((OS.contains("nix") || OS.contains("nux")));
-    }
-
-    static private void log(String v) {
-        System.out.println(v);
-        if (currentTest != null && currentTest.extraLogStream != null) {
-            currentTest.extraLogStream.println(v);
-        }
-    }
-
-    public static Class getTestClass () {
-        return enclosingMainMethodClass();
-    }
-
-    public static void createPropertiesFile(Path propsFilename,
-            Collection<Map.Entry<String, String>> props) {
-        trace(String.format("Create [%s] properties file...",
-                propsFilename.toAbsolutePath().normalize()));
-        try {
-            Files.write(propsFilename, props.stream().peek(e -> trace(
-                    String.format("%s=%s", e.getKey(), e.getValue()))).map(
-                    e -> String.format("%s=%s", e.getKey(), e.getValue())).collect(
-                            Collectors.toList()));
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-        trace("Done");
-    }
-
-    public static void createPropertiesFile(Path propsFilename,
-            Map.Entry<String, String>... props) {
-        createPropertiesFile(propsFilename, List.of(props));
-    }
-
-    public static void createPropertiesFile(Path propsFilename,
-            Map<String, String> props) {
-        createPropertiesFile(propsFilename, props.entrySet());
-    }
-
-    public static void trace(String v) {
-        if (TRACE) {
-            log("TRACE: " + v);
-        }
-    }
-
-    private static void traceAssert(String v) {
-        if (TRACE_ASSERTS) {
-            log("TRACE: " + v);
-        }
-    }
-
-    public static void error(String v) {
-        log("ERROR: " + v);
-        throw new AssertionError(v);
-    }
-
-    private static final String TEMP_FILE_PREFIX = null;
-
-    public static Path createTempDirectory() throws IOException {
-        return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX);
-    }
-
-    public static Path createTempFile(String suffix) throws IOException {
-        return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix);
-    }
-
-    public static void withTempFile(String suffix, ThrowingConsumer<Path> action) {
-        final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile(
-                suffix)).get();
-        boolean keepIt = true;
-        try {
-            ThrowingConsumer.toConsumer(action).accept(tempFile);
-            keepIt = false;
-        } finally {
-            if (tempFile != null && !keepIt) {
-                ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
-            }
-        }
-    }
-
-    public static void withTempDirectory(ThrowingConsumer<Path> action) {
-        final Path tempDir = ThrowingSupplier.toSupplier(
-                () -> createTempDirectory()).get();
-        boolean keepIt = true;
-        try {
-            ThrowingConsumer.toConsumer(action).accept(tempDir);
-            keepIt = false;
-        } finally {
-            if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
-                deleteDirectoryRecursive(tempDir);
-            }
-        }
-    }
-
-    static void deleteDirectoryRecursive(Path path) {
-        ThrowingRunnable.toRunnable(() -> Files.walk(path).sorted(
-                Comparator.reverseOrder()).map(Path::toFile).forEach(
-                File::delete)).run();
-    }
-
-    static void waitForFileCreated(Path fileToWaitFor,
-            long timeoutSeconds) throws IOException {
-
-        trace(String.format("Wait for file [%s] to be available", fileToWaitFor));
-
-        WatchService ws = FileSystems.getDefault().newWatchService();
-
-        Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
-        watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);
-
-        long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000;
-        for (;;) {
-            long timeout = waitUntil - System.currentTimeMillis();
-            assertTrue(timeout > 0, String.format(
-                    "Check timeout value %d is positive", timeout));
-
-            WatchKey key = null;
-            try {
-                key = ws.poll(timeout, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ex) {
-                throw new RuntimeException(ex);
-            }
-
-            if (key == null) {
-                if (fileToWaitFor.toFile().exists()) {
-                    trace(String.format(
-                            "File [%s] is available after poll timeout expired",
-                            fileToWaitFor));
-                    return;
-                }
-                assertUnexpected(String.format("Timeout expired", timeout));
-            }
-
-            for (WatchEvent<?> event : key.pollEvents()) {
-                if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
-                    continue;
-                }
-                Path contextPath = (Path) event.context();
-                if (Files.isSameFile(watchDirectory.resolve(contextPath),
-                        fileToWaitFor)) {
-                    trace(String.format("File [%s] is available", fileToWaitFor));
-                    return;
-                }
-            }
-
-            if (!key.reset()) {
-                assertUnexpected("Watch key invalidated");
-            }
-        }
-    }
-
-    private static String concatMessages(String msg, String msg2) {
-        if (msg2 != null && !msg2.isBlank()) {
-            return msg + ": " + msg2;
-        }
-        return msg;
-    }
-
-    public static void assertEquals(long expected, long actual, String msg) {
-        currentTest.notifyAssert();
-        if (expected != actual) {
-            error(concatMessages(String.format(
-                    "Expected [%d]. Actual [%d]", expected, actual),
-                    msg));
-        }
-
-        traceAssert(String.format("assertEquals(%d): %s", expected, msg));
-    }
-
-    public static void assertNotEquals(long expected, long actual, String msg) {
-        currentTest.notifyAssert();
-        if (expected == actual) {
-            error(concatMessages(String.format("Unexpected [%d] value", actual),
-                    msg));
-        }
-
-        traceAssert(String.format("assertNotEquals(%d, %d): %s", expected,
-                actual, msg));
-    }
-
-    public static void assertEquals(String expected, String actual, String msg) {
-        currentTest.notifyAssert();
-        if ((actual != null && !actual.equals(expected))
-                || (expected != null && !expected.equals(actual))) {
-            error(concatMessages(String.format(
-                    "Expected [%s]. Actual [%s]", expected, actual),
-                    msg));
-        }
-
-        traceAssert(String.format("assertEquals(%s): %s", expected, msg));
-    }
-
-    public static void assertNotEquals(String expected, String actual, String msg) {
-        currentTest.notifyAssert();
-        if ((actual != null && !actual.equals(expected))
-                || (expected != null && !expected.equals(actual))) {
-
-            traceAssert(String.format("assertNotEquals(%s, %s): %s", expected,
-                actual, msg));
-            return;
-        }
-
-        error(concatMessages(String.format("Unexpected [%s] value", actual), msg));
-    }
-
-    public static void assertNull(Object value, String msg) {
-        currentTest.notifyAssert();
-        if (value != null) {
-            error(concatMessages(String.format("Unexpected not null value [%s]",
-                    value), msg));
-        }
-
-        traceAssert(String.format("assertNull(): %s", msg));
-    }
-
-    public static void assertNotNull(Object value, String msg) {
-        currentTest.notifyAssert();
-        if (value == null) {
-            error(concatMessages("Unexpected null value", msg));
-        }
-
-        traceAssert(String.format("assertNotNull(%s): %s", value, msg));
-    }
-
-    public static void assertTrue(boolean actual, String msg) {
-        currentTest.notifyAssert();
-        if (!actual) {
-            error(concatMessages("Unexpected FALSE", msg));
-        }
-
-        traceAssert(String.format("assertTrue(): %s", msg));
-    }
-
-    public static void assertFalse(boolean actual, String msg) {
-        currentTest.notifyAssert();
-        if (actual) {
-            error(concatMessages("Unexpected TRUE", msg));
-        }
-
-        traceAssert(String.format("assertFalse(): %s", msg));
-    }
-
-    public static void assertPathExists(Path path, boolean exists) {
-        if (exists) {
-            assertTrue(path.toFile().exists(), String.format(
-                    "Check [%s] path exists", path));
-        } else {
-            assertFalse(path.toFile().exists(), String.format(
-                    "Check [%s] path doesn't exist", path));
-        }
-    }
-
-    public static void assertDirectoryExists(Path path, boolean exists) {
-        assertPathExists(path, exists);
-        if (exists) {
-            assertTrue(path.toFile().isDirectory(), String.format(
-                    "Check [%s] is a directory", path));
-        }
-    }
-
-    public static void assertFileExists(Path path, boolean exists) {
-        assertPathExists(path, exists);
-        if (exists) {
-            assertTrue(path.toFile().isFile(), String.format(
-                    "Check [%s] is a file", path));
-        }
-    }
-
-    public static void assertExecutableFileExists(Path path, boolean exists) {
-        assertFileExists(path, exists);
-        if (exists) {
-            assertTrue(path.toFile().canExecute(), String.format(
-                    "Check [%s] file is executable", path));
-        }
-    }
-
-    public static void assertReadableFileExists(Path path) {
-        assertFileExists(path, true);
-        assertTrue(path.toFile().canRead(), String.format(
-                "Check [%s] file is readable", path));
-     }
-
-    public static void assertUnexpected(String msg) {
-        currentTest.notifyAssert();
-        error(concatMessages("Unexpected", msg));
-    }
-
-    public static void assertStringListEquals(List<String> expected,
-            List<String> actual, String msg) {
-        currentTest.notifyAssert();
-
-        if (expected.size() < actual.size()) {
-            // Actual string list is longer than expected
-            error(concatMessages(String.format(
-                    "Actual list is longer than expected by %d elements",
-                    actual.size() - expected.size()), msg));
-        }
-
-        if (actual.size() < expected.size()) {
-            // Actual string list is shorter than expected
-            error(concatMessages(String.format(
-                    "Actual list is longer than expected by %d elements",
-                    expected.size() - actual.size()), msg));
-        }
-
-        traceAssert(String.format("assertStringListEquals(): %s", msg));
-
-        String idxFieldFormat = Functional.identity(() -> {
-            int listSize = expected.size();
-            int width = 0;
-            while (listSize != 0) {
-                listSize = listSize / 10;
-                width++;
-            }
-            return "%" + width + "d";
-        }).get();
-
-        AtomicInteger counter = new AtomicInteger(0);
-        Iterator<String> actualIt = actual.iterator();
-        expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> {
-            int idx = counter.incrementAndGet();
-            String actualStr = actualIt.next();
-
-            if ((actualStr != null && !actualStr.equals(expectedStr))
-                    || (expectedStr != null && !expectedStr.equals(actualStr))) {
-                error(concatMessages(String.format(
-                        "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]",
-                        idx, expectedStr, actualStr), msg));
-            }
-
-            traceAssert(String.format(
-                    "assertStringListEquals(" + idxFieldFormat + ", %s)", idx,
-                    expectedStr));
-        });
-    }
-
-    private static PrintStream openLogStream() {
-        if (LOG_FILE == null) {
-            return null;
-        }
-
-        try {
-            return new PrintStream(new FileOutputStream(LOG_FILE.toFile(), true));
-        } catch (FileNotFoundException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private static Instance currentTest;
-
-    private static final boolean TRACE;
-    private static final boolean TRACE_ASSERTS;
-
-    static final boolean VERBOSE_JPACKAGE;
-
-    static String getConfigProperty(String propertyName) {
-        return System.getProperty(getConfigPropertyName(propertyName));
-    }
-
-    static String getConfigPropertyName(String propertyName) {
-        return "jpackage.test." + propertyName;
-    }
-
-    static final Path LOG_FILE = Functional.identity(() -> {
-        String val = getConfigProperty("logfile");
-        if (val == null) {
-            return null;
-        }
-        return Path.of(val);
-    }).get();
-
-    static {
-        String val = getConfigProperty("suppress-logging");
-        if (val == null) {
-            TRACE = true;
-            TRACE_ASSERTS = true;
-            VERBOSE_JPACKAGE = true;
-        } else if ("all".equals(val.toLowerCase())) {
-            TRACE = false;
-            TRACE_ASSERTS = false;
-            VERBOSE_JPACKAGE = 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"));
-        }
-    }
-
-    private static final String OS = System.getProperty("os.name").toLowerCase();
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+final class TestBuilder implements AutoCloseable {
+
+    @Override
+    public void close() throws Exception {
+        flushTestGroup(null);
+    }
+
+    TestBuilder(Consumer<TestInstance> testConsumer) {
+        argProcessors = Map.of(CMDLINE_ARG_PREFIX + "after-run",
+                arg -> getJavaMethodsFromArg(arg).forEach(
+                        (method) -> afterActions.add(wrap(method, dryRun))),
+                CMDLINE_ARG_PREFIX + "before-run",
+                arg -> getJavaMethodsFromArg(arg).forEach(
+                        (method) -> beforeActions.add(wrap(method, dryRun))),
+                CMDLINE_ARG_PREFIX + "run",
+                arg -> flushTestGroup(getJavaMethodsFromArg(arg).map(
+                        TestBuilder::toMethodCalls).flatMap(List::stream).collect(
+                        Collectors.toList())),
+                CMDLINE_ARG_PREFIX + "dry-run",
+                arg -> dryRun = true);
+        this.testConsumer = testConsumer;
+        clear();
+    }
+
+    void processCmdLineArg(String arg) throws Throwable {
+        int separatorIdx = arg.indexOf('=');
+        final String argName;
+        final String argValue;
+        if (separatorIdx != -1) {
+            argName = arg.substring(0, separatorIdx);
+            argValue = arg.substring(separatorIdx + 1);
+        } else {
+            argName = arg;
+            argValue = null;
+        }
+        try {
+            ThrowingConsumer<String> argProcessor = argProcessors.get(argName);
+            if (argProcessor == null) {
+                throw new ParseException("Unrecognized");
+            }
+            argProcessor.accept(argValue);
+        } catch (ParseException ex) {
+            ex.setContext(arg);
+            throw ex;
+        }
+    }
+
+    private void flushTestGroup(List<MethodCall> newTestGroup) {
+        if (testGroup != null) {
+            testGroup.forEach(testBody -> createTestInstance(testBody));
+            clear();
+        }
+        testGroup = newTestGroup;
+    }
+
+    private void createTestInstance(MethodCall testBody) {
+        ThrowingFunction<MethodCall, Object> testContructor;
+        if (dryRun) {
+            testContructor = (unused) -> null;
+            testBody = DRY_RUN_TEST_BODY;
+        } else {
+            testContructor = TestBuilder::constructTest;
+        }
+
+        TestInstance test = new TestInstance(testContructor, testBody,
+                beforeActions, afterActions);
+        trace(String.format("[%s] test constructed", test.fullName()));
+        testConsumer.accept(test);
+    }
+
+    public static void nop () {
+    }
+
+    private final static MethodCall DRY_RUN_TEST_BODY = ThrowingSupplier.toSupplier(() -> {
+        return new MethodCall(TestBuilder.class.getMethod("nop"));
+    }).get();
+
+    private static Object constructTest(MethodCall testBody) throws
+            NoSuchMethodException, InstantiationException,
+            IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException {
+        Constructor ctor = testBody.getRequiredConstructor();
+        if (ctor == null) {
+            return null;
+        }
+        return ctor.newInstance();
+    }
+
+    private void clear() {
+        beforeActions = new ArrayList<>();
+        afterActions = new ArrayList<>();
+        testGroup = null;
+    }
+
+    private static Class probeClass(String name) {
+        try {
+            return Class.forName(name);
+        } catch (ClassNotFoundException ex) {
+            return null;
+        }
+    }
+
+    private static Stream<String> cmdLineArgValueToMethodNames(String v) {
+        List<String> result = new ArrayList<>();
+        String defaultClassName = null;
+        for (String token : v.split(",")) {
+            Class testSet = probeClass(token);
+            if (testSet != null) {
+                // Test set class specified. Pull in all public methods
+                // from the class with @Test annotation removing name duplicates.
+                // Overloads will be handled at the next phase of processing.
+                defaultClassName = token;
+                Stream.of(testSet.getMethods()).filter(
+                        m -> m.isAnnotationPresent(Test.class)).map(
+                                Method::getName).distinct().forEach(
+                                name -> result.add(String.join(".", token, name)));
+                continue;
+            }
+
+            final String qualifiedMethodName;
+            final int lastDotIdx = token.lastIndexOf('.');
+            if (lastDotIdx != -1) {
+                qualifiedMethodName = token;
+                defaultClassName = token.substring(0, lastDotIdx);
+            } else if (defaultClassName == null) {
+                throw new ParseException("Default class name not found in");
+            } else {
+                qualifiedMethodName = String.join(".", defaultClassName, token);
+            }
+            result.add(qualifiedMethodName);
+        }
+        return result.stream();
+    }
+
+    private static boolean filterMethod(String expectedMethodName, Method method) {
+        if (!method.getName().equals(expectedMethodName)) {
+            return false;
+        }
+        switch (method.getParameterCount()) {
+            case 0:
+                return !isParametrized(method);
+            case 1:
+                return isParametrized(method);
+        }
+        return false;
+    }
+
+    private static boolean isParametrized(Method method) {
+        return method.isAnnotationPresent(Parameters.class) || method.isAnnotationPresent(
+                Parameter.class);
+    }
+
+    private static List<Method> getJavaMethodFromString(
+            String qualifiedMethodName) {
+        int lastDotIdx = qualifiedMethodName.lastIndexOf('.');
+        if (lastDotIdx == -1) {
+            throw new ParseException("Class name not found in");
+        }
+        String className = qualifiedMethodName.substring(0, lastDotIdx);
+        String methodName = qualifiedMethodName.substring(lastDotIdx + 1);
+        Class methodClass;
+        try {
+            methodClass = Class.forName(className);
+        } catch (ClassNotFoundException ex) {
+            throw new ParseException(String.format("Class [%s] not found;",
+                    className));
+        }
+        // Get the list of all public methods as need to deal with overloads.
+        List<Method> methods = Stream.of(methodClass.getMethods()).filter(
+                (m) -> filterMethod(methodName, m)).collect(Collectors.toList());
+        if (methods.isEmpty()) {
+            new ParseException(String.format(
+                    "Method [%s] not found in [%s] class;",
+                    methodName, className));
+        }
+        // Make sure default constructor is accessible if the one is needed.
+        // Need to probe all methods as some of them might be static and
+        // some class members.
+        // Onlu class members require default ctor.
+        for (Method method : methods) {
+            try {
+                MethodCall.getRequiredConstructor(method);
+            } catch (NoSuchMethodException ex) {
+                throw new ParseException(String.format(
+                        "Default constructor not found in [%s] class;",
+                        className));
+            }
+        }
+
+        trace(String.format("%s -> %s", qualifiedMethodName, methods));
+        return methods;
+    }
+
+    private static Stream<Method> getJavaMethodsFromArg(String argValue) {
+        return cmdLineArgValueToMethodNames(argValue).map(
+                ThrowingFunction.toFunction(
+                        TestBuilder::getJavaMethodFromString)).flatMap(
+                        List::stream).sequential();
+    }
+
+    private static Parameter[] getParameters(Method method) {
+        if (method.isAnnotationPresent(Parameters.class)) {
+            return ((Parameters) method.getAnnotation(Parameters.class)).value();
+        }
+
+        if (method.isAnnotationPresent(Parameter.class)) {
+            return new Parameter[]{(Parameter) method.getAnnotation(
+                Parameter.class)};
+        }
+
+        // Unexpected
+        return null;
+    }
+
+    private static List<MethodCall> toMethodCalls(Method method) {
+        if (!isParametrized(method)) {
+            return List.of(new MethodCall(method));
+        }
+        Parameter[] annotations = getParameters(method);
+        if (annotations.length == 0) {
+            return List.of(new MethodCall(method));
+        }
+        return Stream.of(annotations).map((a) -> {
+            String annotationValue = a.value();
+            Class paramClass = method.getParameterTypes()[0];
+            return new MethodCall(method,
+                    fromString(annotationValue, paramClass));
+        }).collect(Collectors.toList());
+    }
+
+    private static Object fromString(String value, Class toType) {
+        Function<String, Object> converter = conv.get(toType);
+        if (converter == null) {
+            throw new RuntimeException(String.format(
+                    "Failed to find a conversion of [%s] string to %s type",
+                    value, toType));
+        }
+        return converter.apply(value);
+    }
+
+    // Wraps Method.invike() into ThrowingRunnable.run()
+    private static ThrowingConsumer wrap(Method method, boolean dryRun) {
+        return (test) -> {
+            Class methodClass = method.getDeclaringClass();
+            String methodName = String.join(".", methodClass.getName(),
+                    method.getName());
+            TKit.log(String.format("[ CALL     ] %s()", methodName));
+            if (!dryRun) {
+                if (methodClass.isInstance(test)) {
+                    method.invoke(test);
+                } else {
+                    method.invoke(null);
+                }
+            }
+        };
+    }
+
+    private static class ParseException extends IllegalArgumentException {
+
+        ParseException(String msg) {
+            super(msg);
+        }
+
+        void setContext(String badCmdLineArg) {
+            this.badCmdLineArg = badCmdLineArg;
+        }
+
+        @Override
+        public String getMessage() {
+            String msg = super.getMessage();
+            if (badCmdLineArg != null) {
+                msg = String.format("%s parameter=[%s]", msg, badCmdLineArg);
+            }
+            return msg;
+        }
+        private String badCmdLineArg;
+    }
+
+    private static void trace(String msg) {
+        if (TKit.VERBOSE_TEST_SETUP) {
+            TKit.log(msg);
+        }
+    }
+
+    private final Map<String, ThrowingConsumer<String>> argProcessors;
+    private Consumer<TestInstance> testConsumer;
+    private List<MethodCall> testGroup;
+    private List<ThrowingConsumer> beforeActions;
+    private List<ThrowingConsumer> afterActions;
+    private boolean dryRun;
+
+    private final static Map<Class, Function<String, Object>> conv = Map.of(
+            boolean.class, Boolean::valueOf,
+            Boolean.class, Boolean::valueOf,
+            int.class, Integer::valueOf,
+            Integer.class, Integer::valueOf,
+            long.class, Long::valueOf,
+            Long.class, Long::valueOf,
+            String.class, String::valueOf);
+
+    final static String CMDLINE_ARG_PREFIX = "--jpt-";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingRunnable;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+
+class TestInstance implements ThrowingRunnable {
+
+    static class TestDesc {
+        private TestDesc() {
+        }
+
+        String testFullName() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(clazz.getSimpleName());
+            if (functionName != null) {
+                sb.append('.');
+                sb.append(functionName);
+                if (args != null) {
+                    sb.append('(').append(args).append(')');
+                }
+            }
+            return sb.toString();
+        }
+
+        private Class clazz;
+        private String functionName;
+        private String args;
+
+        private static TestDesc create() {
+            TestDesc desc = new TestDesc();
+            desc.clazz = enclosingMainMethodClass();
+            return desc;
+        }
+
+        static TestDesc create(Method m, Object... args) {
+            TestDesc desc = new TestDesc();
+            desc.clazz = m.getDeclaringClass();
+            desc.functionName = m.getName();
+            if (args.length != 0) {
+                desc.args = Stream.of(args).map(Object::toString).collect(
+                        Collectors.joining(","));
+            }
+            return desc;
+        }
+    }
+
+    TestInstance(ThrowingRunnable testBody) {
+        assertCount = 0;
+        this.testConstructor = (unused) -> null;
+        this.testBody = (unused) -> testBody.run();
+        this.beforeActions = Collections.emptyList();
+        this.afterActions = Collections.emptyList();
+        this.testDesc = TestDesc.create();
+    }
+
+    TestInstance(ThrowingFunction testConstructor, MethodCall testBody,
+            List<ThrowingConsumer> beforeActions,
+            List<ThrowingConsumer> afterActions) {
+        assertCount = 0;
+        this.testConstructor = testConstructor;
+        this.testBody = testBody;
+        this.beforeActions = beforeActions;
+        this.afterActions = afterActions;
+        this.testDesc = testBody.createDescription();
+    }
+
+    void notifyAssert() {
+        assertCount++;
+    }
+
+    boolean passed() {
+        return success;
+    }
+
+    String functionName() {
+        return testDesc.functionName;
+    }
+
+    String baseName() {
+        return testDesc.clazz.getSimpleName();
+    }
+
+    String fullName() {
+        return testDesc.testFullName();
+    }
+
+    @Override
+    public void run() throws Throwable {
+        final String fullName = testDesc.testFullName();
+        TKit.log(String.format("[ RUN      ] %s", fullName));
+        try {
+            Object testInstance = testConstructor.apply(testBody);
+            beforeActions.forEach((a) -> ThrowingConsumer.toConsumer(a).accept(
+                    testInstance));
+            Files.createDirectories(TKit.workDir());
+            try {
+                testBody.accept(testInstance);
+            } finally {
+                afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept(
+                        testInstance)));
+            }
+            success = true;
+        } finally {
+            TKit.log(String.format("%s %s; checks=%d",
+                    success ? "[       OK ]" : "[  FAILED  ]", fullName,
+                    assertCount));
+        }
+    }
+
+    private static Class enclosingMainMethodClass() {
+        StackTraceElement st[] = Thread.currentThread().getStackTrace();
+        for (StackTraceElement ste : st) {
+            if ("main".equals(ste.getMethodName())) {
+                return Functional.ThrowingSupplier.toSupplier(() -> Class.forName(
+                        ste.getClassName())).get();
+            }
+        }
+        return null;
+    }
+
+    private int assertCount;
+    private boolean success;
+    private final TestDesc testDesc;
+    private final ThrowingFunction testConstructor;
+    private final ThrowingConsumer testBody;
+    private final List<ThrowingConsumer> beforeActions;
+    private final List<ThrowingConsumer> afterActions;
+}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java	Mon Sep 30 19:11:19 2019 -0400
@@ -25,9 +25,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -83,16 +81,24 @@
             return Path.of(cmd.name() + ".lnk");
         }
 
+        private void verifyShortcut(Path path, boolean exists) {
+            if (exists) {
+                TKit.assertFileExists(path);
+            } else {
+                TKit.assertPathExists(path, false);
+            }
+        }
+
         private void verifySystemDesktopShortcut(boolean exists) {
             Path dir = Path.of(queryRegistryValueCache(
                     SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop"));
-            Test.assertFileExists(dir.resolve(desktopShortcutPath()), exists);
+            verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
         }
 
         private void verifyUserLocalDesktopShortcut(boolean exists) {
             Path dir = Path.of(
                     queryRegistryValueCache(USER_SHELL_FOLDERS_REGKEY, "Desktop"));
-            Test.assertFileExists(dir.resolve(desktopShortcutPath()), exists);
+            verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
         }
 
         private void verifyStartMenuShortcut() {
@@ -119,13 +125,13 @@
         private void verifySystemStartMenuShortcut(boolean exists) {
             Path dir = Path.of(queryRegistryValueCache(
                     SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs"));
-            Test.assertFileExists(dir.resolve(startMenuShortcutPath()), exists);
+            verifyShortcut(dir.resolve(startMenuShortcutPath()), exists);
         }
 
         private void verifyUserLocalStartMenuShortcut(boolean exists) {
             Path dir = Path.of(queryRegistryValueCache(
                     USER_SHELL_FOLDERS_REGKEY, "Programs"));
-            Test.assertFileExists(dir.resolve(startMenuShortcutPath()), exists);
+            verifyShortcut(dir.resolve(startMenuShortcutPath()), exists);
         }
 
         private void verifyFileAssociationsRegistry() {
@@ -136,7 +142,7 @@
         private void verifyFileAssociationsRegistry(Path faFile) {
             boolean appInstalled = cmd.launcherInstallationPath().toFile().exists();
             try {
-                Test.trace(String.format(
+                TKit.trace(String.format(
                         "Get file association properties from [%s] file",
                         faFile));
                 Map<String, String> faProps = Files.readAllLines(faFile).stream().filter(
@@ -150,10 +156,10 @@
                                 entry -> entry.getValue()));
                 String suffix = faProps.get("extension");
                 String contentType = faProps.get("mime-type");
-                Test.assertNotNull(suffix, String.format(
+                TKit.assertNotNull(suffix, String.format(
                         "Check file association suffix [%s] is found in [%s] property file",
                         suffix, faFile));
-                Test.assertNotNull(contentType, String.format(
+                TKit.assertNotNull(contentType, String.format(
                         "Check file association content type [%s] is found in [%s] property file",
                         contentType, faFile));
                 verifyFileAssociations(appInstalled, "." + suffix, contentType);
@@ -172,14 +178,14 @@
                     "Extension");
 
             if (exists) {
-                Test.assertEquals(suffix, suffixFromRegistry,
+                TKit.assertEquals(suffix, suffixFromRegistry,
                         "Check suffix in registry is as expected");
-                Test.assertEquals(contentType, contentTypeFromRegistry,
+                TKit.assertEquals(contentType, contentTypeFromRegistry,
                         "Check content type in registry is as expected");
             } else {
-                Test.assertNull(suffixFromRegistry,
+                TKit.assertNull(suffixFromRegistry,
                         "Check suffix in registry not found");
-                Test.assertNull(contentTypeFromRegistry,
+                TKit.assertNull(contentTypeFromRegistry,
                         "Check content type in registry not found");
             }
         }
@@ -200,7 +206,7 @@
                     () -> new RuntimeException(String.format(
                             "Failed to find [%s] string in the output",
                             lookupString)));
-            Test.trace(String.format(
+            TKit.trace(String.format(
                     "Registry value [%s] at [%s] path not found", valueName,
                     keyPath));
             return null;
@@ -211,7 +217,7 @@
         //     Common Desktop    REG_SZ    C:\Users\Public\Desktop
         value = value.split("    REG_SZ    ")[1];
 
-        Test.trace(String.format("Registry value [%s] at [%s] path is [%s]",
+        TKit.trace(String.format("Registry value [%s] at [%s] path is [%s]",
                 valueName, keyPath, value));
 
         return value;
@@ -229,6 +235,9 @@
         return value;
     }
 
+    static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+            "bin\\server\\jvm.dll"));
+
     // jtreg resets %ProgramFiles% environment variable by some reason.
     private final static Path PROGRAM_FILES = Path.of(Optional.ofNullable(
             System.getenv("ProgramFiles")).orElse("C:\\Program Files"));
--- a/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/ApplicationLayoutTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/ApplicationLayoutTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -57,7 +57,7 @@
     @Test
     public void testLinux() throws IOException {
         fillLinuxAppImage();
-        testApplicationLayout(ApplicationLayout.linuxApp());
+        testApplicationLayout(ApplicationLayout.linuxAppImage());
     }
 
     private void testApplicationLayout(ApplicationLayout layout) throws IOException {
--- a/test/jdk/tools/jpackage/linux/AppCategoryTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/AppCategoryTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -44,6 +44,7 @@
  * @test
  * @summary jpackage with --linux-app-category
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m AppCategoryTest
@@ -53,7 +54,7 @@
     public static void main(String[] args) {
         final String CATEGORY = "Foo";
 
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.LINUX)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/linux/LicenseTypeTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/LicenseTypeTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -39,6 +39,7 @@
  * @test
  * @summary jpackage with --linux-rpm-license-type
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m LicenseTypeTest
@@ -48,7 +49,7 @@
     public static void main(String[] args) {
         final String LICENSE_TYPE = "JP_LICENSE_TYPE";
 
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest().forTypes(PackageType.LINUX_RPM).configureHelloApp()
             .addInitializer(cmd -> {
                 cmd.addArguments("--linux-rpm-license-type", LICENSE_TYPE);
--- a/test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -44,6 +44,7 @@
  * @test
  * @summary jpackage with --linux-package-name
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m LinuxBundleNameTest
@@ -53,7 +54,7 @@
     public static void main(String[] args) {
         final String PACKAGE_NAME = "quickbrownfox2";
 
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.LINUX)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/linux/MaintainerTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/MaintainerTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -23,7 +23,7 @@
 
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 
 
 /**
@@ -40,6 +40,7 @@
  * @test
  * @summary jpackage with --linux-deb-maintainer
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m MaintainerTest
@@ -49,14 +50,14 @@
     public static void main(String[] args) {
         final String MAINTAINER = "jpackage-test@java.com";
 
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest().forTypes(PackageType.LINUX_DEB).configureHelloApp()
             .addInitializer(cmd -> {
                 cmd.addArguments("--linux-deb-maintainer", MAINTAINER);
             })
             .addBundlePropertyVerifier("Maintainer", (propName, propValue) -> {
                 String lookupValue = "<" + MAINTAINER + ">";
-                Test.assertTrue(propValue.endsWith(lookupValue),
+                TKit.assertTrue(propValue.endsWith(lookupValue),
                         String.format("Check value of %s property [%s] ends with %s",
                                 propName, propValue, lookupValue));
             })
--- a/test/jdk/tools/jpackage/linux/PackageDepsTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/PackageDepsTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -45,28 +45,28 @@
  * @test
  * @summary jpackage with --linux-package-deps
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m PackageDepsTest
  */
 public class PackageDepsTest {
 
-    // Pick the name of prerequisite package to be alphabetically
-    // preceeding the main package name.
-    // This is needed to make Bash script batch installing/uninstalling packages
-    // produced by jtreg tests install/uninstall packages in the right order.
-    static class APackageDepsTestPrereq {
-
-        public static void main(String[] args) {
-            new PackageTest().forTypes(PackageType.LINUX).configureHelloApp().run();
-        }
-    }
-
     public static void main(String[] args) {
+        // Pick the name of prerequisite package to be alphabetically
+        // preceeding the main package name.
+        // This is needed to make Bash script batch installing/uninstalling packages
+        // produced by jtreg tests install/uninstall packages in the right order.
         final String PREREQ_PACKAGE_NAME = "apackagedepstestprereq";
 
-        Test.run(args, () -> {
-            APackageDepsTestPrereq.main(args);
+        TKit.run(args, () -> {
+            new PackageTest()
+            .forTypes(PackageType.LINUX)
+            .configureHelloApp()
+            .addInitializer(cmd -> {
+                cmd.setArgumentValue("--name", PREREQ_PACKAGE_NAME);
+            })
+            .run();
 
             new PackageTest()
             .forTypes(PackageType.LINUX)
--- a/test/jdk/tools/jpackage/linux/ReleaseTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/ReleaseTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -23,7 +23,7 @@
 
 import jdk.jpackage.test.PackageType;
 import jdk.jpackage.test.PackageTest;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 
 
 /**
@@ -43,6 +43,7 @@
  * @test
  * @summary jpackage with --linux-app-release
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m ReleaseTest
@@ -52,7 +53,7 @@
     public static void main(String[] args) {
         final String RELEASE = "Rc3";
 
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.LINUX)
             .configureHelloApp()
@@ -63,7 +64,7 @@
             .addBundlePropertyVerifier("Release", RELEASE)
             .forTypes(PackageType.LINUX_DEB)
             .addBundlePropertyVerifier("Version", (propName, propValue) -> {
-                Test.assertTrue(propValue.endsWith("-" + RELEASE),
+                TKit.assertTrue(propValue.endsWith("-" + RELEASE),
                         String.format("Check value of %s property [%s] ends with %s",
                                 propName, propValue, RELEASE));
             })
--- a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -26,7 +26,8 @@
 import jdk.jpackage.test.FileAssociations;
 import jdk.jpackage.test.PackageType;
 import jdk.jpackage.test.PackageTest;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Annotations.Test;
 
 /**
  * Test --linux-shortcut parameter. Output of the test should be
@@ -48,50 +49,39 @@
  * @test
  * @summary jpackage with --linux-shortcut
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "linux")
  * @modules jdk.jpackage/jdk.jpackage.internal
- * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest
- * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testCustomIcon
- * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testFileAssociations
- * @run main/othervm/timeout=360 -Xmx512m ShortcutHintTest testAdditionaltLaunchers
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=ShortcutHintTest
  */
 public class ShortcutHintTest {
 
-    public static void main(String[] args) {
-        Test.run(args, () -> {
-            if (args.length != 0) {
-                Test.getTestClass().getDeclaredMethod(args[0]).invoke(null);
-                return;
-            }
-
-            createTest(null).addInitializer(cmd -> {
-                cmd.addArgument("--linux-shortcut");
-            }).run();
-        });
+    @Test
+    public static void testBasic() {
+        createTest().addInitializer(cmd -> {
+            cmd.addArgument("--linux-shortcut");
+        }).run();
     }
 
-    private static PackageTest createTest(String name) {
-        PackageTest reply = new PackageTest()
+    private static PackageTest createTest() {
+        return new PackageTest()
                 .forTypes(PackageType.LINUX)
                 .configureHelloApp()
                 .addBundleDesktopIntegrationVerifier(true);
-        if (name != null) {
-            reply.addInitializer(cmd -> cmd.setArgumentValue("--name",
-                    String.format("%s8%s", Test.getTestClass().getSimpleName(),
-                            name)));
-        }
-        return reply;
+
     }
 
     /**
      * Adding `--icon` to jpackage command line should create desktop shortcut
      * even though `--linux-shortcut` is omitted.
      */
-    static void testCustomIcon() {
-        createTest(new Object() {
-        }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> {
+    @Test
+    public static void testCustomIcon() {
+        createTest().addInitializer(cmd -> {
             cmd.setFakeRuntime();
-            cmd.addArguments("--icon", Test.TEST_SRC_ROOT.resolve(
+            cmd.addArguments("--icon", TKit.TEST_SRC_ROOT.resolve(
                     "apps/dukeplug.png"));
         }).run();
     }
@@ -100,9 +90,9 @@
      * Adding `--file-associations` to jpackage command line should create
      * desktop shortcut even though `--linux-shortcut` is omitted.
      */
-    static void testFileAssociations() {
-        createTest(new Object() {
-        }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> {
+    @Test
+    public static void testFileAssociations() {
+        createTest().addInitializer(cmd -> {
             cmd.setFakeRuntime();
 
             FileAssociations fa = new FileAssociations(
@@ -116,20 +106,20 @@
      * Additional launcher with icon should create desktop shortcut even though
      * `--linux-shortcut` is omitted.
      */
-    static void testAdditionaltLaunchers() {
-        createTest(new Object() {
-        }.getClass().getEnclosingMethod().getName()).addInitializer(cmd -> {
+    @Test
+    public static void testAdditionaltLaunchers() {
+        createTest().addInitializer(cmd -> {
             cmd.setFakeRuntime();
 
             final String launcherName = "Foo";
-            final Path propsFile = Test.workDir().resolve(
+            final Path propsFile = TKit.workDir().resolve(
                     launcherName + ".properties");
 
             cmd.addArguments("--add-launcher", String.format("%s=%s",
                     launcherName, propsFile));
 
-            Test.createPropertiesFile(propsFile, Map.entry("icon",
-                    Test.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString()));
+            TKit.createPropertiesFile(propsFile, Map.entry("icon",
+                    TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString()));
         }).run();
     }
 }
--- a/test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -31,7 +31,7 @@
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 import jdk.jpackage.test.FileAssociations;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 
 /**
  * Test --add-launcher parameter. Output of the test should be
@@ -45,13 +45,14 @@
  * @test
  * @summary jpackage with --add-launcher
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m AdditionalLaunchersTest
  */
 public class AdditionalLaunchersTest {
 
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             FileAssociations fa = new FileAssociations(
                     MethodHandles.lookup().lookupClass().getSimpleName());
 
@@ -75,7 +76,7 @@
             AdditionalLauncher barLauncher = new AdditionalLauncher("Bar").setArguments(
                     "one", "two", "three");
             packageTest.forTypes(PackageType.LINUX).addInitializer(cmd -> {
-                barLauncher.setIcon(Test.TEST_SRC_ROOT.resolve("apps/dukeplug.png"));
+                barLauncher.setIcon(TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png"));
             });
             barLauncher.applyTo(packageTest);
 
@@ -109,7 +110,7 @@
         }
 
         void applyTo(PackageTest test) {
-            final Path propsFile = Test.workDir().resolve(name + ".properties");
+            final Path propsFile = TKit.workDir().resolve(name + ".properties");
 
             test.addInitializer(cmd -> {
                 cmd.addArguments("--add-launcher", String.format("%s=%s", name,
@@ -125,13 +126,13 @@
                     properties.put("icon", icon.toAbsolutePath().toString());
                 }
 
-                Test.createPropertiesFile(propsFile, properties);
+                TKit.createPropertiesFile(propsFile, properties);
             });
             test.addInstallVerifier(cmd -> {
                 Path launcherPath = replaceFileName(
                         cmd.launcherInstallationPath(), name);
 
-                Test.assertExecutableFileExists(launcherPath, true);
+                TKit.assertExecutableFileExists(launcherPath);
 
                 if (cmd.isFakeRuntimeInstalled(String.format(
                         "Not running %s launcher", launcherPath))) {
@@ -145,7 +146,7 @@
                 Path launcherPath = replaceFileName(
                         cmd.launcherInstallationPath(), name);
 
-                Test.assertExecutableFileExists(launcherPath, false);
+                TKit.assertPathExists(launcherPath, false);
             });
         }
 
--- a/test/jdk/tools/jpackage/share/AppImagePackageTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -22,7 +22,7 @@
  */
 
 import java.nio.file.Path;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 import jdk.jpackage.test.JPackageCommand;
@@ -37,13 +37,14 @@
  * @test
  * @summary jpackage with --app-image
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m AppImagePackageTest
  */
 public class AppImagePackageTest {
 
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             Path appimageOutput = Path.of("appimage");
 
             JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
@@ -64,7 +65,7 @@
                 }
 
                 cmd.addArguments("--app-image", appimageInput);
-                cmd.removeArgument("--input");
+                cmd.removeArgumentWithValue("--input");
             }).addBundleDesktopIntegrationVerifier(false).run();
         });
     }
--- a/test/jdk/tools/jpackage/share/ArgumentsBase.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.File;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ArgumentsBase {
-
-    private static final String app = JPackagePath.getApp();
-    private static final String appOutput = JPackagePath.getAppOutputFile();
-
-    private static final String ARGUMENT1 = "argument";
-    private static final String ARGUMENT2 = "Some Arguments";
-    private static final String ARGUMENT3 = "Value \"with\" quotes";
-
-    private static final String ARGUMENT_CMD1 = "test";
-
-    private static final List<String> arguments = new ArrayList<>();
-    private static final List<String> argumentsCmd = new ArrayList<>();
-
-    public static void initArguments(boolean toolProvider, String[] cmd) {
-        if (arguments.isEmpty()) {
-            arguments.add(ARGUMENT1);
-            arguments.add(ARGUMENT2);
-            arguments.add(ARGUMENT3);
-        }
-
-        if (argumentsCmd.isEmpty()) {
-            argumentsCmd.add(ARGUMENT_CMD1);
-        }
-
-        String argumentsMap
-                = JPackageHelper.listToArgumentsMap(arguments, toolProvider);
-        cmd[cmd.length - 1] = argumentsMap;
-    }
-
-    private static void validateResult(String[] result, List<String> args)
-            throws Exception {
-        if (result.length != (args.size() + 2)) {
-            throw new AssertionError(
-                    "Unexpected number of lines: " + result.length);
-        }
-
-        if (!result[0].trim().equals("jpackage test application")) {
-            throw new AssertionError("Unexpected result[0]: " + result[0]);
-        }
-
-        if (!result[1].trim().equals("args.length: " + args.size())) {
-            throw new AssertionError("Unexpected result[1]: " + result[1]);
-        }
-
-        int index = 2;
-        for (String arg : args) {
-            if (!result[index].trim().equals(arg)) {
-                throw new AssertionError(
-                        "Unexpected result[" + index + "]: " + result[index]);
-            }
-            index++;
-        }
-    }
-
-    private static void validate(String arg, List<String> expectedArgs)
-            throws Exception {
-        int retVal;
-
-        if (arg == null) {
-            retVal = JPackageHelper.execute(null, app);
-        } else {
-            retVal = JPackageHelper.execute(null, app, arg);
-        }
-        if (retVal != 0) {
-            throw new AssertionError("Test application exited with error: "
-                    + retVal);
-        }
-
-        File outfile = new File(appOutput);
-        if (!outfile.exists()) {
-            throw new AssertionError(appOutput + " was not created");
-        }
-
-        String output = Files.readString(outfile.toPath());
-        String[] result = output.split("\n");
-        validateResult(result, expectedArgs);
-    }
-
-    public static void testCreateAppImage(String[] cmd) throws Exception {
-        initArguments(false, cmd);
-        JPackageHelper.executeCLI(true, cmd);
-        validate(null, arguments);
-        validate(ARGUMENT_CMD1, argumentsCmd);
-    }
-
-    public static void testCreateAppImageToolProvider(String[] cmd) throws Exception {
-        initArguments(true, cmd);
-        JPackageHelper.executeToolProvider(true, cmd);
-        validate(null, arguments);
-        validate(ARGUMENT_CMD1, argumentsCmd);
-    }
-}
--- a/test/jdk/tools/jpackage/share/ArgumentsModuleTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test
- * @summary jpackage create image with --arguments test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build ArgumentsBase
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m ArgumentsModuleTest
- */
-public class ArgumentsModuleTest {
-    private static final String OUTPUT = "output";
-
-    private static final String[] CMD = {
-        "--package-type", "app-image",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--module", "com.hello/com.hello.Hello",
-        "--module-path", "input",
-        "--arguments", "TBD"};
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloModule();
-        ArgumentsBase.testCreateAppImage(CMD);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        ArgumentsBase.testCreateAppImageToolProvider(CMD);
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/ArgumentsTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/ArgumentsTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,33 +21,68 @@
  * questions.
  */
 
+import java.nio.file.Path;
+import java.util.List;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.*;
+
+
+/*
+ * Tricky arguments used in the test require a bunch of levels of character
+ * escaping for proper encoding them in a single string to be used as a value of
+ * `--arguments` option. String with encoded arguments doesn't go through the
+ * system to jpackage executable as is because OS is interpreting escape
+ * characters. This is true for Windows at least.
+ *
+ * String mapping performed by the system corrupts the string and jpackage exits
+ * with error. There is no problem with string corruption when jpackage is used
+ * as tool provider. This is not jpackage issue, so just always run this test
+ * with jpackage used as tool provider.
+ * /
+
 /*
  * @test
  * @summary jpackage create image with --arguments test
  * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build ArgumentsBase
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage
- * @run main/othervm -Xmx512m ArgumentsTest
+ * @compile ArgumentsTest.java
+ * @run main/othervm -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=ArgumentsTest
+ *  --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
  */
 public class ArgumentsTest {
-    private static final String OUTPUT = "output";
-
-    private static final String[] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-        "--arguments", "TBD"};
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        ArgumentsBase.testCreateAppImage(CMD);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        ArgumentsBase.testCreateAppImageToolProvider(CMD);
+    @Test
+    @Parameter("Goodbye")
+    @Parameter("com.hello/com.hello.Hello")
+    public static void testApp(String javaAppDesc) {
+        testIt(javaAppDesc, null);
     }
 
+    private static void testIt(String javaAppDesc,
+            ThrowingConsumer<JPackageCommand> initializer) {
+
+        JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc).addArguments(
+                "--arguments", JPackageCommand.escapeAndJoin(TRICKY_ARGUMENTS));
+        if (initializer != null) {
+            ThrowingConsumer.toConsumer(initializer).accept(cmd);
+        }
+
+        cmd.executeAndAssertImageCreated();
+
+        Path launcherPath = cmd.appImage().resolve(cmd.launcherPathInAppImage());
+        if (!cmd.isFakeRuntimeInAppImage(String.format(
+                "Not running [%s] launcher", launcherPath))) {
+            HelloApp.executeAndVerifyOutput(launcherPath, TRICKY_ARGUMENTS);
+        }
+    }
+
+    private final static List<String> TRICKY_ARGUMENTS = List.of(
+        "argument",
+        "Some Arguments",
+        "Value \"with\" quotes"
+    );
 }
--- a/test/jdk/tools/jpackage/share/AtFilenameTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build Base
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m AtFilenameTest
- */
-public class AtFilenameTest {
-    private static final String OUTPUT = "output";
-
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-
-        doTest(0, 1);  // replace just the mode
-        doTest(0, 11); // replace everything
-        doTest(1, 10); // replace everything except mode
-        doTest(4, 2);  // replace the name and --main-jar without jar name
-
-    }
-
-    private static void doTest(int index, int len) throws Exception {
-        String[] cmdWithAtFilename =
-                JPackageHelper.cmdWithAtFilename(CMD, index, len);
-
-        Base.testCreateAppImageToolProvider(cmdWithAtFilename);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/FileAssociationsTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/FileAssociationsTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.FileAssociations;
 
@@ -47,12 +47,13 @@
  * @test
  * @summary jpackage with --file-associations
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m FileAssociationsTest
  */
 public class FileAssociationsTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             PackageTest packageTest = new PackageTest();
 
             applyFileAssociations(packageTest, new FileAssociations("jptest1"));
--- a/test/jdk/tools/jpackage/share/IconTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/IconTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,105 +21,56 @@
  * questions.
  */
 
-import java.io.File;
 import java.nio.file.Files;
+import jdk.jpackage.internal.ApplicationLayout;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.JPackageCommand;
 
 /*
  * @test
  * @summary jpackage create image to verify --icon
  * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @modules jdk.jpackage
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm -Xmx512m IconTest
  */
 public class IconTest {
-    private static final String OUTPUT = "output";
-    private static final String app = JPackagePath.getApp();
-    private static final String appOutput = JPackagePath.getAppOutputFile();
-
-    private static final String[] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-        "--icon", getIconPath(),
-        "--dest", OUTPUT};
-
-    private static void validateResult(String[] result) throws Exception {
-        if (result.length != 2) {
-            throw new AssertionError(
-                   "Unexpected number of lines: " + result.length);
-        }
+    public static void main(String[] args) {
+        TKit.run(args, () -> {
+            JPackageCommand cmd = JPackageCommand.helloAppImage().addArguments("--icon", GOLDEN_ICON);
+            cmd.useToolProvider(true).executeAndAssertHelloAppImageCreated();
 
-        if (!result[0].trim().equals("jpackage test application")) {
-            throw new AssertionError("Unexpected result[0]: " + result[0]);
-        }
-
-        if (!result[1].trim().equals("args.length: 0")) {
-            throw new AssertionError("Unexpected result[1]: " + result[1]);
-        }
-    }
+            Path iconPath = ApplicationLayout.platformAppImage().resolveAt(
+                    cmd.appImage()).destktopIntegrationDirectory().resolve(
+                    cmd.launcherPathInAppImage().getFileName().toString().replaceAll(
+                            "\\.[^.]*$", "") + ICON_SUFFIX);
 
-    private static void validate() throws Exception {
-        int retVal = JPackageHelper.execute(null, app);
-        if (retVal != 0) {
-            throw new AssertionError(
-                   "Test application exited with error: " + retVal);
-        }
-
-        File outfile = new File(appOutput);
-        if (!outfile.exists()) {
-            throw new AssertionError(appOutput + " was not created");
-        }
-
-        String output = Files.readString(outfile.toPath());
-        String[] result = output.split("\n");
-        validateResult(result);
+            TKit.assertFileExists(iconPath);
+            TKit.assertTrue(-1 == Files.mismatch(GOLDEN_ICON, iconPath),
+                    String.format(
+                            "Check application icon file [%s] is a copy of source icon file [%s]",
+                            iconPath, GOLDEN_ICON));
+        });
     }
 
-    private static void validateIcon() throws Exception {
-        File origIcon = new File(getIconPath());
-        File icon = new File(JPackagePath.getAppIcon());
-        if (origIcon.length() != icon.length()) {
-            System.err.println("origIcon.length(): " + origIcon.length());
-            System.err.println("icon.length(): " + icon.length());
-            throw new AssertionError("Icons size does not match");
+    private final static String ICON_SUFFIX = Functional.identity(() -> {
+        if (TKit.isOSX()) {
+            return ".icns";
         }
-    }
 
-    private static void testIcon() throws Exception {
-        JPackageHelper.executeCLI(true, CMD);
-        validate();
-        validateIcon();
-    }
-
-    private static void testIconToolProvider() throws Exception {
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        JPackageHelper.executeToolProvider(true, CMD);
-        validate();
-        validateIcon();
-    }
-
-    private static String getIconPath() {
-        String ext = ".ico";
-        if (JPackageHelper.isOSX()) {
-            ext = ".icns";
-        } else if (JPackageHelper.isLinux()) {
-            ext = ".png";
+        if (TKit.isLinux()) {
+            return ".png";
         }
 
-        String path = JPackagePath.getTestSrcRoot() + File.separator + "resources"
-                + File.separator + "icon" + ext;
-
-        return path;
-    }
+        if (TKit.isWindows()) {
+            return ".ico";
+        }
 
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        testIcon();
-        testIconToolProvider();
-    }
+        throw TKit.throwUnknownPlatformError();
+    }).get();
 
+    private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
+            "resources", "icon" + ICON_SUFFIX));
 }
--- a/test/jdk/tools/jpackage/share/InstallDirTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/InstallDirTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -25,9 +25,10 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Functional;
 
 /**
  * Test --install-dir parameter. Output of the test should be installdirtest*.*
@@ -53,32 +54,28 @@
  * @test
  * @summary jpackage with --install-dir
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m InstallDirTest
  */
 public class InstallDirTest {
 
     public static void main(String[] args) {
-        final Map<PackageType, Path> INSTALL_DIRS = new Supplier<Map<PackageType, Path>>() {
-            @Override
-            public Map<PackageType, Path> get() {
-                Map<PackageType, Path> reply = new HashMap<>();
-                reply.put(PackageType.WIN_MSI, Path.of(
-                        "TestVendor\\InstallDirTest1234"));
-                reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI));
+        final Map<PackageType, Path> INSTALL_DIRS = Functional.identity(() -> {
+            Map<PackageType, Path> reply = new HashMap<>();
+            reply.put(PackageType.WIN_MSI, Path.of("TestVendor\\InstallDirTest1234"));
+            reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI));
+
+            reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage"));
+            reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB));
 
-                reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage"));
-                reply.put(PackageType.LINUX_RPM,
-                        reply.get(PackageType.LINUX_DEB));
+            reply.put(PackageType.MAC_PKG, Path.of("/Application/jpackage"));
+            reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG));
 
-                reply.put(PackageType.MAC_PKG, Path.of("/Application/jpackage"));
-                reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG));
+            return reply;
+        }).get();
 
-                return reply;
-            }
-        }.get();
-
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest().configureHelloApp()
             .addInitializer(cmd -> {
                 cmd.addArguments("--install-dir", INSTALL_DIRS.get(
--- a/test/jdk/tools/jpackage/share/JLinkModuleTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.util.ArrayList;
-
- /*
-  * @test
-  * @summary jpackage create image modular jar test
-  * @library ../helpers
-  * @build JPackageHelper
-  * @build JPackagePath
-  * @build Base
-  * @modules jdk.jpackage
-  * @run main/othervm -Xmx512m JLinkModuleTest
-  */
-public class JLinkModuleTest {
-    private static final String OUTPUT = "output";
-    private static final String RUNTIME = "runtime";
-
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--module", "com.other/com.other.Other",
-        "--runtime-image", RUNTIME,
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createOtherModule();
-
-        ArrayList<String> jlargs = new ArrayList<>();
-        jlargs.add("--add-modules");
-        jlargs.add("com.other");
-        jlargs.add("--module-path");
-        jlargs.add("module");
-        jlargs.add("--strip-debug");
-        jlargs.add("--no-header-files");
-        jlargs.add("--no-man-pages");
-        jlargs.add("--strip-native-commands");
-        JPackageHelper.createRuntime(jlargs);
-
-
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImage(CMD);
-
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/LicenseTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/LicenseTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -34,7 +34,7 @@
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.LinuxHelper;
 import jdk.jpackage.test.Executor;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 
 /**
  * Test --license-file parameter. Output of the test should be licensetest*.*
@@ -64,8 +64,11 @@
  * @test
  * @summary jpackage with --license-file
  * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @compile LicenseTest.java
  * @modules jdk.jpackage/jdk.jpackage.internal
- * @run main/othervm/timeout=360 -Xmx512m LicenseTest testCommon
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=LicenseTest.testCommon
  */
 
 /*
@@ -73,20 +76,14 @@
  * @summary jpackage with --license-file
  * @library ../helpers
  * @modules jdk.jpackage/jdk.jpackage.internal
+ * @compile LicenseTest.java
  * @requires (os.family == "linux")
- * @run main/othervm/timeout=360 -Xmx512m LicenseTest testCustomDebianCopyright
- * @run main/othervm/timeout=360 -Xmx512m LicenseTest testCustomDebianCopyrightSubst
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=LicenseTest.testCustomDebianCopyright
+ *  --jpt-run=LicenseTest.testCustomDebianCopyrightSubst
  */
 
 public class LicenseTest {
-    public static void main(String[] args) {
-        Test.run(args, () -> {
-            String testFuncName = args[0];
-            Test.trace(String.format("Running %s...", testFuncName));
-            Test.getTestClass().getDeclaredMethod(testFuncName).invoke(null);
-        });
-    }
-
     public static void testCommon() {
         new PackageTest().configureHelloApp()
         .addInitializer(cmd -> {
@@ -97,7 +94,7 @@
             verifyLicenseFileInLinuxPackage(cmd, linuxLicenseFile(cmd));
         })
         .addInstallVerifier(cmd -> {
-            Test.assertReadableFileExists(linuxLicenseFile(cmd));
+            TKit.assertReadableFileExists(linuxLicenseFile(cmd));
         })
         .addUninstallVerifier(cmd -> {
             verifyLicenseFileNotInstalledLinux(linuxLicenseFile(cmd));
@@ -153,7 +150,7 @@
 
     private static void verifyLicenseFileInLinuxPackage(JPackageCommand cmd,
             Path expectedLicensePath) {
-        Test.assertTrue(LinuxHelper.getPackageFiles(cmd).filter(path -> path.equals(
+        TKit.assertTrue(LinuxHelper.getPackageFiles(cmd).filter(path -> path.equals(
                 expectedLicensePath)).findFirst().orElse(null) != null,
                 String.format("Check license file [%s] is in %s package",
                         expectedLicensePath, LinuxHelper.getPackageName(cmd)));
@@ -161,7 +158,7 @@
 
     private static void verifyLicenseFileInstalledRpm(Path licenseFile) throws
             IOException {
-        Test.assertStringListEquals(Files.readAllLines(LICENSE_FILE),
+        TKit.assertStringListEquals(Files.readAllLines(LICENSE_FILE),
                 Files.readAllLines(licenseFile), String.format(
                 "Check contents of package license file [%s] are the same as contents of source license file [%s]",
                 licenseFile, LICENSE_FILE));
@@ -178,17 +175,17 @@
 
         actualLines = DEBIAN_COPYRIGT_FILE_STRIPPER.apply(actualLines);
 
-        Test.assertNotEquals(0, String.join("\n", actualLines).length(),
+        TKit.assertNotEquals(0, String.join("\n", actualLines).length(),
                 "Check stripped license text is not empty");
 
-        Test.assertStringListEquals(DEBIAN_COPYRIGT_FILE_STRIPPER.apply(
+        TKit.assertStringListEquals(DEBIAN_COPYRIGT_FILE_STRIPPER.apply(
                 Files.readAllLines(LICENSE_FILE)), actualLines, String.format(
                 "Check subset of package license file [%s] is a match of the source license file [%s]",
                 licenseFile, LICENSE_FILE));
     }
 
     private static void verifyLicenseFileNotInstalledLinux(Path licenseFile) {
-        Test.assertPathExists(licenseFile.getParent(), false);
+        TKit.assertPathExists(licenseFile.getParent(), false);
     }
 
     private static class CustomDebianCopyrightTest {
@@ -233,7 +230,7 @@
         }
 
         void run() {
-            final Path srcLicenseFile = Test.workDir().resolve("license");
+            final Path srcLicenseFile = TKit.workDir().resolve("license");
             new PackageTest().configureHelloApp().forTypes(PackageType.LINUX_DEB)
             .addInitializer(cmd -> {
                 // Create source license file.
@@ -255,7 +252,7 @@
             })
             .addInstallVerifier(cmd -> {
                 Path installedLicenseFile = debLicenseFile(cmd);
-                Test.assertStringListEquals(expetedLicenseFileText(),
+                TKit.assertStringListEquals(expetedLicenseFileText(),
                         DEBIAN_COPYRIGT_FILE_STRIPPER.apply(Files.readAllLines(
                                 installedLicenseFile)), String.format(
                                 "Check contents of package license file [%s] are the same as contents of source license file [%s]",
@@ -268,10 +265,10 @@
         private String copyright;
         private String licenseText;
 
-        private final Path RESOURCE_DIR = Test.workDir().resolve("resources");
+        private final Path RESOURCE_DIR = TKit.workDir().resolve("resources");
     }
 
-    private static final Path LICENSE_FILE = Test.TEST_SRC_ROOT.resolve(
+    private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(
             Path.of("resources", "license.txt"));
 
     private static final Function<List<String>, List<String>> DEBIAN_COPYRIGT_FILE_STRIPPER = (lines) -> Arrays.asList(
--- a/test/jdk/tools/jpackage/share/ModularJarTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image modular jar test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build Base
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m ModularJarTest
- */
-public class ModularJarTest {
-    private static final String OUTPUT = "output";
-
-    private static final String [] CMD1 = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "com.hello.jar",
-        "--main-class", "com.hello.Hello",
-    };
-
-    private static final String [] CMD2 = {
-        "--package-type", "app-image",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--module", "com.hello/com.hello.Hello",
-        "--module-path", "input/com.hello.jar",
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloModule();
-
-        Base.testCreateAppImage(CMD1);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImageToolProvider(CMD1);
-
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImage(CMD2);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImageToolProvider(CMD2);
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/ModuleTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image modular jar test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build Base
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m ModuleTest
- */
-public class ModuleTest {
-    private static final String OUTPUT = "output";
-
-    private static final String [] CMD1 = {
-        "--package-type", "app-image",
-        "--module-path", "module",
-        "--module", "com.other/com.other.Other",
-        "--dest", OUTPUT,
-        "--name", "test",
-    };
-
-    private static String [] commands = {
-        "--package-type", "app-image",
-        "--module-path", "module",
-        "--module", "com.other/com.other.Other",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--add-modules", "TBD",
-    };
-
-    private final static String [] paths = {
-        "ALL-MODULES",
-        "ALL_MODULE_PATH",
-        "ALL-SYSTEM",
-        "ALL-DEFAULT",
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createOtherModule();
-
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImage(CMD1);
-
-        for (String path : paths) {
-            commands[commands.length - 1] = path;
-            System.out.println("using --add-modules " + path);
-            JPackageHelper.deleteOutputFolder(OUTPUT);
-            Base.testCreateAppImageToolProvider(commands);
-            System.out.println("succeeded");
-        }
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/NoArgTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage no argument test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m NoArgTest
- */
-public class NoArgTest {
-
-    private static final String RESULT1 = "Usage: jpackage <mode> <options>";
-    private static final String[] EXPECTED =
-            {"--help", "list of possible options"};
-
-    private static void validate(String output) throws Exception {
-        String[] result = JPackageHelper.splitAndFilter(output);
-        if (result.length != 2) {
-            System.err.println(output);
-            throw new AssertionError(
-                    "Invalid number of lines in output: " + result.length);
-        }
-
-        if (!result[0].trim().equals(RESULT1)) {
-            System.err.println("Expected: " + RESULT1);
-            System.err.println("Actual: " + result[0]);
-            throw new AssertionError("Unexpected line 1");
-        }
-
-        for (String expected : EXPECTED) {
-            if (!result[1].contains(expected)) {
-                System.err.println("Expected to contain: " + expected);
-                System.err.println("Actual: " + result[1]);
-                throw new AssertionError("Unexpected line 2");
-            }
-        }
-    }
-
-    private static void testNoArg() throws Exception {
-        String output = JPackageHelper.executeCLI(true, new String[0]);
-        validate(output);
-    }
-
-    private static void testNoArgToolProvider() throws Exception {
-        String output =
-                JPackageHelper.executeToolProvider(true, new String[0]);
-        validate(output);
-    }
-
-    public static void main(String[] args) throws Exception {
-        testNoArg();
-        testNoArgToolProvider();
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/NoNameTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.File;
-import java.nio.file.Files;
-
-/*
- * @test
- * @summary jpackage create image with no --name
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m NoNameTest
- */
-public class NoNameTest {
-    private static final String OUTPUT = "output";
-    private static final String app = JPackagePath.getApp("Hello");
-    private static final String appOutput = JPackagePath.getAppOutputFile();
-
-    private static final String[] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-    };
-
-    private static void validateResult(String[] result) throws Exception {
-        if (result.length != 2) {
-            throw new AssertionError(
-                   "Unexpected number of lines: " + result.length);
-        }
-
-        if (!result[0].trim().equals("jpackage test application")) {
-            throw new AssertionError("Unexpected result[0]: " + result[0]);
-        }
-
-        if (!result[1].trim().equals("args.length: 0")) {
-            throw new AssertionError("Unexpected result[1]: " + result[1]);
-        }
-    }
-
-    private static void validate() throws Exception {
-        int retVal = JPackageHelper.execute(null, app);
-        if (retVal != 0) {
-            throw new AssertionError(
-                   "Test application exited with error: " + retVal);
-        }
-
-        File outfile = new File(appOutput);
-        if (!outfile.exists()) {
-            throw new AssertionError(appOutput + " was not created");
-        }
-
-        String output = Files.readString(outfile.toPath());
-        String[] result = output.split("\n");
-        validateResult(result);
-    }
-
-    private static void testMainClassAttribute() throws Exception {
-        JPackageHelper.executeCLI(true, CMD);
-        validate();
-    }
-
-    private static void testMainClassAttributeToolProvider() throws Exception {
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        JPackageHelper.executeToolProvider(true, CMD);
-        validate();
-    }
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        testMainClassAttribute();
-        testMainClassAttributeToolProvider();
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/RuntimeBase.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.File;
-import java.nio.file.Files;
-
- public class RuntimeBase {
-    private static final String app = JPackagePath.getApp();
-    private static final String runtimeJava = JPackagePath.getRuntimeJava();
-    private static final String runtimeJavaOutput = "javaOutput.txt";
-    private static final String appOutput = JPackagePath.getAppOutputFile();
-
-    private static void validateResult(String[] result) throws Exception {
-        if (result.length != 2) {
-            throw new AssertionError("Unexpected number of lines: " + result.length);
-        }
-
-        if (!result[0].trim().equals("jpackage test application")) {
-            throw new AssertionError("Unexpected result[0]: " + result[0]);
-        }
-
-        if (!result[1].trim().equals("args.length: 0")) {
-            throw new AssertionError("Unexpected result[1]: " + result[1]);
-        }
-    }
-
-    private static void validate() throws Exception {
-        int retVal = JPackageHelper.execute(null, app);
-        if (retVal != 0) {
-            throw new AssertionError("Test application exited with error: " + retVal);
-        }
-
-        File outfile = new File(appOutput);
-        if (!outfile.exists()) {
-            throw new AssertionError(appOutput + " was not created");
-        }
-
-        String output = Files.readString(outfile.toPath());
-        String[] result = JPackageHelper.splitAndFilter(output);
-        validateResult(result);
-    }
-
-    private static void validateRuntime() throws Exception {
-        int retVal = JPackageHelper.execute(new File(runtimeJavaOutput), runtimeJava, "--list-modules");
-        if (retVal != 0) {
-            throw new AssertionError("Test application exited with error: " + retVal);
-        }
-
-        File outfile = new File(runtimeJavaOutput);
-        if (!outfile.exists()) {
-            throw new AssertionError(runtimeJavaOutput + " was not created");
-        }
-
-        String output = Files.readString(outfile.toPath());
-        String[] result = JPackageHelper.splitAndFilter(output);
-        if (result.length != 1) {
-            throw new AssertionError("Unexpected number of lines: " + result.length);
-        }
-
-        if (!result[0].startsWith("java.base")) {
-            throw new AssertionError("Unexpected result: " + result[0]);
-        }
-    }
-
-    public static void testCreateAppImage(String [] cmd) throws Exception {
-        JPackageHelper.executeCLI(true, cmd);
-        validate();
-        validateRuntime();
-    }
-
-    public static void testCreateAppImageToolProvider(String [] cmd) throws Exception {
-        JPackageHelper.executeToolProvider(true, cmd);
-        validate();
-        validateRuntime();
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/RuntimeModuleTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image runtime test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build RuntimeBase
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m RuntimeModuleTest
- */
-public class RuntimeModuleTest {
-    private static final String OUTPUT = "output";
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--runtime-image", "runtime",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--module", "com.hello/com.hello.Hello",
-        "--module-path", "input"};
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloModule();
-        JPackageHelper.createRuntime();
-        RuntimeBase.testCreateAppImage(CMD);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        RuntimeBase.testCreateAppImageToolProvider(CMD);
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -23,7 +23,7 @@
 
 import java.nio.file.Path;
 import java.util.Optional;
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.JPackageCommand;
 
@@ -44,6 +44,7 @@
  * @test
  * @summary jpackage with --runtime-image
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @comment Temporary disable for Linux and OSX until functionality implemented
  * @requires (os.family != "mac")
  * @modules jdk.jpackage/jdk.jpackage.internal
@@ -52,7 +53,7 @@
 public class RuntimePackageTest {
 
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .addInitializer(cmd -> {
                 cmd.addArguments("--runtime-image", Optional.ofNullable(
@@ -61,7 +62,7 @@
                 // Remove --input parameter from jpackage command line as we don't
                 // create input directory in the test and jpackage fails
                 // if --input references non existant directory.
-                cmd.removeArgument("--input");
+                cmd.removeArgumentWithValue("--input");
             })
             .run();
         });
--- a/test/jdk/tools/jpackage/share/RuntimeTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image runtime test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build RuntimeBase
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m RuntimeTest
- */
-public class RuntimeTest {
-    private static final String OUTPUT = "output";
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--runtime-image", "runtime",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        JPackageHelper.createRuntime();
-        RuntimeBase.testCreateAppImage(CMD);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        RuntimeBase.testCreateAppImageToolProvider(CMD);
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/SimplePackageTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/share/SimplePackageTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 
 /**
@@ -41,13 +41,14 @@
  * @test
  * @summary Simple jpackage command run
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m SimplePackageTest
  */
 public class SimplePackageTest {
 
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .configureHelloApp()
             .addBundleDesktopIntegrationVerifier(false)
--- a/test/jdk/tools/jpackage/share/TempRootTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.File;
-
- /*
- * @test
- * @requires (os.family == "windows")
- * @summary jpackage create image to test --temp
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m TempRootTest
- */
-public class TempRootTest {
-    private static final String OUTPUT = "output";
-    private static String buildRoot = null;
-    private static final String BUILD_ROOT = "buildRoot";
-    private static final String BUILD_ROOT_TB = "buildRootToolProvider";
-
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-    };
-
-    private static final String [] CMD_BUILD_ROOT = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-        "--temp", "TBD"};
-
-    private static void validate(boolean retain) throws Exception {
-        File br = new File(buildRoot);
-        if (retain) {
-            if (!br.exists()) {
-                throw new AssertionError(br.getAbsolutePath() + " does not exist");
-            }
-        } else {
-            if (br.exists()) {
-                throw new AssertionError(br.getAbsolutePath() + " exist");
-            }
-        }
-    }
-
-    private static void init(boolean toolProvider) {
-        if (toolProvider) {
-            buildRoot = BUILD_ROOT_TB;
-        } else {
-            buildRoot = BUILD_ROOT;
-        }
-
-        CMD_BUILD_ROOT[CMD_BUILD_ROOT.length - 1] = buildRoot;
-    }
-
-    private static void testTempRoot() throws Exception {
-        init(false);
-        JPackageHelper.executeCLI(true, CMD);
-        validate(false);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        JPackageHelper.executeCLI(true, CMD_BUILD_ROOT);
-        validate(true);
-    }
-
-    private static void testTempRootToolProvider() throws Exception {
-        init(true);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        JPackageHelper.executeToolProvider(true, CMD);
-        validate(false);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        JPackageHelper.executeToolProvider(true, CMD_BUILD_ROOT);
-        validate(true);
-    }
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        testTempRoot();
-        testTempRootToolProvider();
-    }
-
-}
--- a/test/jdk/tools/jpackage/share/Test.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage create image test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @build Base
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m Test
- */
-
-public class Test {
-    private static final String OUTPUT = "output";
-
-    private static final String [] CMD = {
-        "--package-type", "app-image",
-        "--input", "input",
-        "--dest", OUTPUT,
-        "--name", "test",
-        "--main-jar", "hello.jar",
-        "--main-class", "Hello",
-    };
-
-    public static void main(String[] args) throws Exception {
-        JPackageHelper.createHelloImageJar();
-        Base.testCreateAppImage(CMD);
-        JPackageHelper.deleteOutputFolder(OUTPUT);
-        Base.testCreateAppImageToolProvider(CMD);
-    }
-}
--- a/test/jdk/tools/jpackage/share/VersionTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
- /*
- * @test
- * @summary jpackage version test
- * @library ../helpers
- * @build JPackageHelper
- * @build JPackagePath
- * @modules jdk.jpackage
- * @run main/othervm -Xmx512m VersionTest
- */
-public class VersionTest {
-
-    private static final String ARG = "--version";
-    private static final String RESULT = System.getProperty("java.version");
-
-    private static void validate(String output) throws Exception {
-        String[] result = JPackageHelper.splitAndFilter(output);
-        if (result.length != 1) {
-            System.err.println(output);
-            throw new AssertionError("Invalid number of lines in output: " + result.length);
-        }
-
-        if (!result[0].trim().equals(RESULT)) {
-            System.err.println("Expected: " + RESULT);
-            System.err.println("Actual: " + result[0]);
-            throw new AssertionError("Unexpected line 1");
-        }
-    }
-
-    private static void testVersion() throws Exception {
-        String output = JPackageHelper.executeCLI(true, ARG);
-        validate(output);
-    }
-
-    private static void testVersionToolProvider() throws Exception {
-        String output = JPackageHelper.executeToolProvider(true, ARG);
-        validate(output);
-    }
-
-    public static void main(String[] args) throws Exception {
-        testVersion();
-        testVersionToolProvider();
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.tests;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.Executor;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.JavaTool;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.Annotations.*;
+
+/*
+ * @test
+ * @summary jpackage basic testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage
+ * @compile BasicTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=jdk.jpackage.tests.BasicTest
+ */
+
+public class BasicTest {
+    @Test
+    public void testNoArgs() {
+        List<String> output = JPackageCommand.filterOutput(
+                createVanillaJPackageCommand().executeAndGetOutput());
+        TKit.assertStringListEquals(List.of("Usage: jpackage <mode> <options>",
+                "Use jpackage --help (or -h) for a list of possible options"),
+                output, "Check jpackage output");
+    }
+
+    @Test
+    public void testVersion() {
+        List<String> output = JPackageCommand.filterOutput(
+                createVanillaJPackageCommand()
+                        .addArgument("--version")
+                        .executeAndGetOutput());
+        TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
+                output, "Check jpackage output");
+    }
+
+    @Test
+    public void testNoName() {
+        final String mainClassName = "Greetings";
+
+        JPackageCommand cmd = new JPackageCommand()
+                .helloAppImage(mainClassName)
+                .removeArgumentWithValue("--name");
+
+        Path expectedImageDir = cmd.outputDir().resolve(mainClassName);
+        if (TKit.isOSX()) {
+            expectedImageDir = expectedImageDir.getParent().resolve(
+                    expectedImageDir.getFileName().toString() + ".app");
+        }
+
+        cmd.executeAndAssertHelloAppImageCreated();
+        TKit.assertEquals(expectedImageDir.toAbsolutePath().normalize().toString(),
+                cmd.appImage().toAbsolutePath().normalize().toString(),
+                String.format(
+                        "Check [%s] directory is filled with application image data",
+                        expectedImageDir));
+    }
+
+    @Test
+    public void testApp() {
+        new JPackageCommand()
+        .helloAppImage()
+        .executeAndAssertHelloAppImageCreated();
+    }
+
+    @Test
+    public void testModularApp() {
+        new JPackageCommand()
+        .helloAppImage("com.other/com.other.Hello")
+        .executeAndAssertHelloAppImageCreated();
+    }
+
+    @Test
+//    @Parameter("ALL-MODULE-PATH") ; This test fails
+    @Parameter("ALL-DEFAULT")
+    public void testAddModules(String addModulesArg) {
+        JPackageCommand cmd = new JPackageCommand()
+            .helloAppImage("com.other/com.other.Hello");
+        if (!addModulesArg.isEmpty()) {
+            cmd.addArguments("--add-modules", addModulesArg);
+        }
+        cmd.executeAndAssertHelloAppImageCreated();
+    }
+
+    /**
+     * Test --temp option. Doesn't make much sense for app image as temporary
+     * directory is used only on Windows.
+     * @throws IOException
+     */
+//    @Test
+    public void testTemp() throws IOException {
+        JPackageCommand cmd = new JPackageCommand().helloAppImage();
+        TKit.withTempDirectory("temp-root", tempDir -> {
+            cmd.addArguments("--temp", tempDir);
+
+            cmd.executeAndAssertHelloAppImageCreated();
+
+            // Check jpackage actually used the supplied directory.
+            TKit.assertNotEquals(0, tempDir.toFile().list().length,
+                    String.format(
+                            "Check jpackage wrote some data in the supplied temporary directory [%s]",
+                            tempDir));
+
+            // Temporary directory should not be empty,
+            // jpackage should exit with error.
+            cmd.execute().assertExitCodeIs(1);
+        });
+    }
+
+    @Test
+    public void testAtFile() throws IOException {
+        JPackageCommand cmd = new JPackageCommand().helloAppImage();
+
+        // Init options file with the list of options configured
+        // for JPackageCommand instance.
+        final Path optionsFile = TKit.workDir().resolve("options");
+        Files.write(optionsFile,
+                List.of(String.join(" ", cmd.getAllArguments())));
+
+        // Build app jar file.
+        cmd.executePrerequisiteActions();
+
+        // Make sure output directory is empty. Normally JPackageCommand would
+        // do this automatically.
+        TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
+
+        // Instead of running jpackage command through configured
+        // JPackageCommand instance, run vanilla jpackage command with @ file.
+        createVanillaJPackageCommand()
+                .addArgument(String.format("@%s", optionsFile))
+                .execute().assertExitCodeIsZero();
+
+        // Verify output of jpackage command.
+        cmd.assertImageCreated();
+        HelloApp.executeLauncherAndVerifyOutput(cmd);
+    }
+
+    @Parameter("Hello")
+    @Parameter("com.foo/com.foo.main.Aloha")
+    @Test
+    public void testJLinkRuntime(String javaAppDesc) {
+        JPackageCommand cmd = new JPackageCommand().helloAppImage(javaAppDesc);
+
+        // If `--module` parameter was set on jpackage command line, get its
+        // value and extract module name.
+        // E.g.: foo.bar2/foo.bar.Buz -> foo.bar2
+        // Note: HelloApp class manages `--module` parameter on jpackage command line
+        final String moduleName = cmd.getArgumentValue("--module", () -> null,
+                (v) -> v.split("/", 2)[0]);
+
+        if (moduleName != null) {
+            // Build module jar.
+            cmd.executePrerequisiteActions();
+        }
+
+        TKit.withTempDirectory("runtime", runtimeDir -> {
+            TKit.deleteDirectoryRecursive(runtimeDir, String.format(
+                    "Delete [%s] output directory for jlink command", runtimeDir));
+            Executor jlink = createJavaToolCommand(JavaTool.JLINK)
+            .saveOutput(false)
+            .addArguments(
+                    "--add-modules", "java.base",
+                    "--output", runtimeDir.toString(),
+                    "--strip-debug",
+                    "--no-header-files",
+                    "--no-man-pages");
+
+            if (moduleName != null) {
+                jlink.addArguments("--add-modules", moduleName, "--module-path",
+                        Path.of(cmd.getArgumentValue("--module-path")).resolve(
+                                "hello.jar").toString());
+            }
+
+            jlink.execute().assertExitCodeIsZero();
+
+            cmd.addArguments("--runtime-image", runtimeDir);
+            cmd.executeAndAssertHelloAppImageCreated();
+
+            final Path appImageRuntimePath = cmd.appImage().resolve(
+                    cmd.appRuntimeDirectoryInAppImage());
+
+            // This is an overkill to list modules in jlink output as we have
+            // already verified that Java app is functional and thus app's runtime
+            // is likely to be OK, but let's double check.
+            List<String> moduleList = new Executor().dumpOutput().setExecutable(
+                    appImageRuntimePath.resolve(
+                            JPackageCommand.relativePathInRuntime(JavaTool.JAVA))).addArguments(
+                            "--list-modules").executeAndGetOutput().stream().sorted().collect(
+                                    Collectors.toList());
+
+            List<String> expectedModules = new ArrayList<>();
+            expectedModules.add(String.format("java.base@%s",
+                    System.getProperty("java.version")));
+            if (moduleName != null) {
+                expectedModules.add(moduleName);
+            }
+            expectedModules = expectedModules.stream().sorted().collect(
+                    Collectors.toList());
+
+            TKit.assertStringListEquals(expectedModules, moduleList,
+                    String.format(
+                            "Check modules in application image runtime directory at [%s]",
+                            appImageRuntimePath));
+        });
+    }
+
+    private static Executor createVanillaJPackageCommand() {
+        return createJavaToolCommand(JavaTool.JPACKAGE);
+    }
+
+    private static Executor createJavaToolCommand(JavaTool tool) {
+        Executor exec = new Executor().dumpOutput().saveOutput();
+        if (new JPackageCommand().isWithToolProvider()) {
+            exec.setToolProvider(tool.asToolProvider());
+        } else {
+            exec.setExecutable(tool);
+        }
+
+        return exec;
+    }
+}
--- a/test/jdk/tools/jpackage/windows/WinConsoleTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinConsoleTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -25,38 +25,40 @@
 import java.io.InputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
-
-import jdk.jpackage.internal.IOUtils;
-import jdk.jpackage.test.Test;
-import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
 
 /*
  * @test
  * @summary jpackage with --win-console
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
- * @modules jdk.jpackage/jdk.jpackage.internal
- * @run main/othervm/timeout=360 -Xmx512m WinConsoleTest
+ * @modules jdk.jpackage
+ * @compile WinConsoleTest.java
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
+ *  --jpt-run=WinConsoleTest
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinConsoleTest
  */
 public class WinConsoleTest {
 
-    public static void main(String[] args) {
-        Test.run(args, () -> {
-            JPackageCommand cmd = JPackageCommand.helloAppImage();
-            final Path launcherPath = cmd.appImage().resolve(
-                    cmd.launcherPathInAppImage());
-
-            IOUtils.deleteRecursive(cmd.outputDir().toFile());
-            cmd.execute().assertExitCodeIsZero();
-            HelloApp.executeLauncherAndVerifyOutput(cmd);
-            checkSubsystem(launcherPath, false);
-
-            IOUtils.deleteRecursive(cmd.outputDir().toFile());
-            cmd.addArgument("--win-console").execute().assertExitCodeIsZero();
-            HelloApp.executeLauncherAndVerifyOutput(cmd);
-            checkSubsystem(launcherPath, true);
-        });
+    @Test
+    @Parameter("true")
+    @Parameter("false")
+    public static void test(boolean withWinConsole) throws IOException {
+        JPackageCommand cmd = JPackageCommand.helloAppImage();
+        if (!withWinConsole) {
+            cmd.removeArgument("--win-console");
+        }
+        cmd.executeAndAssertHelloAppImageCreated();
+        checkSubsystem(cmd.appImage().resolve(cmd.launcherPathInAppImage()),
+                withWinConsole);
     }
 
     private static void checkSubsystem(Path path, boolean isConsole) throws
@@ -69,7 +71,7 @@
 
         try (InputStream inputStream = new FileInputStream(path.toString())) {
             byte[] bytes = new byte[bufferSize];
-            Test.assertEquals(bufferSize, inputStream.read(bytes),
+            TKit.assertEquals(bufferSize, inputStream.read(bytes),
                     String.format("Check %d bytes were read from %s file",
                             bufferSize, path));
 
@@ -82,7 +84,7 @@
                     // Signature, File Header and subsystem offset.
                     i = i + 4 + 20 + 68;
                     byte subsystem = bytes[i];
-                    Test.assertEquals(expectedSubsystem, subsystem,
+                    TKit.assertEquals(expectedSubsystem, subsystem,
                             String.format("Check subsystem of PE [%s] file",
                                     path));
                     return;
@@ -90,7 +92,7 @@
             }
         }
 
-        Test.assertUnexpected(String.format(
+        TKit.assertUnexpected(String.format(
                 "Subsystem not found in PE header of [%s] file", path));
     }
 }
--- a/test/jdk/tools/jpackage/windows/WinDirChooserTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinDirChooserTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -37,6 +37,7 @@
  * @test
  * @summary jpackage with --win-dir-chooser
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinDirChooserTest
@@ -44,7 +45,7 @@
 
 public class WinDirChooserTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.WINDOWS)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -39,6 +39,7 @@
  * @test
  * @summary jpackage with --win-menu and --win-menu-group
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinMenuGroupTest
@@ -46,7 +47,7 @@
 
 public class WinMenuGroupTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.WINDOWS)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/windows/WinMenuTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinMenuTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -36,6 +36,7 @@
  * @test
  * @summary jpackage with --win-menu
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinMenuTest
@@ -43,7 +44,7 @@
 
 public class WinMenuTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.WINDOWS)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -38,6 +38,7 @@
  * @test
  * @summary jpackage with --win-per-user-install, --win-menu, --win-menu-group
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinPerUserInstallTest
@@ -45,7 +46,7 @@
 
 public class WinPerUserInstallTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.WINDOWS)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/windows/WinShortcutTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinShortcutTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -37,6 +37,7 @@
  * @test
  * @summary jpackage with --win-shortcut
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinShortcutTest
@@ -44,7 +45,7 @@
 
 public class WinShortcutTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             new PackageTest()
             .forTypes(PackageType.WINDOWS)
             .configureHelloApp()
--- a/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java	Mon Sep 30 15:59:50 2019 -0400
+++ b/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java	Mon Sep 30 19:11:19 2019 -0400
@@ -21,7 +21,7 @@
  * questions.
  */
 
-import jdk.jpackage.test.Test;
+import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.PackageType;
 
@@ -40,6 +40,7 @@
  * @test
  * @summary jpackage with --win-upgrade-uuid and --app-version
  * @library ../helpers
+ * @build jdk.jpackage.test.*
  * @requires (os.family == "windows")
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @run main/othervm/timeout=360 -Xmx512m WinUpgradeUUIDTest
@@ -47,7 +48,7 @@
 
 public class WinUpgradeUUIDTest {
     public static void main(String[] args) {
-        Test.run(args, () -> {
+        TKit.run(args, () -> {
             PackageTest test = init();
             if (test.getAction() != PackageTest.Action.VERIFY_INSTALL) {
                 test.run();