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 }
--- EOF ---