8194937: Inconsistent behavior of --validate-modules when combined with -m and other options
authoralanb
Thu, 21 Jun 2018 18:56:35 +0100
changeset 50700 97e9c4f58986
parent 50699 cc7fc46cc8c1
child 50701 80fe6f64d8a0
8194937: Inconsistent behavior of --validate-modules when combined with -m and other options Reviewed-by: mchung
src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java
src/java.base/share/classes/jdk/internal/module/ModulePathValidator.java
src/java.base/share/classes/sun/launcher/LauncherHelper.java
src/java.base/share/native/libjli/java.c
test/hotspot/jtreg/compiler/jvmci/TestValidateModules.java
test/jdk/tools/launcher/modules/validate/ValidateModulesTest.java
test/jdk/tools/launcher/modules/validate/src/hello/module-info.java
test/jdk/tools/launcher/modules/validate/src/hello/p/Main.java
--- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Thu Jun 21 10:54:07 2018 -0700
+++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java	Thu Jun 21 18:56:35 2018 +0100
@@ -136,7 +136,7 @@
     /**
      * Initialize the module system, returning the boot layer.
      *
-     * @see java.lang.System#initPhase2()
+     * @see java.lang.System#initPhase2(boolean, boolean)
      */
     public static ModuleLayer boot() throws Exception {
 
@@ -213,11 +213,13 @@
         Counters.add("jdk.module.boot.2.defineBaseTime", t2);
 
 
-        // Step 2a: If --validate-modules is specified then the VM needs to
-        // start with only system modules, all other options are ignored.
+        // Step 2a: Scan all modules when --validate-modules specified
 
         if (getAndRemoveProperty("jdk.module.validation") != null) {
-            return createBootLayerForValidation();
+            int errors = ModulePathValidator.scanAllModules(System.out);
+            if (errors > 0) {
+                fail("Validation of module path failed");
+            }
         }
 
 
@@ -420,26 +422,6 @@
     }
 
     /**
-     * Create a boot module layer for validation that resolves all
-     * system modules.
-     */
-    private static ModuleLayer createBootLayerForValidation() {
-        Set<String> allSystem = ModuleFinder.ofSystem().findAll()
-            .stream()
-            .map(ModuleReference::descriptor)
-            .map(ModuleDescriptor::name)
-            .collect(Collectors.toSet());
-
-        Configuration cf = SharedSecrets.getJavaLangModuleAccess()
-            .resolveAndBind(ModuleFinder.ofSystem(),
-                            allSystem,
-                            null);
-
-        Function<String, ClassLoader> clf = ModuleLoaderMap.mappingFunction(cf);
-        return ModuleLayer.empty().defineModules(cf, clf);
-    }
-
-    /**
      * Load/register the modules to the built-in class loaders.
      */
     private static void loadModules(Configuration cf,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/jdk/internal/module/ModulePathValidator.java	Thu Jun 21 18:56:35 2018 +0100
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2018, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.internal.module;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.module.FindException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * A validator to check for errors and conflicts between modules.
+ */
+
+class ModulePathValidator {
+    private static final String MODULE_INFO = "module-info.class";
+    private static final String INDENT = "    ";
+
+    private final Map<String, ModuleReference> nameToModule;
+    private final Map<String, ModuleReference> packageToModule;
+    private final PrintStream out;
+
+    private int errorCount;
+
+    private ModulePathValidator(PrintStream out) {
+        this.nameToModule = new HashMap<>();
+        this.packageToModule = new HashMap<>();
+        this.out = out;
+    }
+
+    /**
+     * Scans and the validates all modules on the module path. The module path
+     * comprises the upgrade module path, system modules, and the application
+     * module path.
+     *
+     * @param out the print stream for output messages
+     * @return the number of errors found
+     */
+    static int scanAllModules(PrintStream out) {
+        ModulePathValidator validator = new ModulePathValidator(out);
+
+        // upgrade module path
+        String value = System.getProperty("jdk.module.upgrade.path");
+        if (value != null) {
+            Stream.of(value.split(File.pathSeparator))
+                    .map(Path::of)
+                    .forEach(validator::scan);
+        }
+
+        // system modules
+        ModuleFinder.ofSystem().findAll().stream()
+                .sorted(Comparator.comparing(ModuleReference::descriptor))
+                .forEach(validator::process);
+
+        // application module path
+        value = System.getProperty("jdk.module.path");
+        if (value != null) {
+            Stream.of(value.split(File.pathSeparator))
+                    .map(Path::of)
+                    .forEach(validator::scan);
+        }
+
+        return validator.errorCount;
+    }
+
+    /**
+     * Prints the module location and name.
+     */
+    private void printModule(ModuleReference mref) {
+        mref.location()
+                .filter(uri -> !isJrt(uri))
+                .ifPresent(uri -> out.print(uri + " "));
+        ModuleDescriptor descriptor = mref.descriptor();
+        out.print(descriptor.name());
+        if (descriptor.isAutomatic())
+            out.print(" automatic");
+        out.println();
+    }
+
+    /**
+     * Prints the module location and name, checks if the module is
+     * shadowed by a previously seen module, and finally checks for
+     * package conflicts with previously seen modules.
+     */
+    private void process(ModuleReference mref) {
+        String name = mref.descriptor().name();
+        ModuleReference previous = nameToModule.putIfAbsent(name, mref);
+        if (previous != null) {
+            printModule(mref);
+            out.print(INDENT + "shadowed by ");
+            printModule(previous);
+        } else {
+            boolean first = true;
+
+            // check for package conflicts when not shadowed
+            for (String pkg :  mref.descriptor().packages()) {
+                previous = packageToModule.putIfAbsent(pkg, mref);
+                if (previous != null) {
+                    if (first) {
+                        printModule(mref);
+                        first = false;
+                        errorCount++;
+                    }
+                    String mn = previous.descriptor().name();
+                    out.println(INDENT + "contains " + pkg
+                            + " conflicts with module " + mn);
+                }
+            }
+        }
+    }
+
+    /**
+     * Scan an element on a module path. The element is a directory
+     * of modules, an exploded module, or a JAR file.
+     */
+    private void scan(Path entry) {
+        BasicFileAttributes attrs;
+        try {
+            attrs = Files.readAttributes(entry, BasicFileAttributes.class);
+        } catch (NoSuchFileException ignore) {
+            return;
+        } catch (IOException ioe) {
+            out.println(entry + " " + ioe);
+            errorCount++;
+            return;
+        }
+
+        String fn = entry.getFileName().toString();
+        if (attrs.isRegularFile() && fn.endsWith(".jar")) {
+            // JAR file, explicit or automatic module
+            scanModule(entry).ifPresent(this::process);
+        } else if (attrs.isDirectory()) {
+            Path mi = entry.resolve(MODULE_INFO);
+            if (Files.exists(mi)) {
+                // exploded module
+                scanModule(entry).ifPresent(this::process);
+            } else {
+                // directory of modules
+                scanDirectory(entry);
+            }
+        }
+    }
+
+    /**
+     * Scan the JAR files and exploded modules in a directory.
+     */
+    private void scanDirectory(Path dir) {
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
+            Map<String, Path> moduleToEntry = new HashMap<>();
+
+            for (Path entry : stream) {
+                BasicFileAttributes attrs;
+                try {
+                    attrs = Files.readAttributes(entry, BasicFileAttributes.class);
+                } catch (IOException ioe) {
+                    out.println(entry + " " + ioe);
+                    errorCount++;
+                    continue;
+                }
+
+                ModuleReference mref = null;
+
+                String fn = entry.getFileName().toString();
+                if (attrs.isRegularFile() && fn.endsWith(".jar")) {
+                    mref = scanModule(entry).orElse(null);
+                } else if (attrs.isDirectory()) {
+                    Path mi = entry.resolve(MODULE_INFO);
+                    if (Files.exists(mi)) {
+                        mref = scanModule(entry).orElse(null);
+                    }
+                }
+
+                if (mref != null) {
+                    String name = mref.descriptor().name();
+                    Path previous = moduleToEntry.putIfAbsent(name, entry);
+                    if (previous != null) {
+                        // same name as other module in the directory
+                        printModule(mref);
+                        out.println(INDENT + "contains same module as "
+                                + previous.getFileName());
+                        errorCount++;
+                    } else {
+                        process(mref);
+                    }
+                }
+            }
+        } catch (IOException ioe) {
+            out.println(dir + " " + ioe);
+            errorCount++;
+        }
+    }
+
+    /**
+     * Scan a JAR file or exploded module.
+     */
+    private Optional<ModuleReference> scanModule(Path entry) {
+        ModuleFinder finder = ModuleFinder.of(entry);
+        try {
+            return finder.findAll().stream().findFirst();
+        } catch (FindException e) {
+            out.println(entry);
+            out.println(INDENT + e.getMessage());
+            Throwable cause = e.getCause();
+            if (cause != null) {
+                out.println(INDENT + cause);
+            }
+            errorCount++;
+            return Optional.empty();
+        }
+    }
+
+    /**
+     * Returns true if the given URI is a jrt URI
+     */
+    private static boolean isJrt(URI uri) {
+        return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));
+    }
+}
--- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Jun 21 10:54:07 2018 -0700
+++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Jun 21 18:56:35 2018 +0100
@@ -44,7 +44,6 @@
 import java.io.PrintStream;
 import java.io.UnsupportedEncodingException;
 import java.lang.module.Configuration;
