1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @library ../../lib /lib/testlibrary
  27  * @modules java.base/jdk.internal.module
  28  * @build OverlappingExportedPackagesTest CompilerUtils
  29  * @build jdk.testlibrary.OutputAnalyzer jdk.testlibrary.ProcessTools
  30  * @run testng OverlappingExportedPackagesTest
  31  * @summary Test API and runtime behavior if two or more modules export
  32  *          the same package.
  33  */
  34 
  35 import java.io.OutputStream;
  36 import java.nio.file.Path;
  37 import java.nio.file.Paths;
  38 import java.nio.file.Files;
  39 import java.util.Arrays;
  40 import java.util.stream.Collectors;
  41 
  42 import java.lang.module.Configuration;
  43 import java.lang.module.ModuleDescriptor;
  44 import java.lang.module.ModuleFinder;
  45 import java.lang.module.ResolutionException;
  46 
  47 import java.lang.reflect.Layer;
  48 import java.util.Set;
  49 import java.util.regex.Pattern;
  50 import java.util.stream.Stream;
  51 
  52 import jdk.testlibrary.OutputAnalyzer;
  53 import static jdk.testlibrary.ProcessTools.*;
  54 
  55 import jdk.internal.module.ModuleInfoWriter;
  56 
  57 import org.testng.annotations.Test;
  58 import static org.testng.Assert.*;
  59 
  60 @Test
  61 public class OverlappingExportedPackagesTest {
  62 
  63     private static final String TEST_SRC = System.getProperty("test.src");
  64 
  65     private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
  66 
  67     private static final String DUP_2_MODULES =
  68             "Modules m. and m. export package p to module test";
  69 
  70     //a package name
  71     private static final String P = "p";
  72     //some module names
  73     private static final String M1 = "m1", M2 = "m2", M3 = "m3", M4 = "m4",
  74             TEST = "test";
  75 
  76     /** 
  77      * Duplicate package p.
  78      */
  79     public void testOverlap() throws Exception {
  80         Path root = Paths.get("overlap");
  81         write(root, init(M1).exports(P).build());
  82         write(root, init(M2).exports(P).build());
  83         assertTrue(CompilerUtils.compile(SRC_DIR.resolve(TEST), 
  84                 root.resolve(TEST)));
  85         write(root, init(TEST).requires(M1).requires(M2).build());
  86 
  87         String exceptionMessage = assertAPIFail(root);
  88         assertMatches(DUP_2_MODULES, exceptionMessage);
  89 
  90         OutputAnalyzer output = runCommandLine(root, M1, M2);
  91         assertNotEquals(output.getExitValue(), 0);
  92         assertMatches(DUP_2_MODULES, output.getStdout());
  93     }
  94 
  95     /** 
  96      * Triplicate package p.
  97      */
  98     public void testOverlap3() throws Exception {
  99         Path root = Paths.get("overlap");
 100         write(root, init(M1).exports(P).build());
 101         write(root, init(M2).exports(P).build());
 102         write(root, init(M3).exports(P).build());
 103         assertTrue(CompilerUtils.compile(SRC_DIR.resolve(TEST), 
 104                 root.resolve(TEST)));
 105         write(root, init(TEST).requires(M1).requires(M2).requires(M3).build());
 106         
 107         String exceptionMessage = assertAPIFail(root);
 108         assertMatches(DUP_2_MODULES, exceptionMessage);
 109 
 110         OutputAnalyzer output = runCommandLine(root, M1, M2);
 111         assertNotEquals(output.getExitValue(), 0);
 112         assertMatches(DUP_2_MODULES, output.getStdout());
 113     }
 114 
 115     /**
 116      * Duplicate package p is available directly and through "requires public".
 117      */
 118     public void testOverlapRequiresPublic() throws Exception {
 119         Path root = Paths.get("overlap_requires_public");
 120         write(root, init(M1).exports(P).build());
 121         write(root, init(M2).exports(P).build());
 122         write(root, init(M3).requires(ModuleDescriptor.Requires.Modifier.PUBLIC, 
 123                 M2).build());
 124         CompilerUtils.compile(SRC_DIR.resolve(TEST), root.resolve(TEST));
 125         write(root, init(TEST).requires(M1).requires(M3).build());
 126         
 127         String exceptionMessage = assertAPIFail(root);
 128         assertMatches(DUP_2_MODULES, exceptionMessage);
 129         
 130         OutputAnalyzer output = runCommandLine(root, M1, M2);
 131         assertNotEquals(output.getExitValue(), 0);
 132         assertMatches(DUP_2_MODULES, output.getStdout());
 133     }
 134 
 135     /**
 136      * Duplicate package p is available directly and through a qualified export.
 137      */
 138     public void testOverlapExportsTo() throws Exception {
 139         Path root = Paths.get("overlap_exports_to");
 140         write(root, init(M1).exports(P).build());
 141         write(root, init(M2).exports(P, TEST).build());
 142         CompilerUtils.compile(SRC_DIR.resolve(TEST), root.resolve(TEST));
 143         write(root, init(TEST).requires(M1).requires(M2).build());
 144 
 145         String exceptionMessage = assertAPIFail(root);
 146         assertMatches(DUP_2_MODULES, exceptionMessage);
 147 
 148         OutputAnalyzer output = runCommandLine(root, M1, M2);
 149         assertNotEquals(output.getExitValue(), 0);
 150         assertMatches(DUP_2_MODULES, output.getStdout());
 151     }
 152 
 153     /**
 154      * Duplicate package p is available from a required module and also present 
 155      * in the module.
 156      */
 157     public void testOverlapSelf() throws Exception {
 158         Path root = Paths.get("overlap_itself");
 159         write(root, init(M1).exports(P).build());
 160         CompilerUtils.compile(SRC_DIR.resolve(TEST), root.resolve(TEST));
 161         write(root, init(TEST).exports(P).requires(M1).build());
 162         
 163         String error = "Module test contains package p, "+ 
 164                 "module m1 exports package p to test";
 165         String exceptionMessage = assertAPIFail(root);
 166         assertEquals(error, exceptionMessage);
 167 
 168         OutputAnalyzer output = runCommandLine(root, M1);
 169         assertNotEquals(output.getExitValue(), 0);
 170         assertTrue(output.getStdout().contains(error));
 171     }
 172 
 173     /**
 174      * Duplicate package p is exported to different modules.
 175      */
 176     public void testOverlapQualified() throws Exception {
 177         Path root = Paths.get("overlap_qualified");
 178         write(root, init(M1).exports(P, M3).build());
 179         write(root, init(M2).exports(P, M4).build());
 180         write(root, init(M3).requires(M1).build());
 181         write(root, init(M4).requires(M2).build());
 182         CompilerUtils.compile(SRC_DIR.resolve(TEST), root.resolve(TEST));
 183         write(root, init(TEST).requires(M3).requires(M4).build());
 184         
 185         assertAPIPass(root, M1, M2, M3, M4);
 186         
 187         OutputAnalyzer output = runCommandLine(root, M1, M2, M3, M4);
 188         assertNotEquals(output.getExitValue(), 0);
 189         assertTrue(output.getStdout().
 190                 contains("Package p in both module m2 and module m1"));
 191     }
 192 
 193     private Configuration doRunAPI(Path root) {
 194         ModuleFinder finder = ModuleFinder.of(root);
 195         return Configuration.resolve(ModuleFinder.empty(),
 196                     Layer.boot(), finder, TEST).bind();
 197     }
 198     
 199     private void assertAPIPass(Path root, String... modules) {
 200         Set<ModuleDescriptor> descriptors = doRunAPI(root).descriptors();
 201         assertTrue(Stream.of(modules).
 202                 allMatch(m -> descriptors.stream().
 203                         anyMatch(d -> d.name().equals(m))));
 204     }
 205 
 206     private String assertAPIFail(Path root) {
 207         try {
 208             doRunAPI(root);
 209             throw new RuntimeException("No resolution exception");
 210         } catch(ResolutionException e) {
 211             System.out.println("Exception as expected");
 212             e.printStackTrace(System.out);
 213             return e.getMessage();
 214         }
 215     }
 216 
 217     private OutputAnalyzer runCommandLine(Path root, String... modules) 
 218             throws Exception {
 219        return executeTestJava("-mp", root.toString(),
 220                               "-addmods", Arrays.stream(modules).
 221                                       collect(Collectors.joining(",")),
 222                               "-m", "test/test.Main")
 223                 .outputTo(System.out)
 224                 .errorTo(System.err);
 225     }
 226 
 227     private static ModuleDescriptor.Builder init(String name) {
 228         return new ModuleDescriptor.Builder(name).requires("java.base");
 229     }
 230 
 231     private static void write(Path root, ModuleDescriptor descriptor) 
 232             throws Exception {
 233         Path mRoot = root.resolve(descriptor.name());
 234         Files.createDirectories(mRoot);
 235         Path mi = mRoot.resolve("module-info.class");
 236         try (OutputStream out = Files.newOutputStream(mi)) {
 237             ModuleInfoWriter.write(descriptor, out);
 238         }
 239     }
 240     
 241     private static void assertMatches(String regex, CharSequence text) {
 242         if(!Pattern.compile(regex).matcher(text).find()) {
 243             fail("Regex: \"" + regex + "\" is not matched by text: \n" + text);
 244         }
 245     }
 246 }