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 * @summary Unit test for libjimage JIMAGE_Open/Read/Close
27 * @modules java.base/jdk.internal.jimage
28 */
29
30import java.io.File;
31import java.io.FileWriter;
32import java.io.IOException;
33import java.nio.ByteBuffer;
34import java.nio.ByteOrder;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.util.Arrays;
39
40import jdk.internal.jimage.BasicImageReader;
41import jdk.internal.jimage.ImageReader;
42import jdk.internal.jimage.ImageLocation;
43
44import org.testng.annotations.DataProvider;
45import org.testng.annotations.Optional;
46import org.testng.annotations.Parameters;
47import org.testng.annotations.Test;
48import org.testng.Assert;
49import org.testng.TestNG;
50
51@Test
52public class JImageReadTest {
53
54    static String javaHome = System.getProperty("java.home");
55    static Path imageFile = Paths.get(javaHome, "lib", "modules");
56
57    @DataProvider(name="classes")
58    static Object[][] loadClasses() {
59        return new Object[][] {
60                {"java.base", "java/lang/String.class"},
61                {"java.base", "java/lang/Object.class"},
62                {"java.base", "sun/reflect/generics/tree/TypeArgument.class"},
63                {"java.base", "sun/net/www/content-types.properties"},
64                {"java.logging", "java/util/logging/Logger.class"},
65                {"java.base", "java/NOSUCHCLASS/yyy.class"},    // non-existent
66                {"NOSUCHMODULE", "java/lang/Class.class"},    // non-existent
67        };
68    }
69
70    /**
71     * Test a class is correctly accessible from the image in a module.
72     *
73     * @param moduleName the module name
74     * @param className the classname
75     * @throws Exception is thrown if there is a test error
76     */
77    @Test(dataProvider="classes")
78    public static void test1_ReadClasses(String moduleName, String className) throws Exception {
79        final int classMagic = 0xCAFEBABE;
80
81        if (!Files.exists(imageFile)) {
82            System.out.printf("Test skipped; no jimage file");
83            return;
84        }
85
86        BasicImageReader reader = BasicImageReader.open(imageFile);
87        Assert.assertTrue(reader != null, "JIMAGE_Open failed: " + imageFile);
88
89        ImageLocation location = reader.findLocation(moduleName, className);
90
91        if (location != null && !location.verify("/" + moduleName + "/" + className)) {
92            location = null;
93        }
94
95        long size = location != null ? location.getUncompressedSize() : 0;
96
97        System.out.printf("reading: module: %s, path: %s, size: %d%n",
98                moduleName, className, size);
99        if (moduleName.contains("NOSUCH") || className.contains("NOSUCH")) {
100            Assert.assertTrue(location == null,
101                    "location found for non-existing module: "
102                    + moduleName
103                    + ", or class: " + className);
104            return;         // no more to test for non-existing class
105        } else {
106            Assert.assertTrue(location != null, "location not found: " + className);
107            Assert.assertTrue(size > 0, "size of should be > 0: " + className);
108        }
109
110        // positive: read whole class
111        ByteBuffer buffer = reader.getResourceBuffer(location);
112        Assert.assertTrue(buffer != null, "bytes read not equal bytes requested");
113
114        if (className.endsWith(".class")) {
115            int m = buffer.getInt();
116            Assert.assertEquals(m, classMagic, "Classfile has bad magic number");
117        }
118
119        reader.close();
120    }
121
122    /**
123     * For all the resource names, check the name and approximate count.
124     *
125     * @throws IOException thrown if an error occurs
126     */
127    @Test
128    static void test2_ImageResources() throws IOException {
129        if (!Files.exists(imageFile)) {
130            System.out.printf("Test skipped; no jimage file");
131            return;
132        }
133
134        BasicImageReader reader = BasicImageReader.open(imageFile);
135        Assert.assertTrue(reader != null, "JIMAGE_Open failed: " + imageFile);
136
137        String[] names = reader.getEntryNames();
138
139        // Repeat with count available
140        int count = names.length;
141
142        System.out.printf(" count: %d, a class: %s\n", count, names[0]);
143
144        int minEntryCount = 16000;
145        Assert.assertTrue(minEntryCount < count,
146                "unexpected count of entries, count: " + count
147                        + ", min: " + minEntryCount);
148        for (int i = 0; i < count; i++) {
149            checkFullName(names[i]);
150        }
151
152        reader.close();
153    }
154
155    static void checkFullName(String path) {
156        if (path.startsWith("/packages") || path.startsWith("/modules")) {
157            return;
158        }
159
160        int next = 0;
161        String m = null;
162        String p = null;
163        String b = null;
164        String e = null;
165        if (path.startsWith("/")) {
166            next = path.indexOf('/', 1);
167            m = path.substring(1, next);
168            next = next + 1;
169        }
170        int lastSlash = path.lastIndexOf('/');
171        if (lastSlash > next) {
172            // has a parent
173            p = path.substring(next, lastSlash);
174            next = lastSlash + 1;
175        }
176        int period = path.indexOf('.', next);
177        if (period > next) {
178            b = path.substring(next, period);
179            e = path.substring(period + 1);
180        } else {
181            b = path.substring(next);
182        }
183        Assert.assertNotNull(m, "module must be non-empty");
184        Assert.assertNotNull(b, "base name must be non-empty");
185    }
186
187    /**
188     * Verify that all of the resource names from BasicImageReader
189     * match those returned from the native JIMAGE_Resources iterator.
190     * Names that start with /modules, /packages, and bootmodules.jdata
191     * must appear in the names from JIMAGE_Resource iterator;
192     * from the BasicImageReader they are ignored.
193     */
194    @Test
195    static void test3_verifyNames() {
196        if (!Files.exists(imageFile)) {
197            System.out.printf("Test skipped; no jimage file");
198            return;
199        }
200
201        try {
202            String[] names = BasicImageReader_Names();
203            //writeNames("/tmp/basic-names.txt", names);              // debug
204
205            // Read all the names from the native JIMAGE API
206            String[] nativeNames = JIMAGE_Names();
207            //writeNames("/tmp/native-names.txt", nativeNames);       // debug
208
209
210            int modCount = 0;
211            int pkgCount = 0;
212            int otherCount = 0;
213            for (String n : nativeNames) {
214                if (n.startsWith("/modules/")) {
215                    modCount++;
216                } else if (n.startsWith("/packages/")) {
217                    pkgCount++;
218                } else {
219                    otherCount++;
220                }
221            }
222            System.out.printf("native name count: %d, modCount: %d, pkgCount: %d, otherCount: %d%n",
223                    names.length, modCount, pkgCount, otherCount);
224
225            // Sort and merge the two arrays.  Every name should appear exactly twice.
226            Arrays.sort(names);
227            Arrays.sort(nativeNames);
228            String[] combined = Arrays.copyOf(names, nativeNames.length + names.length);
229            System.arraycopy(nativeNames,0, combined, names.length, nativeNames.length);
230            Arrays.sort(combined);
231            int missing = 0;
232            for (int i = 0; i < combined.length; i++) {
233                String s = combined[i];
234                if (isMetaName(s)) {
235                    // Ignore /modules and /packages in BasicImageReader names
236                    continue;
237                }
238
239                if (i < combined.length - 1 && s.equals(combined[i + 1])) {
240                    i++;        // string appears in both java and native
241                    continue;
242                }
243
244                missing++;
245                int ndx = Arrays.binarySearch(names, s);
246                String which = (ndx >= 0) ? "java BasicImageReader" : "native JIMAGE_Resources";
247                System.out.printf("Missing Resource: %s found only via %s%n", s, which);
248            }
249            Assert.assertEquals(missing, 0, "Resources missing");
250
251        } catch (IOException ioe) {
252            Assert.fail("I/O exception", ioe);
253        }
254    }
255
256    /**
257     * Return true if the name is one of the meta-data names
258     * @param name a name
259     * @return return true if starts with either /packages or /modules
260     */
261    static boolean isMetaName(String name) {
262        return name.startsWith("/modules")
263                || name.startsWith("/packages")
264                || name.startsWith("META-INF")
265                || name.equals("bootmodules.jdata");
266    }
267
268    /**
269     * Return all of the names from BasicImageReader.
270     * @return the names returned from BasicImageReader
271     */
272    static String[] BasicImageReader_Names() throws IOException {
273        String[] names = null;
274        try (BasicImageReader reader = BasicImageReader.open(imageFile)) {
275            names = reader.getEntryNames();
276        } catch (IOException ioe) {
277            Assert.fail("I/O exception", ioe);
278        }
279        return names;
280    }
281
282    /**
283     * Returns an array of all of the names returned from JIMAGE_Resources
284     */
285    static String[] JIMAGE_Names() throws IOException {
286
287        BasicImageReader reader = BasicImageReader.open(imageFile);
288        Assert.assertNotNull(reader, "JIMAGE_Open failed: " + imageFile);
289
290        String[] names = reader.getEntryNames();
291
292        reader.close();
293
294        return names;
295    }
296
297    // Write an array of names to a file for debugging
298    static void writeNames(String fname, String[] names) throws IOException {
299        try (FileWriter wr = new FileWriter(new File(fname))) {
300            for (String s : names) {
301                wr.write(s);
302                wr.write("\n");
303            }
304
305        }
306        System.out.printf(" %s: %d names%n", fname, names.length);
307    }
308
309    //@Test
310    static void test4_nameTooLong() throws IOException {
311        long[] size = new long[1];
312        String moduleName = "FictiousModuleName";
313        String className = String.format("A%09999d", 1);
314
315        BasicImageReader reader = BasicImageReader.open(imageFile);
316        Assert.assertNotNull(reader, "JIMAGE_Open failed: " + imageFile);
317
318        String name = "/" + moduleName + "/" + className;
319        ImageLocation location = reader.findLocation(name);
320
321        if (location != null && !location.verify(name)) {
322            location = null;
323        }
324        Assert.assertTrue(location == null, "Too long name should have failed");
325
326        reader.close();
327    }
328
329    /**
330     * Verify that the ImageReader returned by ImageReader.open has the
331     * the requested endianness or fails with an IOException if not.
332     */
333    @Test
334    static void test5_imageReaderEndianness() throws IOException {
335        ImageReader nativeReader = ImageReader.open(imageFile);
336        Assert.assertEquals(nativeReader.getByteOrder(), ByteOrder.nativeOrder());
337
338        try {
339            ImageReader leReader = ImageReader.open(imageFile, ByteOrder.LITTLE_ENDIAN);
340            Assert.assertEquals(leReader.getByteOrder(), ByteOrder.LITTLE_ENDIAN);
341            leReader.close();
342        } catch (IOException io) {
343            // IOException expected if LITTLE_ENDIAN not the nativeOrder()
344            Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.LITTLE_ENDIAN);
345        }
346
347        try {
348            ImageReader beReader = ImageReader.open(imageFile, ByteOrder.BIG_ENDIAN);
349            Assert.assertEquals(beReader.getByteOrder(), ByteOrder.BIG_ENDIAN);
350            beReader.close();
351        } catch (IOException io) {
352            // IOException expected if LITTLE_ENDIAN not the nativeOrder()
353            Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.BIG_ENDIAN);
354        }
355
356        nativeReader.close();
357    }
358    // main method to run standalone from jtreg
359
360    @Test(enabled=false)
361    @Parameters({"x"})
362    @SuppressWarnings("raw_types")
363    public static void main(@Optional String[] args) {
364        Class<?>[] testclass = { JImageReadTest.class};
365        TestNG testng = new TestNG();
366        testng.setTestClasses(testclass);
367        testng.run();
368    }
369
370}
371