-import java.lang.module.FindException;
 import java.lang.module.ModuleDescriptor;
 import java.lang.module.ModuleDescriptor.Requires;
 import java.lang.module.ModuleDescriptor.Exports;
@@ -62,21 +61,16 @@
 import java.nio.charset.Charset;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
 import java.text.Normalizer;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale.Category;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.ResourceBundle;
@@ -1212,197 +1206,4 @@
         return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));
     }
 
-    /**
-     * Called by the launcher to validate the modules on the upgrade and
-     * application module paths.
-     *
-     * @return {@code true} if no errors are found
-     */
-    private static boolean validateModules() {
-        initOutput(System.out);
-
-        ModuleValidator validator = new ModuleValidator();
-
-        // upgrade module path
-        String value = System.getProperty("jdk.module.upgrade.path");
-        if (value != null) {
-            Stream.of(value.split(File.pathSeparator))
-                    .map(Paths::get)
-                    .forEach(validator::scan);
-        }
-
-        // system modules
-        ModuleFinder.ofSystem().findAll().stream()
-                .sorted(Comparator.comparing(ModuleReference::descriptor))
-                .forEach(validator::process);
-
-        // application module path
-        value = System.getProperty("jdk.module.path");
-        if (value != null) {
-            Stream.of(value.split(File.pathSeparator))
-                    .map(Paths::get)
-                    .forEach(validator::scan);
-        }
-
-        return !validator.foundErrors();
-    }
-
-    /**
-     * A simple validator to check for errors and conflicts between modules.
-     */
-    static class ModuleValidator {
-        private static final String MODULE_INFO = "module-info.class";
-
-        private Map<String, ModuleReference> nameToModule = new HashMap<>();
-        private Map<String, ModuleReference> packageToModule = new HashMap<>();
-        private boolean errorFound;
-
-        /**
-         * Returns true if at least one error was found
-         */
-        boolean foundErrors() {
-            return errorFound;
-        }
-
-        /**
-         * Prints the module location and name.
-         */
-        private void printModule(ModuleReference mref) {
-            mref.location()
-                .filter(uri -> !isJrt(uri))
-                .ifPresent(uri -> ostream.print(uri + " "));
-            ModuleDescriptor descriptor = mref.descriptor();
-            ostream.print(descriptor.name());
-            if (descriptor.isAutomatic())
-                ostream.print(" automatic");
-            ostream.println();
-        }
-
-        /**
-         * Prints the module location and name, checks if the module is
-         * shadowed by a previously seen module, and finally checks for
-         * package conflicts with previously seen modules.
-         */
-        void process(ModuleReference mref) {
-            printModule(mref);
-
-            String name = mref.descriptor().name();
-            ModuleReference previous = nameToModule.putIfAbsent(name, mref);
-            if (previous != null) {
-                ostream.print(INDENT + "shadowed by ");
-                printModule(previous);
-            } else {
-                // check for package conflicts when not shadowed
-                for (String pkg :  mref.descriptor().packages()) {
-                    previous = packageToModule.putIfAbsent(pkg, mref);
-                    if (previous != null) {
-                        String mn = previous.descriptor().name();
-                        ostream.println(INDENT + "contains " + pkg
-                                        + " conflicts with module " + mn);
-                        errorFound = true;
-                    }
-                }
-            }
-        }
-
-        /**
-         * Scan an element on a module path. The element is a directory
-         * of modules, an exploded module, or a JAR file.
-         */
-        void scan(Path entry) {
-            BasicFileAttributes attrs;
-            try {
-                attrs = Files.readAttributes(entry, BasicFileAttributes.class);
-            } catch (NoSuchFileException ignore) {
-                return;
-            } catch (IOException ioe) {
-                ostream.println(entry + " " + ioe);
-                errorFound = true;
-                return;
-            }
-
-            String fn = entry.getFileName().toString();
-            if (attrs.isRegularFile() && fn.endsWith(".jar")) {
-                // JAR file, explicit or automatic module
-                scanModule(entry).ifPresent(this::process);
-            } else if (attrs.isDirectory()) {
-                Path mi = entry.resolve(MODULE_INFO);
-                if (Files.exists(mi)) {
-                    // exploded module
-                    scanModule(entry).ifPresent(this::process);
-                } else {
-                    // directory of modules
-                    scanDirectory(entry);
-                }
-            }
-        }
-
-        /**
-         * Scan the JAR files and exploded modules in a directory.
-         */
-        private void scanDirectory(Path dir) {
-            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
-                Map<String, Path> moduleToEntry = new HashMap<>();
-
-                for (Path entry : stream) {
-                    BasicFileAttributes attrs;
-                    try {
-                        attrs = Files.readAttributes(entry, BasicFileAttributes.class);
-                    } catch (IOException ioe) {
-                        ostream.println(entry + " " + ioe);
-                        errorFound = true;
-                        continue;
-                    }
-
-                    ModuleReference mref = null;
-
-                    String fn = entry.getFileName().toString();
-                    if (attrs.isRegularFile() && fn.endsWith(".jar")) {
-                        mref = scanModule(entry).orElse(null);
-                    } else if (attrs.isDirectory()) {
-                        Path mi = entry.resolve(MODULE_INFO);
-                        if (Files.exists(mi)) {
-                            mref = scanModule(entry).orElse(null);
-                        }
-                    }
-
-                    if (mref != null) {
-                        String name = mref.descriptor().name();
-                        Path previous = moduleToEntry.putIfAbsent(name, entry);
-                        if (previous != null) {
-                            // same name as other module in the directory
-                            printModule(mref);
-                            ostream.println(INDENT + "contains same module as "
-                                            + previous.getFileName());
-                            errorFound = true;
-                        } else {
-                            process(mref);
-                        }
-                    }
-                }
-            } catch (IOException ioe) {
-                ostream.println(dir + " " + ioe);
-                errorFound = true;
-            }
-        }
-
-        /**
-         * Scan a JAR file or exploded module.
-         */
-        private Optional<ModuleReference> scanModule(Path entry) {
-            ModuleFinder finder = ModuleFinder.of(entry);
-            try {
-                return finder.findAll().stream().findFirst();
-            } catch (FindException e) {
-                ostream.println(entry);
-                ostream.println(INDENT + e.getMessage());
-                Throwable cause = e.getCause();
-                if (cause != null) {
-                    ostream.println(INDENT + cause);
-                }
-                errorFound = true;
-                return Optional.empty();
-            }
-        }
-    }
 }
