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 import jdk.internal.jimage.ImageReader;
25 import jdk.internal.jimage.ImageReader.Node;
26 import jdk.test.lib.compiler.InMemoryJavaCompiler;
27 import jdk.test.lib.util.JarBuilder;
28 import jdk.tools.jlink.internal.LinkableRuntimeImage;
29 import org.junit.jupiter.api.Assumptions;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.TestInstance;
32 import org.junit.jupiter.params.ParameterizedTest;
33 import org.junit.jupiter.params.provider.CsvSource;
34 import org.junit.jupiter.params.provider.ValueSource;
35 import tests.Helper;
36 import tests.JImageGenerator;
37
38 import java.io.IOException;
39 import java.nio.file.Path;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45
46 import static org.junit.jupiter.api.Assertions.assertEquals;
47 import static org.junit.jupiter.api.Assertions.assertFalse;
48 import static org.junit.jupiter.api.Assertions.assertNotNull;
49 import static org.junit.jupiter.api.Assertions.assertNull;
50 import static org.junit.jupiter.api.Assertions.assertSame;
51 import static org.junit.jupiter.api.Assertions.assertThrows;
52 import static org.junit.jupiter.api.Assertions.assertTrue;
53 import static org.junit.jupiter.api.Assertions.fail;
54 import static org.junit.jupiter.api.Assumptions.assumeTrue;
55 import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
56
57 /*
58 * @test
59 * @summary Tests for ImageReader.
60 * @modules java.base/jdk.internal.jimage
61 * jdk.jlink/jdk.tools.jlink.internal
62 * jdk.jlink/jdk.tools.jimage
63 * @library /test/jdk/tools/lib
64 * /test/lib
65 * @build tests.*
66 * @run junit/othervm ImageReaderTest
67 */
68
69 /// Using PER_CLASS lifecycle means the (expensive) image file is only build once.
70 /// There is no mutable test instance state to worry about.
71 @TestInstance(PER_CLASS)
72 public class ImageReaderTest {
73
74 private static final Map<String, List<String>> IMAGE_ENTRIES = Map.of(
75 "modfoo", Arrays.asList(
76 "com.foo.Alpha",
77 "com.foo.Beta",
78 "com.foo.bar.Gamma"),
79 "modbar", Arrays.asList(
80 "com.bar.One",
81 "com.bar.Two"));
82 private final Path image = buildJImage(IMAGE_ENTRIES);
83
84 @ParameterizedTest
85 @ValueSource(strings = {
86 "/",
87 "/modules",
88 "/modules/modfoo",
89 "/modules/modbar",
90 "/modules/modfoo/com",
91 "/modules/modfoo/com/foo",
92 "/modules/modfoo/com/foo/bar"})
93 public void testModuleDirectories_expected(String name) throws IOException {
94 try (ImageReader reader = ImageReader.open(image)) {
95 assertDir(reader, name);
96 }
97 }
98
99 @ParameterizedTest
100 @ValueSource(strings = {
101 "",
102 "//",
103 "/modules/",
104 "/modules/unknown",
105 "/modules/modbar/",
106 "/modules/modfoo//com",
107 "/modules/modfoo/com/"})
108 public void testModuleNodes_absent(String name) throws IOException {
109 try (ImageReader reader = ImageReader.open(image)) {
110 assertAbsent(reader, name);
111 }
112 }
113
114 @Test
115 public void testModuleResources() throws IOException {
116 try (ImageReader reader = ImageReader.open(image)) {
117 assertNode(reader, "/modules/modfoo/com/foo/Alpha.class");
118 assertNode(reader, "/modules/modbar/com/bar/One.class");
119
120 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
121 assertEquals("Class: com.foo.Alpha", loader.loadAndGetToString("modfoo", "com.foo.Alpha"));
122 assertEquals("Class: com.foo.Beta", loader.loadAndGetToString("modfoo", "com.foo.Beta"));
123 assertEquals("Class: com.foo.bar.Gamma", loader.loadAndGetToString("modfoo", "com.foo.bar.Gamma"));
124 assertEquals("Class: com.bar.One", loader.loadAndGetToString("modbar", "com.bar.One"));
125 }
126 }
127
128 @ParameterizedTest
129 @CsvSource(delimiter = ':', value = {
130 "modfoo:com/foo/Alpha.class",
131 "modbar:com/bar/One.class",
132 })
133 public void testResource_present(String modName, String resPath) throws IOException {
134 try (ImageReader reader = ImageReader.open(image)) {
135 assertNotNull(reader.findResourceNode(modName, resPath));
136 assertTrue(reader.containsResource(modName, resPath));
137
138 String canonicalNodeName = "/modules/" + modName + "/" + resPath;
139 Node node = reader.findNode(canonicalNodeName);
140 assertTrue(node != null && node.isResource());
141 }
142 }
143
144 @ParameterizedTest
145 @CsvSource(delimiter = ':', value = {
146 // Absolute resource names are not allowed.
147 "modfoo:/com/bar/One.class",
148 // Resource in wrong module.
149 "modfoo:com/bar/One.class",
150 "modbar:com/foo/Alpha.class",
151 // Directories are not returned.
152 "modfoo:com/foo",
153 "modbar:com/bar",
154 // JImage entries exist for these, but they are not resources.
155 "modules:modfoo/com/foo/Alpha.class",
156 "packages:com.foo/modfoo",
157 // Empty module names/paths do not find resources.
158 "'':modfoo/com/foo/Alpha.class",
159 "modfoo:''"})
160 public void testResource_absent(String modName, String resPath) throws IOException {
161 try (ImageReader reader = ImageReader.open(image)) {
162 assertNull(reader.findResourceNode(modName, resPath));
163 assertFalse(reader.containsResource(modName, resPath));
164
165 // Non-existent resources names should either not be found,
166 // or (in the case of directory nodes) not be resources.
167 String canonicalNodeName = "/modules/" + modName + "/" + resPath;
168 Node node = reader.findNode(canonicalNodeName);
169 assertTrue(node == null || !node.isResource());
170 }
171 }
172
173 @ParameterizedTest
174 @CsvSource(delimiter = ':', value = {
175 // Don't permit module names to contain paths.
176 "modfoo/com/bar:One.class",
177 "modfoo/com:bar/One.class",
178 "modules/modfoo/com:foo/Alpha.class",
179 })
180 public void testResource_invalid(String modName, String resPath) throws IOException {
181 try (ImageReader reader = ImageReader.open(image)) {
182 assertThrows(IllegalArgumentException.class, () -> reader.containsResource(modName, resPath));
183 assertThrows(IllegalArgumentException.class, () -> reader.findResourceNode(modName, resPath));
184 }
185 }
186
187 @Test
188 public void testPackageDirectories() throws IOException {
189 try (ImageReader reader = ImageReader.open(image)) {
190 Node root = assertDir(reader, "/packages");
191 Set<String> pkgNames = root.getChildNames().collect(Collectors.toSet());
192 assertTrue(pkgNames.contains("/packages/com"));
193 assertTrue(pkgNames.contains("/packages/com.foo"));
194 assertTrue(pkgNames.contains("/packages/com.bar"));
195
196 // Even though no classes exist directly in the "com" package, it still
197 // creates a directory with links back to all the modules which contain it.
198 Set<String> comLinks = assertDir(reader, "/packages/com").getChildNames().collect(Collectors.toSet());
199 assertTrue(comLinks.contains("/packages/com/modfoo"));
200 assertTrue(comLinks.contains("/packages/com/modbar"));
201 }
202 }
203
204 @Test
205 public void testPackageLinks() throws IOException {
206 try (ImageReader reader = ImageReader.open(image)) {
207 Node moduleFoo = assertDir(reader, "/modules/modfoo");
208 Node moduleBar = assertDir(reader, "/modules/modbar");
209 assertSame(assertLink(reader, "/packages/com.foo/modfoo").resolveLink(), moduleFoo);
210 assertSame(assertLink(reader, "/packages/com.bar/modbar").resolveLink(), moduleBar);
211 }
212 }
213
214 private static ImageReader.Node assertNode(ImageReader reader, String name) throws IOException {
215 ImageReader.Node node = reader.findNode(name);
216 assertNotNull(node, "Could not find node: " + name);
217 return node;
218 }
219
220 private static ImageReader.Node assertDir(ImageReader reader, String name) throws IOException {
221 ImageReader.Node dir = assertNode(reader, name);
222 assertTrue(dir.isDirectory(), "Node was not a directory: " + name);
223 return dir;
224 }
225
226 private static ImageReader.Node assertLink(ImageReader reader, String name) throws IOException {
227 ImageReader.Node link = assertNode(reader, name);
228 assertTrue(link.isLink(), "Node was not a symbolic link: " + name);
229 return link;
230 }
231
232 private static void assertAbsent(ImageReader reader, String name) throws IOException {
233 assertNull(reader.findNode(name), "Should not be able to find node: " + name);
234 }
235
236 /// Builds a jimage file with the specified class entries. The classes in the built
237 /// image can be loaded and executed to return their names via `toString()` to confirm
238 /// the correct bytes were returned.
239 public static Path buildJImage(Map<String, List<String>> entries) {
240 Helper helper = getHelper();
241 Path outDir = helper.createNewImageDir("test");
242 JImageGenerator.JLinkTask jlink = JImageGenerator.getJLinkTask()
243 .modulePath(helper.defaultModulePath())
244 .output(outDir);
245
246 Path jarDir = helper.getJarDir();
247 entries.forEach((module, classes) -> {
248 JarBuilder jar = new JarBuilder(jarDir.resolve(module + ".jar").toString());
249 String moduleInfo = "module " + module + " {}";
250 jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo));
251
252 classes.forEach(fqn -> {
253 int lastDot = fqn.lastIndexOf('.');
254 String pkg = fqn.substring(0, lastDot);
255 String cls = fqn.substring(lastDot + 1);
256
257 String path = fqn.replace('.', '/') + ".class";
258 String source = String.format(
259 """
260 package %s;
261 public class %s {
262 public String toString() {
263 return "Class: %s";
264 }
265 }
266 """, pkg, cls, fqn);
267 jar.addEntry(path, InMemoryJavaCompiler.compile(fqn, source));
268 });
269 try {
270 jar.build();
271 } catch (IOException e) {
272 throw new RuntimeException(e);
273 }
274 jlink.addMods(module);
275 });
276 return jlink.call().assertSuccess().resolve("lib", "modules");
277 }
278
279 /// Returns the helper for building JAR and jimage files.
280 private static Helper getHelper() {
281 Helper helper;
282 try {
283 boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
284 helper = Helper.newHelper(isLinkableRuntime);
285 } catch (IOException e) {
286 throw new RuntimeException(e);
|
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 import jdk.internal.jimage.ImageReader;
25 import jdk.internal.jimage.ImageReader.Node;
26 import jdk.internal.jimage.PreviewMode;
27 import jdk.test.lib.compiler.InMemoryJavaCompiler;
28 import jdk.test.lib.util.JarBuilder;
29 import jdk.tools.jlink.internal.LinkableRuntimeImage;
30 import org.junit.jupiter.api.Assumptions;
31 import org.junit.jupiter.api.Test;
32 import org.junit.jupiter.api.TestInstance;
33 import org.junit.jupiter.params.ParameterizedTest;
34 import org.junit.jupiter.params.provider.CsvSource;
35 import org.junit.jupiter.params.provider.ValueSource;
36 import tests.Helper;
37 import tests.JImageGenerator;
38
39 import java.io.IOException;
40 import java.nio.file.Path;
41 import java.util.Arrays;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.stream.Collectors;
46
47 import static java.util.stream.Collectors.toSet;
48 import static org.junit.jupiter.api.Assertions.assertEquals;
49 import static org.junit.jupiter.api.Assertions.assertFalse;
50 import static org.junit.jupiter.api.Assertions.assertNotNull;
51 import static org.junit.jupiter.api.Assertions.assertNull;
52 import static org.junit.jupiter.api.Assertions.assertSame;
53 import static org.junit.jupiter.api.Assertions.assertThrows;
54 import static org.junit.jupiter.api.Assertions.assertTrue;
55 import static org.junit.jupiter.api.Assertions.fail;
56 import static org.junit.jupiter.api.Assumptions.assumeTrue;
57 import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
58
59 /*
60 * @test
61 * @summary Tests for ImageReader.
62 * @modules java.base/jdk.internal.jimage
63 * jdk.jlink/jdk.tools.jlink.internal
64 * jdk.jlink/jdk.tools.jimage
65 * @library /test/jdk/tools/lib
66 * /test/lib
67 * @build tests.*
68 * @run junit/othervm -esa ImageReaderTest
69 */
70
71 /// Using PER_CLASS lifecycle means the (expensive) image file is only built once.
72 /// There is no mutable test instance state to worry about.
73 @TestInstance(PER_CLASS)
74 public class ImageReaderTest {
75 // The '@' prefix marks the entry as a preview entry which will be placed in
76 // the '/modules/<module>/META-INF/preview/...' namespace.
77 private static final Map<String, List<String>> IMAGE_ENTRIES = Map.of(
78 "modfoo", Arrays.asList(
79 "com.foo.HasPreviewVersion",
80 "com.foo.NormalFoo",
81 "com.foo.bar.NormalBar",
82 // Replaces original class in preview mode.
83 "@com.foo.HasPreviewVersion",
84 // New class in existing package in preview mode.
85 "@com.foo.bar.IsPreviewOnly"),
86 "modbar", Arrays.asList(
87 "com.bar.One",
88 "com.bar.Two",
89 // Two new packages in preview mode (new symbolic links).
90 "@com.bar.preview.stuff.Foo",
91 "@com.bar.preview.stuff.Bar"),
92 "modgus", Arrays.asList(
93 // A second module with a preview-only empty package (preview).
94 "@com.bar.preview.other.Gus"));
95 private final Path image = buildJImage(IMAGE_ENTRIES);
96
97 @ParameterizedTest
98 @ValueSource(strings = {
99 "/",
100 "/modules",
101 "/modules/modfoo",
102 "/modules/modbar",
103 "/modules/modfoo/com",
104 "/modules/modfoo/com/foo",
105 "/modules/modfoo/com/foo/bar",
106 })
107 public void testModuleDirectories_expected(String name) throws IOException {
108 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
109 assertDir(reader, name);
110 }
111 }
112
113 @ParameterizedTest
114 @ValueSource(strings = {
115 "",
116 "//",
117 "/modules/",
118 "/modules/unknown",
119 "/modules/modbar/",
120 "/modules/modfoo//com",
121 "/modules/modfoo/com/",
122 })
123 public void testModuleNodes_absent(String name) throws IOException {
124 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
125 assertAbsent(reader, name);
126 }
127 }
128
129 @Test
130 public void testModuleResources() throws IOException {
131 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
132 assertNode(reader, "/modules/modfoo/com/foo/HasPreviewVersion.class");
133 assertNode(reader, "/modules/modbar/com/bar/One.class");
134
135 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
136 assertNonPreviewVersion(loader, "modfoo", "com.foo.HasPreviewVersion");
137 assertNonPreviewVersion(loader, "modfoo", "com.foo.NormalFoo");
138 assertNonPreviewVersion(loader, "modfoo", "com.foo.bar.NormalBar");
139 assertNonPreviewVersion(loader, "modbar", "com.bar.One");
140 }
141 }
142
143 @ParameterizedTest
144 @CsvSource(delimiter = ':', value = {
145 "modfoo:com/foo/HasPreviewVersion.class",
146 "modbar:com/bar/One.class",
147 })
148 public void testResource_present(String modName, String resPath) throws IOException {
149 for (PreviewMode mode : List.of(PreviewMode.ENABLED, PreviewMode.DISABLED)) {
150 try (ImageReader reader = ImageReader.open(image, mode)) {
151 assertNotNull(reader.findResourceNode(modName, resPath));
152 assertTrue(reader.containsResource(modName, resPath));
153
154 String canonicalNodeName = "/modules/" + modName + "/" + resPath;
155 Node node = reader.findNode(canonicalNodeName);
156 assertTrue(node != null && node.isResource());
157 }
158 }
159 }
160
161 @ParameterizedTest
162 @CsvSource(delimiter = ':', value = {
163 // Absolute resource names are not allowed.
164 "modfoo:/com/bar/One.class",
165 // Resource in wrong module.
166 "modfoo:com/bar/One.class",
167 "modbar:com/foo/HasPreviewVersion.class",
168 // Directories are not returned.
169 "modfoo:com/foo",
170 "modbar:com/bar",
171 // JImage entries exist for these, but they are not resources.
172 "modules:modfoo/com/foo/HasPreviewVersion.class",
173 "packages:com.foo/modfoo",
174 // Empty module names/paths do not find resources.
175 "'':modfoo/com/foo/HasPreviewVersion.class",
176 "modfoo:''",
177 // Make sure preview paths are excluded.
178 "modfoo:META-INF/preview/com/foo/HasPreviewVersion.class",
179 })
180 public void testResource_absent(String modName, String resPath) throws IOException {
181 for (PreviewMode mode : List.of(PreviewMode.ENABLED, PreviewMode.DISABLED)) {
182 try (ImageReader reader = ImageReader.open(image, mode)) {
183 assertNull(reader.findResourceNode(modName, resPath));
184 assertFalse(reader.containsResource(modName, resPath));
185
186 // Non-existent resources names should either not be found,
187 // or (in the case of directory nodes) not be resources.
188 String canonicalNodeName = "/modules/" + modName + "/" + resPath;
189 Node node = reader.findNode(canonicalNodeName);
190 assertTrue(node == null || !node.isResource());
191 }
192 }
193 }
194
195 @ParameterizedTest
196 @CsvSource(delimiter = ':', value = {
197 // Don't permit module names to contain paths.
198 "modfoo/com/bar:One.class",
199 "modfoo/com:bar/One.class",
200 "modules/modfoo/com:foo/HasPreviewVersion.class",
201 })
202 public void testResource_invalid(String modName, String resPath) throws IOException {
203 for (PreviewMode mode : List.of(PreviewMode.ENABLED, PreviewMode.DISABLED)) {
204 try (ImageReader reader = ImageReader.open(image, mode)) {
205 assertThrows(IllegalArgumentException.class, () -> reader.containsResource(modName, resPath));
206 assertThrows(IllegalArgumentException.class, () -> reader.findResourceNode(modName, resPath));
207 }
208 }
209 }
210
211 @Test
212 public void testPackageDirectories() throws IOException {
213 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
214 Node root = assertDir(reader, "/packages");
215 Set<String> pkgNames = root.getChildNames().collect(toSet());
216 assertTrue(pkgNames.contains("/packages/com"));
217 assertTrue(pkgNames.contains("/packages/com.foo"));
218 assertTrue(pkgNames.contains("/packages/com.bar"));
219
220 // Even though no classes exist directly in the "com" package, it still
221 // creates a directory with links back to all the modules which contain it.
222 Set<String> comLinks = assertDir(reader, "/packages/com").getChildNames().collect(Collectors.toSet());
223 assertTrue(comLinks.contains("/packages/com/modfoo"));
224 assertTrue(comLinks.contains("/packages/com/modbar"));
225 }
226 }
227
228 @Test
229 public void testPackageLinks() throws IOException {
230 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
231 Node moduleFoo = assertDir(reader, "/modules/modfoo");
232 Node moduleBar = assertDir(reader, "/modules/modbar");
233 assertSame(assertLink(reader, "/packages/com.foo/modfoo").resolveLink(), moduleFoo);
234 assertSame(assertLink(reader, "/packages/com.bar/modbar").resolveLink(), moduleBar);
235 }
236 }
237
238 @Test
239 public void testPreviewResources_disabled() throws IOException {
240 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
241 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
242
243 // No preview classes visible.
244 assertNonPreviewVersion(loader, "modfoo", "com.foo.HasPreviewVersion");
245 assertNonPreviewVersion(loader, "modfoo", "com.foo.NormalFoo");
246 assertNonPreviewVersion(loader, "modfoo", "com.foo.bar.NormalBar");
247
248 // NormalBar exists but IsPreviewOnly doesn't.
249 assertResource(reader, "modfoo", "com/foo/bar/NormalBar.class");
250 assertAbsent(reader, "/modules/modfoo/com/foo/bar/IsPreviewOnly.class");
251 assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar");
252 assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class");
253 }
254 }
255
256 @Test
257 public void testPreviewResources_enabled() throws IOException {
258 try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) {
259 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
260
261 // Preview version of classes either overwrite existing entries or are added to directories.
262 assertPreviewVersion(loader, "modfoo", "com.foo.HasPreviewVersion");
263 assertNonPreviewVersion(loader, "modfoo", "com.foo.NormalFoo");
264 assertNonPreviewVersion(loader, "modfoo", "com.foo.bar.NormalBar");
265 assertPreviewVersion(loader, "modfoo", "com.foo.bar.IsPreviewOnly");
266
267 // Both NormalBar and IsPreviewOnly exist (direct lookup and as child nodes).
268 assertResource(reader, "modfoo", "com/foo/bar/NormalBar.class");
269 assertResource(reader, "modfoo", "com/foo/bar/IsPreviewOnly.class");
270 assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar");
271 assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class", "IsPreviewOnly.class");
272 }
273 }
274
275 @Test
276 public void testPreviewOnlyPackages_disabled() throws IOException {
277 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
278 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
279
280 // No 'preview' package or anything inside it.
281 assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class");
282 assertAbsent(reader, "/modules/modbar/com/bar/preview");
283 assertAbsent(reader, "/modules/modbar/com/bar/preview/stuff/Foo.class");
284
285 // And no package link.
286 assertAbsent(reader, "/packages/com.bar.preview");
287 }
288 }
289
290 @Test
291 public void testPreviewOnlyPackages_enabled() throws IOException {
292 try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) {
293 ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet());
294
295 // In preview mode 'preview' package exists with preview only content.
296 assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class", "preview");
297 assertDirContents(reader, "/modules/modbar/com/bar/preview/stuff", "Foo.class", "Bar.class");
298 assertResource(reader, "modbar", "com/bar/preview/stuff/Foo.class");
299
300 // And package links exists.
301 assertDirContents(reader, "/packages/com.bar.preview", "modbar", "modgus");
302 }
303 }
304
305 @Test
306 public void testPreviewModeLinks_disabled() throws IOException {
307 try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) {
308 assertDirContents(reader, "/packages/com.bar", "modbar");
309 // Missing symbolic link and directory when not in preview mode.
310 assertAbsent(reader, "/packages/com.bar.preview");
311 assertAbsent(reader, "/packages/com.bar.preview.stuff");
312 assertAbsent(reader, "/modules/modbar/com/bar/preview");
313 assertAbsent(reader, "/modules/modbar/com/bar/preview/stuff");
314 }
315 }
316
317 @Test
318 public void testPreviewModeLinks_enabled() throws IOException {
319 try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) {
320 // In preview mode there is a new preview-only module visible.
321 assertDirContents(reader, "/packages/com.bar", "modbar", "modgus");
322 // And additional packages are present.
323 assertDirContents(reader, "/packages/com.bar.preview", "modbar", "modgus");
324 assertDirContents(reader, "/packages/com.bar.preview.stuff", "modbar");
325 assertDirContents(reader, "/packages/com.bar.preview.other", "modgus");
326 // And the preview-only content appears as we expect.
327 assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class", "preview");
328 assertDirContents(reader, "/modules/modbar/com/bar/preview", "stuff");
329 assertDirContents(reader, "/modules/modbar/com/bar/preview/stuff", "Foo.class", "Bar.class");
330 // In both modules in which it was added.
331 assertDirContents(reader, "/modules/modgus/com/bar", "preview");
332 assertDirContents(reader, "/modules/modgus/com/bar/preview", "other");
333 assertDirContents(reader, "/modules/modgus/com/bar/preview/other", "Gus.class");
334 }
335 }
336
337 @ParameterizedTest
338 @ValueSource(booleans = {false, true})
339 public void testPreviewEntriesAlwaysHidden(boolean previewMode) throws IOException {
340 try (ImageReader reader = ImageReader.open(image, previewMode ? PreviewMode.ENABLED : PreviewMode.DISABLED)) {
341 // The META-INF directory exists, but does not contain the preview directory.
342 Node dir = assertDir(reader, "/modules/modfoo/META-INF");
343 assertEquals(0, dir.getChildNames().filter(n -> n.endsWith("/preview")).count());
344 // Neither the preview directory, nor anything in it, can be looked-up directly.
345 assertAbsent(reader, "/modules/modfoo/META-INF/preview");
346 assertAbsent(reader, "/modules/modfoo/META-INF/preview/com/foo");
347 // HasPreviewVersion.class is a preview class in the test data, and thus appears in
348 // two places in the jimage). Ensure the preview version is always hidden.
349 String previewPath = "com/foo/HasPreviewVersion.class";
350 assertNode(reader, "/modules/modfoo/" + previewPath);
351 assertAbsent(reader, "/modules/modfoo/META-INF/preview/" + previewPath);
352 }
353 }
354
355 private static ImageReader.Node assertNode(ImageReader reader, String name) throws IOException {
356 ImageReader.Node node = reader.findNode(name);
357 assertNotNull(node, "Could not find node: " + name);
358 return node;
359 }
360
361 private static ImageReader.Node assertDir(ImageReader reader, String name) throws IOException {
362 ImageReader.Node dir = assertNode(reader, name);
363 assertTrue(dir.isDirectory(), "Node was not a directory: " + name);
364 return dir;
365 }
366
367 private static void assertDirContents(ImageReader reader, String name, String... expectedChildNames) throws IOException {
368 Node dir = assertDir(reader, name);
369 Set<String> localChildNames = dir.getChildNames()
370 .peek(s -> assertTrue(s.startsWith(name + "/")))
371 .map(s -> s.substring(name.length() + 1))
372 .collect(toSet());
373 assertEquals(
374 Set.of(expectedChildNames),
375 localChildNames,
376 String.format("Unexpected child names in directory '%s'", name));
377 }
378
379 private static void assertResource(ImageReader reader, String modName, String resPath) throws IOException {
380 assertTrue(reader.containsResource(modName, resPath), "Resource should exist: " + modName + "/" + resPath);
381 Node resNode = reader.findResourceNode(modName, resPath);
382 assertTrue(resNode.isResource(), "Node should be a resource: " + resNode.getName());
383 String nodeName = "/modules/" + modName + "/" + resPath;
384 assertEquals(nodeName, resNode.getName());
385 assertSame(resNode, reader.findNode(nodeName));
386 }
387
388 private static void assertNonPreviewVersion(ImageClassLoader loader, String module, String fqn) throws IOException {
389 assertEquals("Class: " + fqn, loader.loadAndGetToString(module, fqn));
390 }
391
392 private static void assertPreviewVersion(ImageClassLoader loader, String module, String fqn) throws IOException {
393 assertEquals("Preview: " + fqn, loader.loadAndGetToString(module, fqn));
394 }
395
396 private static ImageReader.Node assertLink(ImageReader reader, String name) throws IOException {
397 ImageReader.Node link = assertNode(reader, name);
398 assertTrue(link.isLink(), "Node should be a symbolic link: " + link.getName());
399 return link;
400 }
401
402 private static void assertAbsent(ImageReader reader, String name) throws IOException {
403 assertNull(reader.findNode(name), "Should not be able to find node: " + name);
404 }
405
406 /// Builds a jimage file with the specified class entries. The classes in the built
407 /// image can be loaded and executed to return their names via `toString()` to confirm
408 /// the correct bytes were returned.
409 public static Path buildJImage(Map<String, List<String>> entries) {
410 Helper helper = getHelper();
411 Path outDir = helper.createNewImageDir("test");
412 JImageGenerator.JLinkTask jlink = JImageGenerator.getJLinkTask()
413 .modulePath(helper.defaultModulePath())
414 .output(outDir);
415
416 Path jarDir = helper.getJarDir();
417 entries.forEach((module, classes) -> {
418 JarBuilder jar = new JarBuilder(jarDir.resolve(module + ".jar").toString());
419 String moduleInfo = "module " + module + " {}";
420 jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo));
421
422 classes.forEach(fqn -> {
423 boolean isPreviewEntry = fqn.startsWith("@");
424 if (isPreviewEntry) {
425 fqn = fqn.substring(1);
426 }
427 int lastDot = fqn.lastIndexOf('.');
428 String pkg = fqn.substring(0, lastDot);
429 String cls = fqn.substring(lastDot + 1);
430 String source = String.format(
431 """
432 package %s;
433 public class %s {
434 public String toString() {
435 return "%s: %s";
436 }
437 }
438 """, pkg, cls, isPreviewEntry ? "Preview" : "Class", fqn);
439 String path = (isPreviewEntry ? "META-INF/preview/" : "") + fqn.replace('.', '/') + ".class";
440 jar.addEntry(path, InMemoryJavaCompiler.compile(fqn, source));
441 });
442 try {
443 jar.build();
444 } catch (IOException e) {
445 throw new RuntimeException(e);
446 }
447 jlink.addMods(module);
448 });
449 return jlink.call().assertSuccess().resolve("lib", "modules");
450 }
451
452 /// Returns the helper for building JAR and jimage files.
453 private static Helper getHelper() {
454 Helper helper;
455 try {
456 boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
457 helper = Helper.newHelper(isLinkableRuntime);
458 } catch (IOException e) {
459 throw new RuntimeException(e);
|