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