blob: 910c35aec4a70b94ef8fdb160098c0205a00ac41 [file] [log] [blame]
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @modules java.base/jdk.internal.jimage
* @summary Unit test for libjimage JIMAGE_Open/Read/Close
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import jdk.internal.jimage.BasicImageReader;
import jdk.internal.jimage.ImageNativeSubstrate;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.testng.TestNG;
@Test
public class JImageReadTest {
static String javaHome = System.getProperty("java.home");
static String imageFile = javaHome + File.separator + "lib"
+ File.separator + "modules" + File.separator
+ "bootmodules.jimage";
@DataProvider(name="classes")
static Object[][] loadClasses() {
return new Object[][] {
{"java.base", "java/lang/String.class"},
{"java.base", "java/lang/Object.class"},
{"java.base", "sun/reflect/generics/tree/TypeArgument.class"},
{"jdk.jdeps", "com/sun/tools/javap/StackMapWriter$StackMapBuilder.class"},
{"jdk.hotspot.agent", "sa.properties"},
{"java.logging", "java/util/logging/Logger.class"},
{"java.base", "java/NOSUCHCLASS/yyy.class"}, // non-existent
{"NOSUCHMODULE", "java/lang/Class.class"}, // non-existent
};
}
@DataProvider(name="packages")
static Object[][] loadPackages() {
return new Object[][] {
{"java.base", "java/lang"},
{"java.base", "java/io"},
{"java.logging", "java/util/logging"},
};
}
/**
* Test a class is correctly accessible from the image in a module.
*
* @param moduleName the module name
* @param className the classname
* @throws Exception is thrown if there is a test error
*/
@Test(dataProvider="classes")
public static void test1_ReadClasses(String moduleName, String className) throws Exception {
final int classMagic = 0xCAFEBABE;
final long NOT_FOUND = 0L;
if (!(new File(imageFile)).exists()) {
System.out.printf("Test skipped; no jimage file");
return;
}
long jimageHandle = ImageNativeSubstrate.JIMAGE_Open(imageFile);
Assert.assertTrue(jimageHandle != 0, "JIMAGE_Open failed: id: " + jimageHandle);
long[] sizeArray = new long[1];
long locationHandle =
ImageNativeSubstrate.JIMAGE_FindResource(jimageHandle,
moduleName, "9.0", className, sizeArray);
long size = sizeArray[0];
System.out.printf("reading: module: %s, path: %s, handle: %16x, " +
"location: %d, size: %d%n",
moduleName, className, jimageHandle, locationHandle, size);
if (moduleName.contains("NOSUCH") || className.contains("NOSUCH")) {
Assert.assertEquals(locationHandle, NOT_FOUND,
"location found for non-existing module: "
+ moduleName
+ ", or class: " + className);
return; // no more to test for non-existing class
} else {
Assert.assertTrue(locationHandle != NOT_FOUND, "location not found: " + className);
Assert.assertTrue(size > 0, "size of should be > 0: " + className);
}
// positive: read whole class
ByteBuffer buffer = ByteBuffer.allocate((int)size);
long actual = ImageNativeSubstrate.JIMAGE_GetResource(jimageHandle,
locationHandle, buffer.array(), size);
Assert.assertEquals(actual, size, "bytes read not equal bytes requested");
if (className.endsWith(".class")) {
int m = buffer.getInt();
Assert.assertEquals(m, classMagic, "Classfile has bad magic number");
}
// Read less than the size of the artifact
buffer.rewind();
Arrays.fill(buffer.array(), (byte)0xc0);
long sizeExpected = size - 10;
actual = ImageNativeSubstrate.JIMAGE_GetResource(jimageHandle,
locationHandle, buffer.array(), sizeExpected);
Assert.assertEquals(actual, sizeExpected, "bytes read not equal bytes requested");
if (className.endsWith(".class")) {
int m1 = buffer.getInt();
Assert.assertEquals(m1, classMagic, "Read operation succeeded but has bad magic number");
}
ImageNativeSubstrate.JIMAGE_Close(jimageHandle);
}
/**
* For all the resource names, check the name and approximate count.
*
* @throws IOException thrown if an error occurs
*/
@Test
static void test2_ImageResources() throws IOException {
if (!(new File(imageFile)).exists()) {
System.out.printf("Test skipped; no jimage file");
return;
}
long jimageHandle = ImageNativeSubstrate.JIMAGE_Open(imageFile);
Assert.assertTrue(jimageHandle != 0, "JIMAGE_Open failed: id: " + jimageHandle);
String[] names = new String[4096];
int max = ImageNativeSubstrate.JIMAGE_Resources(jimageHandle,
names);
// Repeat with count available
names = new String[max + 1];
int count = ImageNativeSubstrate.JIMAGE_Resources(jimageHandle,
names);
System.out.printf(" count: %d, a class: %s\n", count, names[0]);
Assert.assertTrue(max > 31000,
"missing entries, should be more than 31000, reported: " + count);
Assert.assertTrue(count == max,
"unexpected count of entries, count: " + count
+ ", max: " + max);
for (int i = 0; i < count; i++) {
checkFullName(names[i]);
}
ImageNativeSubstrate.JIMAGE_Close(jimageHandle);
}
/**
* Tests that a package exists and correctly mapped to the module.
*
* @param moduleName the module name
* @param packageName the package name
* @throws IOException thrown if an error occurs
*/
@Test(dataProvider="packages")
static void test3_PackageToModule(String moduleName, String packageName) throws IOException {
if (!(new File(imageFile)).exists()) {
System.out.printf("Test skipped; no jimage file");
return;
}
long jimageHandle = ImageNativeSubstrate.JIMAGE_Open(imageFile);
Assert.assertTrue(jimageHandle != 0, "JIMAGE_Open failed: id: " + jimageHandle);
String result = ImageNativeSubstrate.JIMAGE_PackageToModule(jimageHandle, packageName);
System.out.printf(" package: %s, module: %s%n", packageName, result);
Assert.assertEquals(result, moduleName, "wrong module for package: " + packageName);
ImageNativeSubstrate.JIMAGE_Close(jimageHandle);
}
static void checkFullName(String path) {
int next = 0;
String m = null;
String p = null;
String b = null;
String e = null;
if (path.startsWith("/")) {
next = path.indexOf('/', 1);
m = path.substring(1, next);
next = next + 1;
}
int lastSlash = path.lastIndexOf('/');
if (lastSlash > next) {
// has a parent
p = path.substring(next, lastSlash);
next = lastSlash + 1;
}
int period = path.indexOf('.', next);
if (period > next) {
b = path.substring(next, period);
e = path.substring(period + 1);
} else {
b = path.substring(next);
}
Assert.assertNotNull(m, "module must be non-empty");
Assert.assertNotNull(b, "base name must be non-empty");
}
/**
* Verify that all of the resource names from BasicImageReader
* match those returned from the native JIMAGE_Resources iterator.
* Names that start with /modules, /packages, and bootmodules.jdata
* must appear in the names from JIMAGE_Resource iterator;
* from the BasicImageReader they are ignored.
*/
@Test
static void test4_verifyNames() {
if (!(new File(imageFile)).exists()) {
System.out.printf("Test skipped; no jimage file");
return;
}
try {
String[] names = BasicImageReader_Names();
//writeNames("/tmp/basic-names.txt", names); // debug
// Read all the names from the native JIMAGE API
String[] nativeNames = JIMAGE_Names();
//writeNames("/tmp/native-names.txt", nativeNames); // debug
int modCount = 0;
int pkgCount = 0;
int otherCount = 0;
for (String n : nativeNames) {
if (n.startsWith("/modules/")) {
modCount++;
} else if (n.startsWith("/packages/")) {
pkgCount++;
} else {
otherCount++;
}
}
System.out.printf("native name count: %d, modCount: %d, pkgCount: %d, otherCount: %d%n",
names.length, modCount, pkgCount, otherCount);
Assert.assertEquals(modCount, 0, "JIMAGE_ResourceIterator should not return any '/modules' paths");
Assert.assertEquals(pkgCount, 0, "JIMAGE_ResourceIterator should not return any '/packages' paths");
// Sort and merge the two arrays. Every name should appear exactly twice.
Arrays.sort(names);
Arrays.sort(nativeNames);
String[] combined = Arrays.copyOf(names, nativeNames.length + names.length);
System.arraycopy(nativeNames,0, combined, names.length, nativeNames.length);
Arrays.sort(combined);
int missing = 0;
for (int i = 0; i < combined.length; i++) {
String s = combined[i];
if (isMetaName(s)) {
// Ignore /modules and /packages in BasicImageReader names
continue;
}
if (i < combined.length - 1 && s.equals(combined[i + 1])) {
i++; // string appears in both java and native
continue;
}
missing++;
int ndx = Arrays.binarySearch(names, s);
String which = (ndx >= 0) ? "java BasicImageReader" : "native JIMAGE_Resources";
System.out.printf("Missing Resource: %s found only via %s%n", s, which);
}
Assert.assertEquals(missing, 0, "Resources missing");
} catch (IOException ioe) {
Assert.fail("I/O exception", ioe);
}
}
/**
* Return true if the name is one of the meta-data names
* @param name a name
* @return return true if starts with either /packages or /modules
*/
static boolean isMetaName(String name) {
return name.startsWith("/modules")
|| name.startsWith("/packages")
|| name.equals("bootmodules.jdata");
}
/**
* Return all of the names from BasicImageReader.
* @return the names returned from BasicImageReader
*/
static String[] BasicImageReader_Names() throws IOException {
String[] names = null;
try (BasicImageReader reader = BasicImageReader.open(imageFile)) {
names = reader.getEntryNames();
} catch (IOException ioe) {
Assert.fail("I/O exception", ioe);
}
return names;
}
/**
* Returns an array of all of the names returned from JIMAGE_Resources
*/
static String[] JIMAGE_Names() throws IOException {
long jimageHandle = ImageNativeSubstrate.JIMAGE_Open(imageFile);
Assert.assertTrue(jimageHandle != 0, "JIMAGE_Open failed: id: " + jimageHandle);
String[] names = new String[50000];
int max = ImageNativeSubstrate.JIMAGE_Resources(jimageHandle, names);
if (max > names.length) {
// Not all names fit, repeat with correct size
names = new String[max];
max = ImageNativeSubstrate.JIMAGE_Resources(jimageHandle, names);
} else {
names = Arrays.copyOf(names, max);
}
ImageNativeSubstrate.JIMAGE_Close(jimageHandle);
return names;
}
// Write an array of names to a file for debugging
static void writeNames(String fname, String[] names) throws IOException {
try (FileWriter wr = new FileWriter(new File(fname))) {
for (String s : names) {
wr.write(s);
wr.write("\n");
}
}
System.out.printf(" %s: %d names%n", fname, names.length);
}
// main method to run standalone from jtreg
@Test(enabled=false)
@Parameters({"x"})
@SuppressWarnings("raw_types")
public static void main(@Optional String[] args) {
Class<?>[] testclass = { JImageReadTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
}