1 /* 2 * Copyright (c) 2015, 2023, 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 * @bug 8142968 8300228 27 * @library /test/lib 28 * @modules java.base/jdk.internal.module 29 * jdk.compiler 30 * jdk.jlink 31 * @build ModuleReaderTest 32 * jdk.test.lib.compiler.CompilerUtils 33 * jdk.test.lib.util.JarUtils 34 * @run junit ModuleReaderTest 35 * @summary Basic tests for java.lang.module.ModuleReader 36 */ 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.lang.module.ModuleFinder; 42 import java.lang.module.ModuleReader; 43 import java.lang.module.ModuleReference; 44 import java.net.URI; 45 import java.net.URL; 46 import java.net.URLConnection; 47 import java.nio.ByteBuffer; 48 import java.nio.charset.StandardCharsets; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.nio.file.Paths; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Optional; 55 import java.util.Set; 56 import java.util.spi.ToolProvider; 57 import java.util.stream.Stream; 58 59 import jdk.internal.module.ModulePath; 60 import jdk.test.lib.Utils; 61 import jdk.test.lib.compiler.CompilerUtils; 62 import jdk.test.lib.util.JarUtils; 63 import org.junit.jupiter.api.BeforeAll; 64 import org.junit.jupiter.api.Test; 65 66 import static org.junit.jupiter.api.Assertions.assertArrayEquals; 67 import static org.junit.jupiter.api.Assertions.assertEquals; 68 import static org.junit.jupiter.api.Assertions.assertFalse; 69 import static org.junit.jupiter.api.Assertions.assertThrows; 70 import static org.junit.jupiter.api.Assertions.assertTrue; 71 72 public class ModuleReaderTest { 73 private static final Path MODS_DIR = Paths.get("mods"); 74 75 // the module name of the base module 76 private static final String BASE_MODULE = "java.base"; 77 78 // the module name of the test module 79 private static final String TEST_MODULE = "m"; 80 81 // resources in the base module 82 private static final String[] BASE_RESOURCES = { 83 "java/lang/Object.class" 84 }; 85 86 // (directory) resources that may be in the base module 87 private static final String[] MAYBE_BASE_RESOURCES = { 88 "java", 89 "java/", 90 "java/lang", 91 "java/lang/", 92 }; 93 94 // resource names that should not be found in the base module 95 private static final String[] NOT_BASE_RESOURCES = { 96 "NotFound", 97 "/java", 98 "//java", 99 "/java/lang", 100 "//java/lang", 101 "java//lang", 102 "/java/lang/Object.class", 103 "//java/lang/Object.class", 104 "java/lang/Object.class/", 105 "java//lang//Object.class", 106 "./java/lang/Object.class", 107 "java/./lang/Object.class", 108 "java/lang/./Object.class", 109 "../java/lang/Object.class", 110 "java/../lang/Object.class", 111 "java/lang/../Object.class", 112 113 // junk resource names 114 "java\u0000", 115 "C:java", 116 "C:\\java", 117 "java\\lang\\Object.class" 118 }; 119 120 // Resources in test module (can't use module-info.class as a test 121 // resource as it will be modified by the jmod tool) 122 private static final String[] TEST_RESOURCES = { 123 "p/Main.class", 124 "p/test.txt" 125 }; 126 127 // (directory) resources that may be in the test module 128 private static final String[] MAYBE_TEST_RESOURCES = { 129 "p", 130 "p/" 131 }; 132 133 // resource names that should not be found in the test module 134 private static final String[] NOT_TEST_RESOURCES = { 135 "NotFound", 136 "/p", 137 "//p", 138 "/p/Main.class", 139 "//p/Main.class", 140 "p/Main.class/", 141 "p//Main.class", 142 "./p/Main.class", 143 "p/./Main.class", 144 "../p/Main.class", 145 "p/../p/Main.class", 146 147 // junk resource names 148 "p\u0000", 149 "C:p", 150 "C:\\p", 151 "p\\Main.class" 152 }; 153 154 @BeforeAll 155 public static void compileTestModules() throws Exception { 156 // Write simplest module-info class. 157 Path srcDir = Path.of("src", TEST_MODULE); 158 Files.createDirectories(srcDir); 159 Files.writeString(srcDir.resolve("module-info.java"), "module " + TEST_MODULE + " {}"); 160 161 // Write and compile test class "p.Main". 162 Path pkgPath = Path.of("p"); 163 Path javaSrc = srcDir.resolve(pkgPath).resolve("Main.java"); 164 Files.createDirectories(javaSrc.getParent()); 165 Files.writeString(javaSrc, 166 """ 167 package p; 168 public class Main { 169 public static void main(String[] args) { } 170 } 171 """); 172 173 // javac -d <outDir> <srcDir>/** 174 Path outDir = MODS_DIR.resolve(TEST_MODULE); 175 boolean compiled = CompilerUtils.compile(srcDir, outDir); 176 assertTrue(compiled, "test module did not compile"); 177 178 // Add two versions of a resource for preview mode testing. 179 Files.writeString(outDir.resolve(pkgPath).resolve("test.txt"), "Normal Version"); 180 Path previewDir = outDir.resolve("META-INF", "preview").resolve(pkgPath); 181 Files.createDirectories(previewDir); 182 Files.writeString(previewDir.resolve("test.txt"), "Preview Version"); 183 } 184 185 /** 186 * Test ModuleReader with module in runtime image. 187 */ 188 @Test 189 public void testImage() throws IOException { 190 ModuleFinder finder = ModuleFinder.ofSystem(); 191 ModuleReference mref = finder.find(BASE_MODULE).get(); 192 ModuleReader reader = mref.open(); 193 194 try (reader) { 195 196 for (String name : BASE_RESOURCES) { 197 byte[] expectedBytes; 198 Module baseModule = Object.class.getModule(); 199 try (InputStream in = baseModule.getResourceAsStream(name)) { 200 expectedBytes = in.readAllBytes(); 201 } 202 203 testFind(reader, name, expectedBytes); 204 testOpen(reader, name, expectedBytes); 205 testRead(reader, name, expectedBytes); 206 testList(reader, name); 207 } 208 209 // test resources that may be in the base module 210 for (String name : MAYBE_BASE_RESOURCES) { 211 Optional<URI> ouri = reader.find(name); 212 ouri.ifPresent(uri -> { 213 if (name.endsWith("/")) 214 assertTrue(uri.toString().endsWith("/"), 215 "mismatched directory URI for '" + name + "': " + uri); 216 }); 217 } 218 219 // test "not found" in java.base module 220 for (String name : NOT_BASE_RESOURCES) { 221 assertFalse(reader.find(name).isPresent(), "Unexpected resource found: " + name); 222 assertFalse(reader.open(name).isPresent(), "Unexpected resource opened: " + name); 223 assertFalse(reader.read(name).isPresent(), "Unexpected resource read: " + name); 224 } 225 226 // test nulls 227 assertThrows(NullPointerException.class, () -> reader.find(null)); 228 assertThrows(NullPointerException.class, () -> reader.open(null)); 229 assertThrows(NullPointerException.class, () -> reader.read(null)); 230 assertThrows(NullPointerException.class, () -> reader.release(null)); 231 } 232 233 // test closed ModuleReader 234 assertThrows(IOException.class, () -> reader.open(BASE_RESOURCES[0])); 235 assertThrows(IOException.class, () -> reader.read(BASE_RESOURCES[0])); 236 assertThrows(IOException.class, reader::list); 237 } 238 239 /** 240 * Test ModuleReader with exploded module. 241 */ 242 @Test 243 public void testExplodedModule() throws IOException { 244 test(MODS_DIR); 245 } 246 247 /** 248 * Test equivalent of the system ModuleReader with preview mode. This differs 249 * in behavior to other "exploded modules" because it supports preview mode. 250 * It also hides preview resources when preview mode is enabled. 251 * 252 * <p>Note: When preview mode is not enabled, preview resources are visible 253 * via their un-mapped path. This is not the same behavior as things like 254 * the JRT filesystem or non-exploded module readers, in which preview paths 255 * are always hidden. 256 */ 257 @Test 258 public void testExplodedSystemModule() throws IOException { 259 ModuleFinder normalFinder = ModulePath.of(/* modulePatcher */ null, /* previewMode */ false, MODS_DIR); 260 try (ModuleReader reader = normalFinder.find(TEST_MODULE).get().open()) { 261 assertEquals("Normal Version", assertUtf8Resource(reader, "p/test.txt")); 262 // This file is not visible in an exploded image when using JRT filesystem. 263 assertEquals("Preview Version", assertUtf8Resource(reader, "META-INF/preview/p/test.txt")); 264 } 265 ModuleFinder previewFinder = ModulePath.of(/* modulePatcher */ null, /* previewMode */ true, MODS_DIR); 266 try (ModuleReader reader = previewFinder.find(TEST_MODULE).get().open()) { 267 assertEquals("Preview Version", assertUtf8Resource(reader, "p/test.txt")); 268 assertFalse(reader.find("META-INF/preview/p/test.txt").isPresent(), "unexpected preview resource"); 269 } 270 } 271 272 private static String assertUtf8Resource(ModuleReader reader, String name) throws IOException { 273 // Check the resource can be found with the expected URI. 274 Optional<URI> uri = reader.find(name); 275 assertTrue(uri.isPresent(), "resource not found: " + name); 276 assertTrue(uri.get().getPath().endsWith(name), "unexpected path: " + uri.get()); 277 278 // Open and read all resource bytes. 279 Optional<InputStream> is = reader.open(name); 280 assertTrue(is.isPresent(), "resource cannot be opened: " + name); 281 byte[] bytes = is.get().readAllBytes(); 282 283 // Cross-check that read() returns the same bytes as open(). 284 Optional<ByteBuffer> buffer = reader.read(name); 285 assertTrue(buffer.isPresent(), "resource cannot be read: " + name); 286 assertArrayEquals(buffer.get().array(), bytes, "resource bytes differ: " + name); 287 // Return the string of the UTF-8 bytes for checking the actual content. 288 return new String(bytes, StandardCharsets.UTF_8); 289 } 290 291 /** 292 * Test ModuleReader with module in modular JAR. 293 */ 294 @Test 295 public void testModularJar() throws IOException { 296 Path dir = Utils.createTempDirectory("mlib"); 297 298 // jar cf mlib/${TESTMODULE}.jar -C mods . 299 JarUtils.createJarFile(dir.resolve(TEST_MODULE + ".jar"), 300 MODS_DIR.resolve(TEST_MODULE)); 301 302 test(dir); 303 } 304 305 /** 306 * Test ModuleReader with module in a JMOD file. 307 */ 308 @Test 309 public void testJMod() throws IOException { 310 Path dir = Utils.createTempDirectory("mlib"); 311 312 // jmod create --class-path mods/${TESTMODULE} mlib/${TESTMODULE}.jmod 313 String cp = MODS_DIR.resolve(TEST_MODULE).toString(); 314 String jmod = dir.resolve(TEST_MODULE + ".jmod").toString(); 315 String[] args = {"create", "--class-path", cp, jmod}; 316 ToolProvider jmodTool = ToolProvider.findFirst("jmod") 317 .orElseThrow(() -> 318 new RuntimeException("jmod tool not found") 319 ); 320 assertEquals(0, jmodTool.run(System.out, System.out, args), "jmod tool failed"); 321 322 test(dir); 323 } 324 325 /** 326 * The test module is found on the given module path. Open a ModuleReader 327 * to the test module and test the reader. 328 */ 329 void test(Path mp) throws IOException { 330 ModuleFinder finder = ModulePath.of(Runtime.version(), true, mp); 331 ModuleReference mref = finder.find(TEST_MODULE).get(); 332 ModuleReader reader = mref.open(); 333 334 try (reader) { 335 336 // test resources in test module 337 for (String name : TEST_RESOURCES) { 338 System.out.println("resource: " + name); 339 byte[] expectedBytes 340 = Files.readAllBytes(MODS_DIR 341 .resolve(TEST_MODULE) 342 .resolve(name.replace('/', File.separatorChar))); 343 344 testFind(reader, name, expectedBytes); 345 testOpen(reader, name, expectedBytes); 346 testRead(reader, name, expectedBytes); 347 testList(reader, name); 348 } 349 350 // test resources that may be in the test module 351 for (String name : MAYBE_TEST_RESOURCES) { 352 System.out.println("resource: " + name); 353 Optional<URI> ouri = reader.find(name); 354 ouri.ifPresent(uri -> { 355 if (name.endsWith("/")) 356 assertTrue(uri.toString().endsWith("/"), 357 "mismatched directory URI for '" + name + "': " + uri); 358 }); 359 } 360 361 // test "not found" in test module 362 for (String name : NOT_TEST_RESOURCES) { 363 System.out.println("resource: " + name); 364 assertFalse(reader.find(name).isPresent(), "Unexpected resource found: " + name); 365 assertFalse(reader.open(name).isPresent(), "Unexpected resource open: " + name); 366 assertFalse(reader.read(name).isPresent(), "Unexpected resource read: " + name); 367 } 368 369 // test nulls 370 assertThrows(NullPointerException.class, () -> reader.find(null)); 371 assertThrows(NullPointerException.class, () -> reader.open(null)); 372 assertThrows(NullPointerException.class, () -> reader.read(null)); 373 assertThrows(NullPointerException.class, () -> reader.release(null)); 374 } 375 376 // test closed ModuleReader 377 assertThrows(IOException.class, () -> reader.open(BASE_RESOURCES[0])); 378 assertThrows(IOException.class, () -> reader.read(BASE_RESOURCES[0])); 379 assertThrows(IOException.class, reader::list); 380 } 381 382 /** 383 * Test ModuleReader#find 384 */ 385 void testFind(ModuleReader reader, String name, byte[] expectedBytes) 386 throws IOException 387 { 388 Optional<URI> ouri = reader.find(name); 389 assertTrue(ouri.isPresent(), "missing URI for: " + name); 390 391 URL url = ouri.get().toURL(); 392 if (!url.getProtocol().equalsIgnoreCase("jmod")) { 393 URLConnection uc = url.openConnection(); 394 uc.setUseCaches(false); 395 try (InputStream in = uc.getInputStream()) { 396 byte[] bytes = in.readAllBytes(); 397 assertArrayEquals(expectedBytes, bytes, "resource bytes differ for: " + name); 398 } 399 } 400 } 401 402 /** 403 * Test ModuleReader#open 404 */ 405 void testOpen(ModuleReader reader, String name, byte[] expectedBytes) 406 throws IOException 407 { 408 Optional<InputStream> oin = reader.open(name); 409 assertTrue(oin.isPresent(), "missing input stream for: " + name); 410 try (InputStream in = oin.get()) { 411 byte[] bytes = in.readAllBytes(); 412 assertArrayEquals(expectedBytes, bytes, "resource bytes differ for: " + name); 413 } 414 } 415 416 /** 417 * Test ModuleReader#read 418 */ 419 void testRead(ModuleReader reader, String name, byte[] expectedBytes) 420 throws IOException 421 { 422 Optional<ByteBuffer> obb = reader.read(name); 423 assertTrue(obb.isPresent()); 424 425 ByteBuffer bb = obb.get(); 426 try { 427 int rem = bb.remaining(); 428 assertEquals(expectedBytes.length, rem, "resource lengths differ: " + name); 429 byte[] bytes = new byte[rem]; 430 bb.get(bytes); 431 assertArrayEquals(expectedBytes, bytes, "resource bytes differ: " + name); 432 } finally { 433 reader.release(bb); 434 } 435 } 436 437 /** 438 * Test ModuleReader#list 439 */ 440 void testList(ModuleReader reader, String name) throws IOException { 441 final List<String> list; 442 try (Stream<String> stream = reader.list()) { 443 list = stream.toList(); 444 } 445 Set<String> names = new HashSet<>(list); 446 assertEquals(names.size(), list.size(), "resource list contains duplicates: " + list); 447 448 assertTrue(names.contains("module-info.class"), "resource list did not contain 'module-info.class': " + list); 449 assertTrue(names.contains(name), "resource list did not contain '" + name + "'" + list); 450 451 // all resources should be locatable via find 452 for (String e : names) { 453 assertTrue(reader.find(e).isPresent(), "resource not found: " + name); 454 } 455 } 456 457 } --- EOF ---