--- a/src/java.base/share/native/libjli/java.c	Thu Jun 21 10:54:07 2018 -0700
+++ b/src/java.base/share/native/libjli/java.c	Thu Jun 21 18:56:35 2018 +0100
@@ -441,14 +441,6 @@
         LEAVE();
     }
 
-    // validate modules on the module path, then exit
-    if (validateModules) {
-        jboolean okay = ValidateModules(env);
-        CHECK_EXCEPTION_LEAVE(1);
-        if (!okay) ret = 1;
-        LEAVE();
-    }
-
     if (printVersion || showVersion) {
         PrintJavaVersion(env, showVersion);
         CHECK_EXCEPTION_LEAVE(0);
@@ -457,6 +449,11 @@
         }
     }
 
+    // modules have been validated at startup so exit
+    if (validateModules) {
+        LEAVE();
+    }
+
     /* If the user specified neither a class name nor a JAR file */
     if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
         PrintUsage(env, printXUsage);
@@ -1955,20 +1952,6 @@
     (*env)->CallStaticVoidMethod(env, cls, describeModuleID, joptString);
 }
 
-/**
- * Validate modules
- */
-static jboolean
-ValidateModules(JNIEnv *env)
-{
-    jmethodID validateModulesID;
-    jclass cls = GetLauncherHelperClass(env);
-    NULL_CHECK_RETURN_VALUE(cls, JNI_FALSE);
-    validateModulesID = (*env)->GetStaticMethodID(env, cls, "validateModules", "()Z");
-    NULL_CHECK_RETURN_VALUE(cls, JNI_FALSE);
-    return (*env)->CallStaticBooleanMethod(env, cls, validateModulesID);
-}
-
 /*
  * Prints default usage or the Xusage message, see sun.launcher.LauncherHelper.java
  */
