51 import java.util.Collections; |
54 import java.util.Collections; |
52 import java.util.List; |
55 import java.util.List; |
53 import java.util.Set; |
56 import java.util.Set; |
54 import java.util.spi.ToolProvider; |
57 import java.util.spi.ToolProvider; |
55 import java.util.stream.Collectors; |
58 import java.util.stream.Collectors; |
|
59 import java.util.stream.Stream; |
56 |
60 |
57 import jdk.internal.module.ModuleInfo; |
61 import jdk.internal.module.ModuleInfo; |
58 import jdk.internal.module.ModuleHashes; |
62 import jdk.internal.module.ModuleHashes; |
59 import jdk.internal.module.ModulePath; |
63 import jdk.internal.module.ModulePath; |
60 |
64 |
61 import org.testng.annotations.BeforeTest; |
|
62 import org.testng.annotations.Test; |
65 import org.testng.annotations.Test; |
63 |
66 |
64 import static org.testng.Assert.*; |
67 import static org.testng.Assert.*; |
|
68 import static java.lang.module.ModuleDescriptor.Requires.Modifier.*; |
65 |
69 |
66 public class HashesTest { |
70 public class HashesTest { |
67 static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") |
71 static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") |
68 .orElseThrow(() -> |
72 .orElseThrow(() -> |
69 new RuntimeException("jmod tool not found") |
73 new RuntimeException("jmod tool not found") |
70 ); |
74 ); |
71 |
75 static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") |
72 private final Path testSrc = Paths.get(System.getProperty("test.src")); |
76 .orElseThrow(() -> |
73 private final Path modSrc = testSrc.resolve("src"); |
77 new RuntimeException("jar tool not found") |
74 private final Path mods = Paths.get("mods"); |
78 ); |
75 private final Path jmods = Paths.get("jmods"); |
79 |
76 private final String[] modules = new String[] { "m1", "m2", "m3"}; |
80 private final Path mods; |
77 |
81 private final Path srcDir; |
78 @BeforeTest |
82 private final Path lib; |
79 private void setup() throws Exception { |
83 private final ModuleInfoMaker builder; |
80 if (Files.exists(jmods)) { |
84 HashesTest(Path dest) throws IOException { |
81 deleteDirectory(jmods); |
85 if (Files.exists(dest)) { |
82 } |
86 deleteDirectory(dest); |
83 Files.createDirectories(jmods); |
87 } |
84 |
88 this.mods = dest.resolve("mods"); |
85 // build m2, m3 required by m1 |
89 this.srcDir = dest.resolve("src"); |
86 compileModule("m2", modSrc); |
90 this.lib = dest.resolve("lib"); |
87 jmod("m2"); |
91 this.builder = new ModuleInfoMaker(srcDir); |
88 |
92 |
89 compileModule("m3", modSrc); |
93 Files.createDirectories(lib); |
90 jmod("m3"); |
94 Files.createDirectories(mods); |
91 |
95 } |
92 // build m1 |
96 |
93 compileModule("m1", modSrc); |
97 @Test |
|
98 public static void test() throws IOException { |
|
99 Path dest = Paths.get("test"); |
|
100 HashesTest ht = new HashesTest(dest); |
|
101 |
|
102 // create modules for test cases |
|
103 ht.makeModule("m2"); |
|
104 ht.makeModule("m3"); |
|
105 ht.makeModule("m1", "m2", "m3"); |
|
106 |
|
107 ht.makeModule("org.bar", TRANSITIVE, "m1"); |
|
108 ht.makeModule("org.foo", TRANSITIVE, "org.bar"); |
|
109 |
|
110 // create JMOD for m1, m2, m3 |
|
111 ht.makeJmod("m2"); |
|
112 ht.makeJmod("m3"); |
|
113 |
94 // no hash is recorded since m1 has outgoing edges |
114 // no hash is recorded since m1 has outgoing edges |
95 jmod("m1", "--module-path", jmods.toString(), "--hash-modules", ".*"); |
115 ht.jmodHashModules("m1", ".*"); |
96 |
116 |
97 // compile org.bar and org.foo |
117 // no hash is recorded in m1, m2, m3 |
98 compileModule("org.bar", modSrc); |
118 assertTrue(ht.hashes("m1") == null); |
99 compileModule("org.foo", modSrc); |
119 assertTrue(ht.hashes("m2") == null); |
100 } |
120 assertTrue(ht.hashes("m3") == null); |
101 |
|
102 @Test |
|
103 public void test() throws Exception { |
|
104 for (String mn : modules) { |
|
105 assertTrue(hashes(mn) == null); |
|
106 } |
|
107 |
121 |
108 // hash m1 in m2 |
122 // hash m1 in m2 |
109 jmod("m2", "--module-path", jmods.toString(), "--hash-modules", "m1"); |
123 ht.jmodHashModules("m2", "m1"); |
110 checkHashes(hashes("m2"), "m1"); |
124 ht.checkHashes("m2", "m1"); |
111 |
125 |
112 // hash m1 in m2 |
126 // hash m1 in m2 |
113 jmod("m2", "--module-path", jmods.toString(), "--hash-modules", ".*"); |
127 ht.jmodHashModules("m2", ".*"); |
114 checkHashes(hashes("m2"), "m1"); |
128 ht.checkHashes("m2", "m1"); |
115 |
129 |
116 // create m2.jmod with no hash |
130 // create m2.jmod with no hash |
117 jmod("m2"); |
131 ht.makeJmod("m2"); |
118 // run jmod hash command to hash m1 in m2 and m3 |
132 // run jmod hash command to hash m1 in m2 and m3 |
119 runJmod(Arrays.asList("hash", "--module-path", jmods.toString(), |
133 runJmod(List.of("hash", "--module-path", ht.lib.toString(), |
120 "--hash-modules", ".*")); |
134 "--hash-modules", ".*")); |
121 checkHashes(hashes("m2"), "m1"); |
135 ht.checkHashes("m2", "m1"); |
122 checkHashes(hashes("m3"), "m1"); |
136 ht.checkHashes("m3", "m1"); |
123 |
137 |
124 jmod("org.bar"); |
138 // check transitive requires |
125 jmod("org.foo"); |
139 ht.makeJmod("org.bar"); |
126 |
140 ht.makeJmod("org.foo"); |
127 jmod("org.bar", "--module-path", jmods.toString(), "--hash-modules", "org.*"); |
141 |
128 checkHashes(hashes("org.bar"), "org.foo"); |
142 ht.jmodHashModules("org.bar", "org.*"); |
129 |
143 ht.checkHashes("org.bar", "org.foo"); |
130 jmod("m3", "--module-path", jmods.toString(), "--hash-modules", ".*"); |
144 |
131 checkHashes(hashes("m3"), "org.foo", "org.bar", "m1"); |
145 ht.jmodHashModules( "m3", ".*"); |
132 } |
146 ht.checkHashes("m3", "org.foo", "org.bar", "m1"); |
133 |
147 } |
134 private void checkHashes(ModuleHashes hashes, String... hashModules) { |
148 |
135 assertTrue(hashes.names().equals(Set.of(hashModules))); |
149 @Test |
136 } |
150 public static void multiBaseModules() throws IOException { |
137 |
151 Path dest = Paths.get("test2"); |
138 private ModuleHashes hashes(String name) throws Exception { |
152 HashesTest ht = new HashesTest(dest); |
|
153 |
|
154 /* |
|
155 * y2 -----------> y1 |
|
156 * |______ |
|
157 * | | |
|
158 * V V |
|
159 * z3 -> z2 |
|
160 * | | |
|
161 * | V |
|
162 * |---> z1 |
|
163 */ |
|
164 |
|
165 ht.makeModule("z1"); |
|
166 ht.makeModule("z2", "z1"); |
|
167 ht.makeModule("z3", "z1", "z2"); |
|
168 |
|
169 ht.makeModule("y1"); |
|
170 ht.makeModule("y2", "y1", "z2", "z3"); |
|
171 |
|
172 Set<String> ys = Set.of("y1", "y2"); |
|
173 Set<String> zs = Set.of("z1", "z2", "z3"); |
|
174 |
|
175 // create JMOD files |
|
176 Stream.concat(ys.stream(), zs.stream()).forEach(ht::makeJmod); |
|
177 |
|
178 // run jmod hash command |
|
179 runJmod(List.of("hash", "--module-path", ht.lib.toString(), |
|
180 "--hash-modules", ".*")); |
|
181 |
|
182 /* |
|
183 * z1 and y1 are the modules with hashes recorded. |
|
184 */ |
|
185 ht.checkHashes("y1", "y2"); |
|
186 ht.checkHashes("z1", "z2", "z3", "y2"); |
|
187 Stream.concat(ys.stream(), zs.stream()) |
|
188 .filter(mn -> !mn.equals("y1") && !mn.equals("z1")) |
|
189 .forEach(mn -> assertTrue(ht.hashes(mn) == null)); |
|
190 } |
|
191 |
|
192 @Test |
|
193 public static void mixJmodAndJarFile() throws IOException { |
|
194 Path dest = Paths.get("test3"); |
|
195 HashesTest ht = new HashesTest(dest); |
|
196 |
|
197 /* |
|
198 * j3 -----------> j2 |
|
199 * |______ |
|
200 * | | |
|
201 * V V |
|
202 * m3 -> m2 |
|
203 * | | |
|
204 * | V |
|
205 * |---> m1 -> j1 -> jdk.jlink |
|
206 */ |
|
207 |
|
208 ht.makeModule("j1"); |
|
209 ht.makeModule("j2"); |
|
210 ht.makeModule("m1", "j1"); |
|
211 ht.makeModule("m2", "m1"); |
|
212 ht.makeModule("m3", "m1", "m2"); |
|
213 |
|
214 ht.makeModule("j3", "j2", "m2", "m3"); |
|
215 |
|
216 Set<String> jars = Set.of("j1", "j2", "j3"); |
|
217 Set<String> jmods = Set.of("m1", "m2", "m3"); |
|
218 |
|
219 // create JMOD and JAR files |
|
220 jars.forEach(ht::makeJar); |
|
221 jmods.forEach(ht::makeJmod); |
|
222 |
|
223 // run jmod hash command |
|
224 runJmod(List.of("hash", "--module-path", ht.lib.toString(), |
|
225 "--hash-modules", "^j.*|^m.*")); |
|
226 |
|
227 /* |
|
228 * j1 and j2 are the modules with hashes recorded. |
|
229 */ |
|
230 ht.checkHashes("j2", "j3"); |
|
231 ht.checkHashes("j1", "m1", "m2", "m3", "j3"); |
|
232 Stream.concat(jars.stream(), jmods.stream()) |
|
233 .filter(mn -> !mn.equals("j1") && !mn.equals("j2")) |
|
234 .forEach(mn -> assertTrue(ht.hashes(mn) == null)); |
|
235 } |
|
236 |
|
237 @Test |
|
238 public static void upgradeableModule() throws IOException { |
|
239 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); |
|
240 if (!Files.exists(mpath)) { |
|
241 return; |
|
242 } |
|
243 |
|
244 Path dest = Paths.get("test4"); |
|
245 HashesTest ht = new HashesTest(dest); |
|
246 ht.makeModule("m1"); |
|
247 ht.makeModule("java.xml.bind", "m1"); |
|
248 ht.makeModule("java.xml.ws", "java.xml.bind"); |
|
249 ht.makeModule("m2", "java.xml.ws"); |
|
250 |
|
251 ht.makeJmod("m1"); |
|
252 ht.makeJmod("m2"); |
|
253 ht.makeJmod("java.xml.ws"); |
|
254 ht.makeJmod("java.xml.bind", |
|
255 "--module-path", |
|
256 ht.lib.toString() + File.pathSeparator + mpath, |
|
257 "--hash-modules", "^java.xml.*|^m.*"); |
|
258 |
|
259 ht.checkHashes("java.xml.bind", "java.xml.ws", "m2"); |
|
260 } |
|
261 |
|
262 @Test |
|
263 public static void testImageJmods() throws IOException { |
|
264 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); |
|
265 if (!Files.exists(mpath)) { |
|
266 return; |
|
267 } |
|
268 |
|
269 Path dest = Paths.get("test5"); |
|
270 HashesTest ht = new HashesTest(dest); |
|
271 ht.makeModule("m1", "jdk.compiler", "jdk.attach"); |
|
272 ht.makeModule("m2", "m1"); |
|
273 ht.makeModule("m3", "java.compiler"); |
|
274 |
|
275 ht.makeJmod("m1"); |
|
276 ht.makeJmod("m2"); |
|
277 |
|
278 runJmod(List.of("hash", |
|
279 "--module-path", |
|
280 mpath.toString() + File.pathSeparator + ht.lib.toString(), |
|
281 "--hash-modules", ".*")); |
|
282 |
|
283 validateImageJmodsTest(ht, mpath); |
|
284 } |
|
285 |
|
286 @Test |
|
287 public static void testImageJmods1() throws IOException { |
|
288 Path mpath = Paths.get(System.getProperty("java.home"), "jmods"); |
|
289 if (!Files.exists(mpath)) { |
|
290 return; |
|
291 } |
|
292 |
|
293 Path dest = Paths.get("test6"); |
|
294 HashesTest ht = new HashesTest(dest); |
|
295 ht.makeModule("m1", "jdk.compiler", "jdk.attach"); |
|
296 ht.makeModule("m2", "m1"); |
|
297 ht.makeModule("m3", "java.compiler"); |
|
298 |
|
299 ht.makeJar("m2"); |
|
300 ht.makeJar("m1", |
|
301 "--module-path", |
|
302 mpath.toString() + File.pathSeparator + ht.lib.toString(), |
|
303 "--hash-modules", ".*"); |
|
304 validateImageJmodsTest(ht, mpath); |
|
305 } |
|
306 |
|
307 private static void validateImageJmodsTest(HashesTest ht, Path mpath) |
|
308 throws IOException |
|
309 { |
|
310 // hash is recorded in m1 and not any other packaged modules on module path |
|
311 ht.checkHashes("m1", "m2"); |
|
312 assertTrue(ht.hashes("m2") == null); |
|
313 |
|
314 // should not override any JDK packaged modules |
139 ModuleFinder finder = new ModulePath(Runtime.version(), |
315 ModuleFinder finder = new ModulePath(Runtime.version(), |
140 true, |
316 true, |
141 jmods.resolve(name + ".jmod")); |
317 mpath); |
|
318 assertTrue(ht.hashes(finder,"jdk.compiler") == null); |
|
319 assertTrue(ht.hashes(finder,"jdk.attach") == null); |
|
320 } |
|
321 |
|
322 private void checkHashes(String mn, String... hashModules) throws IOException { |
|
323 ModuleHashes hashes = hashes(mn); |
|
324 assertTrue(hashes.names().equals(Set.of(hashModules))); |
|
325 } |
|
326 |
|
327 private ModuleHashes hashes(String name) { |
|
328 ModuleFinder finder = new ModulePath(Runtime.version(), |
|
329 true, |
|
330 lib); |
|
331 return hashes(finder, name); |
|
332 } |
|
333 |
|
334 private ModuleHashes hashes(ModuleFinder finder, String name) { |
142 ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new); |
335 ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new); |
143 ModuleReader reader = mref.open(); |
336 try { |
144 try (InputStream in = reader.open("module-info.class").get()) { |
337 ModuleReader reader = mref.open(); |
145 ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes(); |
338 try (InputStream in = reader.open("module-info.class").get()) { |
146 System.out.format("hashes in module %s %s%n", name, |
339 ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes(); |
|
340 System.out.format("hashes in module %s %s%n", name, |
147 (hashes != null) ? "present" : "absent"); |
341 (hashes != null) ? "present" : "absent"); |
148 if (hashes != null) { |
342 if (hashes != null) { |
149 hashes.names().stream() |
343 hashes.names().stream().sorted().forEach(n -> |
150 .sorted() |
344 System.out.format(" %s %s%n", n, toHex(hashes.hashFor(n))) |
151 .forEach(n -> System.out.format(" %s %s%n", n, hashes.hashFor(n))); |
345 ); |
152 } |
346 } |
153 return hashes; |
347 return hashes; |
154 } finally { |
348 } finally { |
155 reader.close(); |
349 reader.close(); |
156 } |
350 } |
|
351 } catch (IOException e) { |
|
352 throw new UncheckedIOException(e); |
|
353 } |
|
354 } |
|
355 |
|
356 private String toHex(byte[] ba) { |
|
357 StringBuilder sb = new StringBuilder(ba.length); |
|
358 for (byte b: ba) { |
|
359 sb.append(String.format("%02x", b & 0xff)); |
|
360 } |
|
361 return sb.toString(); |
157 } |
362 } |
158 |
363 |
159 private void deleteDirectory(Path dir) throws IOException { |
364 private void deleteDirectory(Path dir) throws IOException { |
160 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { |
365 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { |
161 @Override |
366 @Override |
174 return FileVisitResult.CONTINUE; |
379 return FileVisitResult.CONTINUE; |
175 } |
380 } |
176 }); |
381 }); |
177 } |
382 } |
178 |
383 |
|
384 |
|
385 private void makeModule(String mn, String... deps) throws IOException { |
|
386 makeModule(mn, null, deps); |
|
387 } |
|
388 |
|
389 private void makeModule(String mn, ModuleDescriptor.Requires.Modifier mod, String... deps) |
|
390 throws IOException |
|
391 { |
|
392 if (mod != null && mod != TRANSITIVE && mod != STATIC) { |
|
393 throw new IllegalArgumentException(mod.toString()); |
|
394 } |
|
395 |
|
396 StringBuilder sb = new StringBuilder(); |
|
397 sb.append("module " + mn + " {").append("\n"); |
|
398 Arrays.stream(deps).forEach(req -> { |
|
399 sb.append(" requires "); |
|
400 if (mod != null) { |
|
401 sb.append(mod.toString().toLowerCase()).append(" "); |
|
402 } |
|
403 sb.append(req + ";\n"); |
|
404 }); |
|
405 sb.append("}\n"); |
|
406 builder.writeJavaFiles(mn, sb.toString()); |
|
407 |
|
408 compileModule(mn, srcDir); |
|
409 } |
|
410 |
179 private void compileModule(String moduleName, Path src) throws IOException { |
411 private void compileModule(String moduleName, Path src) throws IOException { |
180 Path msrc = src.resolve(moduleName); |
412 Path msrc = src.resolve(moduleName); |
181 assertTrue(CompilerUtils.compile(msrc, mods, "--module-source-path", src.toString())); |
413 assertTrue(CompilerUtils.compile(msrc, mods, "--module-source-path", src.toString())); |
182 } |
414 } |
183 |
415 |
184 private void jmod(String moduleName, String... options) throws IOException { |
416 private void jmodHashModules(String moduleName, String hashModulesPattern) { |
|
417 makeJmod(moduleName, "--module-path", lib.toString(), |
|
418 "--hash-modules", hashModulesPattern); |
|
419 } |
|
420 |
|
421 private void makeJmod(String moduleName, String... options) { |
185 Path mclasses = mods.resolve(moduleName); |
422 Path mclasses = mods.resolve(moduleName); |
186 Path outfile = jmods.resolve(moduleName + ".jmod"); |
423 Path outfile = lib.resolve(moduleName + ".jmod"); |
187 List<String> args = new ArrayList<>(); |
424 List<String> args = new ArrayList<>(); |
188 args.add("create"); |
425 args.add("create"); |
189 Collections.addAll(args, options); |
426 Collections.addAll(args, options); |
190 Collections.addAll(args, "--class-path", mclasses.toString(), |
427 Collections.addAll(args, "--class-path", mclasses.toString(), |
191 outfile.toString()); |
428 outfile.toString()); |
192 |
429 |
193 if (Files.exists(outfile)) |
430 if (Files.exists(outfile)) { |
194 Files.delete(outfile); |
431 try { |
195 |
432 Files.delete(outfile); |
|
433 } catch (IOException e) { |
|
434 throw new UncheckedIOException(e); |
|
435 } |
|
436 } |
196 runJmod(args); |
437 runJmod(args); |
197 } |
438 } |
198 |
439 |
199 private void runJmod(List<String> args) { |
440 private static void runJmod(List<String> args) { |
200 int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); |
441 int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); |
201 System.out.println("jmod options: " + args.stream().collect(Collectors.joining(" "))); |
442 System.out.println("jmod " + args.stream().collect(Collectors.joining(" "))); |
202 if (rc != 0) { |
443 if (rc != 0) { |
203 throw new AssertionError("Jmod failed: rc = " + rc); |
444 throw new AssertionError("jmod failed: rc = " + rc); |
|
445 } |
|
446 } |
|
447 |
|
448 private void makeJar(String moduleName, String... options) { |
|
449 Path mclasses = mods.resolve(moduleName); |
|
450 Path outfile = lib.resolve(moduleName + ".jar"); |
|
451 List<String> args = new ArrayList<>(); |
|
452 Stream.concat(Stream.of("--create", |
|
453 "--file=" + outfile.toString()), |
|
454 Arrays.stream(options)) |
|
455 .forEach(args::add); |
|
456 args.add("-C"); |
|
457 args.add(mclasses.toString()); |
|
458 args.add("."); |
|
459 |
|
460 if (Files.exists(outfile)) { |
|
461 try { |
|
462 Files.delete(outfile); |
|
463 } catch (IOException e) { |
|
464 throw new UncheckedIOException(e); |
|
465 } |
|
466 } |
|
467 |
|
468 int rc = JAR_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); |
|
469 System.out.println("jar " + args.stream().collect(Collectors.joining(" "))); |
|
470 if (rc != 0) { |
|
471 throw new AssertionError("jar failed: rc = " + rc); |
204 } |
472 } |
205 } |
473 } |
206 } |
474 } |