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 import java.nicl.metadata.C; 25 import java.nicl.metadata.CallingConvention; 26 import java.nicl.metadata.Header; 27 import java.nicl.metadata.NativeType; 28 import java.nicl.metadata.Offset; 29 import java.nio.file.Files; 30 import java.io.ByteArrayInputStream; 31 import java.io.ByteArrayOutputStream; 32 import java.io.File; 33 import java.io.IOException; 34 import java.io.PrintWriter; 35 import java.lang.annotation.Annotation; 36 import java.lang.reflect.Method; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.util.Arrays; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.jar.JarEntry; 45 import java.util.jar.JarInputStream; 46 import java.util.jar.JarOutputStream; 47 import javax.tools.JavaCompiler; 48 import javax.tools.StandardJavaFileManager; 49 import javax.tools.ToolProvider; 50 import jdk.internal.org.objectweb.asm.ClassReader; 51 import jdk.internal.org.objectweb.asm.tree.ClassNode; 52 import jdk.internal.org.objectweb.asm.util.ASMifier; 53 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; 54 import jdk.internal.org.objectweb.asm.util.Textifier; 55 import jdk.internal.org.objectweb.asm.util.TraceClassVisitor; 56 import static org.testng.Assert.assertEquals; 57 import static org.testng.Assert.assertNotNull; 58 import static org.testng.Assert.assertTrue; 59 import static org.testng.Assert.fail; 60 import org.testng.annotations.BeforeClass; 61 import org.testng.annotations.DataProvider; 62 import org.testng.annotations.Factory; 63 import org.testng.annotations.Test; 64 import com.sun.tools.jextract.*; 65 66 /* 67 * @test 68 * @summary Main test runner created all cases 69 * @modules java.base/jdk.internal.org.objectweb.asm 70 * @modules java.base/jdk.internal.org.objectweb.asm.tree 71 * @modules java.base/jdk.internal.org.objectweb.asm.util 72 * @modules jdk.jextract/com.sun.tools.jextract 73 * @build InMemoryFileManager 74 * @run testng Runner 75 */ 76 public class Runner { 77 private final Path nativeSrc; 78 private final Path[] javaSrcFiles; 79 private final Context ctx; 80 private final String pkg; 81 82 private InMemoryFileManager<StandardJavaFileManager> mfm; 83 private ClassLoader expectedCL; 84 private Map<String, byte[]> actualClz; 85 private ClassLoader actualCL; 86 private Object[][] clz_data; 87 88 public Runner(Path nativeSrc, String pkg, Path[] javaSrcFiles) { 89 ctx = Context.newInstance(); 90 this.nativeSrc = nativeSrc; 91 this.pkg = pkg; 92 this.javaSrcFiles = javaSrcFiles; 93 } 94 95 private Map<String, byte[]> extract(String pkg) throws IOException { 96 if (!Files.isReadable(nativeSrc)) { 97 throw new IllegalArgumentException("Cannot read the file: " + nativeSrc); 98 } 99 Path p = nativeSrc.toAbsolutePath(); 100 ctx.usePackageForFolder(p.getParent(), pkg); 101 ctx.addSource(p); 102 ctx.parse(AsmCodeFactory::new); 103 return ctx.collectClasses(pkg); 104 } 105 106 private InMemoryFileManager<StandardJavaFileManager> compileJavaCode() { 107 JavaCompiler cl = ToolProvider.getSystemJavaCompiler(); 108 StandardJavaFileManager sfm = cl.getStandardFileManager(null, null, null); 109 InMemoryFileManager<StandardJavaFileManager> fm = new InMemoryFileManager<>(sfm); 110 JavaCompiler.CompilationTask task = cl.getTask(null, fm, null, null, 111 null, sfm.getJavaFileObjects(javaSrcFiles)); 112 task.call(); 113 return fm; 114 } 115 116 @Test 117 public void testJarManifest() throws IOException { 118 // Get the jar 119 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 120 ctx.collectJarFile(new JarOutputStream(bos), pkg); 121 122 System.out.println("Jar built, verifying..."); 123 JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bos.toByteArray())); 124 125 // List all classes in the jar 126 Set<String> files = new HashSet<>(); 127 for (JarEntry e = jis.getNextJarEntry(); e != null; e = jis.getNextJarEntry()) { 128 if (e.isDirectory()) { 129 continue; 130 } 131 String name = e.getName(); 132 if (! name.endsWith(".class")) { 133 // Should not have file not class files 134 System.err.println("Warning: unexpected file " + name); 135 } 136 name = name.substring(0, name.length() - 6); 137 files.add(name.replace(File.separatorChar, '.')); 138 } 139 140 assertEquals(files, mfm.listClasses()); 141 } 142 143 private void verifyAnnotationC(C actual, C expected) { 144 // Only check the filename, not full path 145 assertNotNull(actual); 146 assertNotNull(expected); 147 assertEquals(Paths.get(actual.file()).getFileName(), 148 Paths.get(expected.file()).getFileName()); 149 assertEquals(actual.line(), expected.line()); 150 assertEquals(actual.column(), expected.column()); 151 } 152 153 private void verifyMethodAnnotation(Method actual, Method expected) { 154 Annotation[] aa = actual.getAnnotations(); 155 Annotation[] ea = expected.getAnnotations(); 156 // allow test case has extra annotation used by test case 157 assertTrue(ea.length >= aa.length); 158 159 for (Annotation a: ea) { 160 if (a instanceof C) { 161 verifyAnnotationC(actual.getAnnotation(C.class), (C) a); 162 } else if (a instanceof NativeType) { 163 assertEquals(actual.getAnnotation(NativeType.class), (NativeType) a); 164 } else if (a instanceof Offset) { 165 assertEquals(actual.getAnnotation(Offset.class), (Offset) a); 166 } else if (a instanceof CallingConvention) { 167 assertNotNull(actual.getAnnotation(CallingConvention.class)); 168 } 169 } 170 } 171 172 @Test(dataProvider = "classes") 173 public void testMethods(Class<?> actual, Class<?> expected) { 174 Method[] am = actual.getMethods(); 175 Method[] em = expected.getMethods(); 176 HashMap<String, Method> ams = new HashMap<>(); 177 for (Method m: am) { 178 ams.put(m.toGenericString(), m); 179 } 180 HashMap<String, Method> ems = new HashMap<>(); 181 for (Method m: em) { 182 String sig = m.toGenericString(); 183 Method ma = ams.remove(sig); 184 if (ma == null) { 185 System.out.println("Missing " + sig); 186 ems.put(m.toGenericString(), m); 187 } else { 188 try { 189 verifyMethodAnnotation(ma, m); 190 } catch (Throwable t) { 191 fail("Failed method " + sig, t); 192 } 193 } 194 } 195 ams.keySet().forEach((sig) -> { 196 System.out.println("Got: " + sig); 197 }); 198 assertTrue(ams.isEmpty()); 199 assertTrue(ems.isEmpty()); 200 } 201 202 @Test(dataProvider = "classes") 203 public void testAnnotations(Class<?> actual, Class<?> expected) { 204 Annotation[] aa = actual.getAnnotations(); 205 Annotation[] ea = expected.getAnnotations(); 206 // allow test case has extra annotation used by test case 207 assertTrue(ea.length >= aa.length); 208 209 if (actual.getName().contains("$")) { 210 assertTrue(actual.getName().contains("$")); 211 assertTrue(expected.isMemberClass()); 212 assertTrue(actual.isMemberClass()); 213 NativeType ant = actual.getAnnotation(NativeType.class); 214 assertNotNull(ant); 215 assertEquals(ant, expected.getAnnotation(NativeType.class)); 216 C ac = actual.getAnnotation(C.class); 217 assertNotNull(ac); 218 verifyAnnotationC(ac, expected.getAnnotation(C.class)); 219 } else { 220 Header ah = actual.getAnnotation(Header.class); 221 assertNotNull(ah); 222 Header eh = actual.getAnnotation(Header.class); 223 assertNotNull(eh); 224 assertEquals(Paths.get(ah.path()).getFileName(), 225 Paths.get(eh.path()).getFileName()); 226 } 227 228 } 229 230 private void AsmCheckClass(String name) { 231 ClassReader cr = new ClassReader(actualClz.get(name)); 232 PrintWriter pw = new PrintWriter(System.out); 233 TraceClassVisitor tcv = new TraceClassVisitor( 234 new CheckClassAdapter(new ClassNode()), 235 new Textifier(), pw); 236 try { 237 cr.accept(tcv, ClassReader.SKIP_FRAMES); 238 } catch (Throwable e) { 239 tcv.p.print(pw); 240 e.printStackTrace(pw); 241 } 242 pw.flush(); 243 } 244 245 private void AsmifyClass(String name) throws IOException { 246 ClassReader cr = new ClassReader(mfm.getClassData(name)); 247 PrintWriter pw = new PrintWriter(System.out); 248 TraceClassVisitor tcv = new TraceClassVisitor( 249 new CheckClassAdapter(new ClassNode()), 250 new ASMifier(), pw); 251 try { 252 cr.accept(tcv, ClassReader.SKIP_FRAMES); 253 } catch (Throwable e) { 254 tcv.p.print(pw); 255 e.printStackTrace(pw); 256 } 257 pw.flush(); 258 } 259 260 @DataProvider(name = "classes") 261 public Object[][] loadClasses() throws ClassNotFoundException, IOException { 262 // compile(); 263 if (clz_data == null) { 264 String[] clz_names = mfm.listClasses().toArray(new String[0]); 265 final int len = clz_names.length; 266 clz_data = new Object[len][2]; 267 for (int i = 0; i < len; i++) { 268 String name = clz_names[i]; 269 System.err.println("Load class " + name); 270 // AsmCheckClass(name); 271 // AsmifyClass(name); 272 clz_data[i][0] = actualCL.loadClass(name); 273 clz_data[i][1] = expectedCL.loadClass(name); 274 } 275 } 276 return clz_data; 277 } 278 279 @BeforeClass 280 public void compile() throws IOException { 281 System.out.println("Compiling..."); 282 mfm = compileJavaCode(); 283 actualClz = extract(pkg); 284 expectedCL = mfm.getTheClassLoader(); 285 actualCL = new ClassLoader() { 286 @Override 287 protected Class<?> findClass(String name) throws ClassNotFoundException { 288 byte[] byteCode = actualClz.get(name); 289 if (byteCode == null) throw new ClassNotFoundException(name); 290 return defineClass(name, byteCode, 0, byteCode.length); 291 } 292 }; 293 System.out.println("Done compile, ready for test"); 294 assertEquals(actualClz.keySet(), mfm.listClasses()); 295 System.out.println("Compile result validated."); 296 } 297 298 private static Path[] paths(String testDir, String[] files) { 299 return Arrays.stream(files) 300 .map(f -> Paths.get(testDir, f)) 301 .toArray(Path[]::new); 302 } 303 304 @Factory(dataProvider = "cases") 305 public static Object[] run(String nativeSrc, String pkgName, String[] javaSrcFiles) { 306 String testDir = System.getProperty("test.src", "."); 307 System.out.println("Use test case files in " + testDir); 308 return new Object[] { 309 new Runner(Paths.get(testDir, nativeSrc), pkgName, paths(testDir, javaSrcFiles)) 310 }; 311 } 312 313 @DataProvider(name = "cases") 314 public static Object[][] cases() { 315 return new Object[][] { 316 { "simple.h", "com.acme", new String[] { "simple.java" }}, 317 { "recursive.h", "com.acme", new String[] { "recursive.java" }} 318 }; 319 } 320 }