--- a/test/hotspot/jtreg/compiler/jvmci/TestValidateModules.java	Thu Jun 21 10:54:07 2018 -0700
+++ b/test/hotspot/jtreg/compiler/jvmci/TestValidateModules.java	Thu Jun 21 18:56:35 2018 +0100
@@ -37,7 +37,8 @@
     public static void main(String... args) throws Exception {
         ProcessTools.executeTestJava("-XX:+UnlockExperimentalVMOptions",
                                      "-XX:+EnableJVMCI",
-                                     "--validate-modules")
+                                     "--validate-modules",
+                                     "--list-modules")
                 .outputTo(System.out)
                 .errorTo(System.out)
                 .stdoutShouldContain("java.base")
--- a/test/jdk/tools/launcher/modules/validate/ValidateModulesTest.java	Thu Jun 21 10:54:07 2018 -0700
+++ b/test/jdk/tools/launcher/modules/validate/ValidateModulesTest.java	Thu Jun 21 18:56:35 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, 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
@@ -23,9 +23,10 @@
 
 /**
  * @test
+ * @bug 8178380 8194937
  * @modules java.xml
- * @library /lib/testlibrary
- * @build ValidateModulesTest JarUtils jdk.testlibrary.*
+ * @library src /lib/testlibrary
+ * @build ValidateModulesTest hello/* JarUtils jdk.testlibrary.*
  * @run testng ValidateModulesTest
  * @summary Basic test for java --validate-modules
  */
