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