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