@@ -45,13 +46,40 @@
 public class ValidateModulesTest {
 
     /**
-     * Test that the system modules validate.
+     * Basic test --validate-modules when there are no errors.
      */
-    public void testSystemModules() throws Exception {
-        run("--validate-modules")
-                .stdoutShouldContain("java.base")
-                .stdoutShouldContain("java.xml")
-                .shouldHaveExitValue(0);
+    public void testNoErrors() throws Exception {
+        String modulePath = System.getProperty("test.module.path");
+
+        test("--validate-modules");
+
+        test("--validate-modules", "-version")
+                .shouldContain("Runtime Environment");
+
+        test("--validate-modules", "--list-modules")
+                .shouldContain("java.base");
+
+        test("--validate-modules", "-d", "java.base")
+                .shouldContain("exports java.lang");
+
+        test("-p", modulePath, "-m", "hello/p.Main")
+                .shouldContain("Hello world");
+
+        test("-p", modulePath, "--validate-modules", "-m", "hello/p.Main")
+                .shouldNotContain("Hello world");
+
+        test("-p", modulePath, "--validate-modules", "--list-modules")
+                .shouldContain("hello");
+
+        test("-p", modulePath, "--validate-modules", "-d", "hello")
+                .shouldContain("hello")
+                .shouldContain("contains p");
+
+        testExpectingError("--validate-modules", "--add-modules", "BAD")
+                .shouldContain("Module BAD not found");
+
+        testExpectingError("--validate-modules", "-m", "BAD")
+                .shouldContain("Module BAD not found");
     }
 
     /**
@@ -68,12 +96,9 @@
         Path lib = Files.createDirectory(tmpdir.resolve("lib"));
         JarUtils.createJarFile(lib.resolve("xml.jar"), classes);
 
-        int exitValue = run("-p", lib.toString(), "--validate-modules")
+        testExpectingError("-p", lib.toString(), "--validate-modules")
                 .shouldContain("xml automatic")
-                .shouldContain("conflicts with module java.xml")
-                .getExitValue();
-        assertTrue(exitValue != 0);
-
+                .shouldContain("conflicts with module java.xml");
     }
 
     /**
@@ -89,10 +114,8 @@
         JarUtils.createJarFile(lib.resolve("foo-1.0.jar"), classes);
         JarUtils.createJarFile(lib.resolve("foo-2.0.jar"), classes);
 
-        int exitValue = run("-p", lib.toString(), "--validate-modules")
-                .shouldContain("contains same module")
-                .getExitValue();
-        assertTrue(exitValue != 0);
+        testExpectingError("-p", lib.toString(), "--validate-modules")
+                .shouldContain("contains same module");
     }
 
     /**
@@ -110,18 +133,30 @@
         Path lib2 = Files.createDirectory(tmpdir.resolve("lib2"));
         JarUtils.createJarFile(lib2.resolve("foo-2.0.jar"), classes);
 
-        run("-p", lib1 + File.pathSeparator + lib2, "--validate-modules")
-                .shouldContain("shadowed by")
-                .shouldHaveExitValue(0);
+        test("-p", lib1 + File.pathSeparator + lib2, "--validate-modules")
+                .shouldContain("shadowed by");
     }
 
     /**
-     * Runs the java launcher with the given arguments.
+     * Runs the java launcher with the given arguments, expecting a 0 exit code
      */
-    private OutputAnalyzer run(String... args) throws Exception {
-        return ProcessTools.executeTestJava(args)
+    private OutputAnalyzer test(String... args) throws Exception {
+        OutputAnalyzer analyzer = ProcessTools.executeTestJava(args)
                 .outputTo(System.out)
                 .errorTo(System.out);
+        assertTrue(analyzer.getExitValue() == 0);
+        return analyzer;
+    }
+
+    /**
+     * Runs the java launcher with the given arguments, expecting a non-0 exit code
+     */
+    private OutputAnalyzer testExpectingError(String... args) throws Exception {
+        OutputAnalyzer analyzer = ProcessTools.executeTestJava(args)
+                .outputTo(System.out)
+                .errorTo(System.out);
+        assertTrue(analyzer.getExitValue() != 0);
+        return analyzer;
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/launcher/modules/validate/src/hello/module-info.java	Thu Jun 21 18:56:35 2018 +0100
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+module hello { }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/launcher/modules/validate/src/hello/p/Main.java	Thu Jun 21 18:56:35 2018 +0100
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018, 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 p;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("Hello world");
+    }
+}