| /* | |
| * Copyright (c) 2015, 2024, 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. | |
| */ | |
| package tests; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Properties; | |
| import java.lang.classfile.ClassFile; | |
| import java.lang.classfile.ClassHierarchyResolver; | |
| import java.util.function.Consumer; | |
| import jdk.internal.jimage.BasicImageReader; | |
| import jdk.internal.jimage.ImageLocation; | |
| /** | |
| * | |
| * JDK Modular image validator | |
| */ | |
| public class JImageValidator { | |
| private static final String[] dirs = {"bin", "lib"}; | |
| private final File rootDir; | |
| private final List<String> expectedLocations; | |
| private final String module; | |
| private long moduleExecutionTime; | |
| private long javaExecutionTime; | |
| private final List<String> unexpectedPaths; | |
| private final List<String> unexpectedFiles; | |
| private final String[] expectedFiles; | |
| public JImageValidator(String module, List<String> expectedLocations, | |
| File rootDir, | |
| List<String> unexpectedPaths, | |
| List<String> unexpectedFiles) throws Exception { | |
| this(module, expectedLocations, rootDir, unexpectedPaths, unexpectedFiles, null); | |
| } | |
| public JImageValidator(String module, List<String> expectedLocations, | |
| File rootDir, | |
| List<String> unexpectedPaths, | |
| List<String> unexpectedFiles, | |
| String[] expectedFiles) throws IOException { | |
| if (!rootDir.exists()) { | |
| throw new IOException("Image root dir not found " + | |
| rootDir.getAbsolutePath()); | |
| } | |
| this.expectedLocations = expectedLocations; | |
| this.rootDir = rootDir; | |
| this.module = module; | |
| this.unexpectedPaths = unexpectedPaths; | |
| this.unexpectedFiles = unexpectedFiles; | |
| this.expectedFiles = expectedFiles == null ? new String[0] : expectedFiles; | |
| } | |
| public void validate() throws IOException { | |
| for (String d : dirs) { | |
| File dir = new File(rootDir, d); | |
| if (!dir.isDirectory()) { | |
| throw new IOException("Invalid directory " + d); | |
| } | |
| } | |
| //check jimage file | |
| Path path = rootDir.toPath().resolve("lib").resolve("modules"); | |
| if (!Files.isRegularFile(path)) { | |
| throw new IOException(path + " No jimage file generated"); | |
| } | |
| // Check binary file | |
| File launcher = new File(rootDir, "bin" + File.separator + module); | |
| if (launcher.exists()) { | |
| ProcessBuilder builder = new ProcessBuilder("sh", launcher.getAbsolutePath()); | |
| long t = System.currentTimeMillis(); | |
| Process process = builder.inheritIO().start(); | |
| int ret = waitFor(process); | |
| moduleExecutionTime += System.currentTimeMillis() - t; | |
| if (ret != 0) { | |
| throw new IOException("Image " + module + " execution failed, check logs."); | |
| } | |
| } | |
| for (String f : expectedFiles) { | |
| File dd = new File(rootDir, f); | |
| if (!dd.exists()) { | |
| throw new IOException("Expected File " + f + " not found"); | |
| } | |
| } | |
| //Walk and check that unexpected files are not there | |
| try (java.util.stream.Stream<Path> stream = Files.walk(rootDir.toPath())) { | |
| stream.forEach((p) -> { | |
| for (String u : unexpectedFiles) { | |
| if (p.toString().equals(u)) { | |
| throw new RuntimeException("Seen unexpected path " + p); | |
| } | |
| } | |
| }); | |
| } | |
| File javaLauncher = new File(rootDir, "bin" + File.separator + | |
| (isWindows() ? "java.exe" : "java")); | |
| if (javaLauncher.exists()) { | |
| ProcessBuilder builder = new ProcessBuilder(javaLauncher.getAbsolutePath(), | |
| "-version"); | |
| long t = System.currentTimeMillis(); | |
| Process process = builder.start(); | |
| int ret = waitFor(process); | |
| javaExecutionTime += System.currentTimeMillis() - t; | |
| if (ret != 0) { | |
| throw new RuntimeException("java launcher execution failed, check logs."); | |
| } | |
| } else { | |
| throw new IOException("java launcher not found."); | |
| } | |
| // Check release file | |
| File release = new File(rootDir, "release"); | |
| if (!release.exists()) { | |
| throw new IOException("Release file not generated"); | |
| } else { | |
| Properties props = new Properties(); | |
| try (FileInputStream fs = new FileInputStream(release)) { | |
| props.load(fs); | |
| String s = props.getProperty("MODULES"); | |
| if (s == null) { | |
| throw new IOException("No MODULES property in release"); | |
| } | |
| if (!s.contains(module)) { | |
| throw new IOException("Module not found in release file " + s); | |
| } | |
| } | |
| } | |
| } | |
| private int waitFor(Process process) { | |
| try { | |
| return process.waitFor(); | |
| } catch (InterruptedException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| private static boolean isWindows() { | |
| return System.getProperty("os.name").startsWith("Windows"); | |
| } | |
| public static void validate(Path jimage, List<String> expectedLocations, | |
| List<String> unexpectedPaths) throws IOException { | |
| validate(jimage, expectedLocations, unexpectedPaths, _ -> {}); | |
| } | |
| public static void validate(Path jimage, List<String> expectedLocations, | |
| List<String> unexpectedPaths, Consumer<byte[]> classChecker) throws IOException { | |
| BasicImageReader reader = BasicImageReader.open(jimage); | |
| // Validate expected locations | |
| List<String> seenLocations = new ArrayList<>(); | |
| for (String loc : expectedLocations) { | |
| ImageLocation il = reader.findLocation(loc); | |
| if (il == null) { | |
| throw new IOException("Location " + loc + " not present in " + jimage); | |
| } | |
| } | |
| seenLocations.addAll(expectedLocations); | |
| for (String s : reader.getEntryNames()) { | |
| if (s.endsWith(".class") && !s.endsWith("module-info.class")) { | |
| ImageLocation il = reader.findLocation(s); | |
| try { | |
| byte[] r = reader.getResource(il); | |
| if(r == null) { | |
| System.out.println("IL, compressed " + | |
| il.getCompressedSize() + " uncompressed " + | |
| il.getUncompressedSize()); | |
| throw new IOException("NULL RESOURCE " + s); | |
| } | |
| readClass(r); | |
| classChecker.accept(r); | |
| } catch (IOException ex) { | |
| System.err.println(s + " ERROR " + ex); | |
| throw ex; | |
| } | |
| } | |
| if (seenLocations.contains(s)) { | |
| seenLocations.remove(s); | |
| } | |
| for(String p : unexpectedPaths) { | |
| if (s.equals(p)) { | |
| throw new IOException("Seen unexpected path " + s); | |
| } | |
| } | |
| } | |
| if (!seenLocations.isEmpty()) { | |
| throw new IOException("ImageReader did not return " + seenLocations); | |
| } | |
| } | |
| public long getJavaLauncherExecutionTime() { | |
| return javaExecutionTime; | |
| } | |
| public long getModuleLauncherExecutionTime() { | |
| return moduleExecutionTime; | |
| } | |
| public static void readClass(byte[] clazz) throws IOException { | |
| var errors = ClassFile.of( | |
| //resolution of all classes as interfaces cancels assignability verification | |
| ClassFile.ClassHierarchyResolverOption.of(cls -> ClassHierarchyResolver.ClassHierarchyInfo.ofInterface())) | |
| .verify(clazz); | |
| if (!errors.isEmpty()) { | |
| var itr = errors.iterator(); | |
| var thrown = itr.next(); | |
| itr.forEachRemaining(thrown::addSuppressed); | |
| throw new IOException(thrown); | |
| } | |
| } | |
| } |