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), new String[0], 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(".properties")) {
 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 
 141         assertEquals(files, mfm.listClasses());
 142     }
 143 
 144     private void verifyNativeLocation(NativeLocation actual, NativeLocation expected) {
 145         // Only check the filename, not full path
 146         assertNotNull(actual);
 147         assertNotNull(expected);
 148         assertEquals(Paths.get(actual.file()).getFileName(),
 149                      Paths.get(expected.file()).getFileName());
 150         assertEquals(actual.line(), expected.line());
 151         assertEquals(actual.column(), expected.column());
 152     }
 153 
 154     private void verifyMethodAnnotation(Method actual, Method expected) {
 155         Annotation[] aa = actual.getAnnotations();
 156         Annotation[] ea = expected.getAnnotations();
 157 
 158         for (Annotation a: ea) {
 159             if (a instanceof NativeLocation) {
 160                 verifyNativeLocation(actual.getAnnotation(NativeLocation.class), (NativeLocation) a);
 161             }
 162         }
 163     }
 164 
 165     @Test(dataProvider = "classes")
 166     public void testMethods(Class<?> actual, Class<?> expected) {
 167         Method[] am = actual.getMethods();
 168         Method[] em = expected.getMethods();
 169         HashMap<String, Method> ams = new HashMap<>();
 170         for (Method m: am) {
 171             ams.put(m.toGenericString(), m);
 172         }
 173         HashMap<String, Method> ems = new HashMap<>();
 174         for (Method m: em) {
 175             String sig = m.toGenericString();
 176             Method ma = ams.remove(sig);
 177             if (ma == null) {
 178                 System.out.println("Missing " + sig);
 179                 ems.put(m.toGenericString(), m);
 180             } else {
 181                 assertEquals(ma.isVarArgs(), m.isVarArgs());
 182                 try {
 183                     verifyMethodAnnotation(ma, m);
 184                 } catch (Throwable t) {
 185                     fail("Failed method " + sig, t);
 186                 }
 187             }
 188         }
 189         ams.keySet().forEach((sig) -> {
 190             System.out.println("Got: " + sig);
 191         });
 192         assertTrue(ams.isEmpty());
 193         assertTrue(ems.isEmpty());
 194     }
 195 
 196     @Test(dataProvider = "classes")
 197     public void testAnnotations(Class<?> actual, Class<?> expected) {
 198         Annotation[] aa = actual.getAnnotations();
 199         Annotation[] ea = expected.getAnnotations();
 200 
 201         if (actual.getName().contains("$")) {
 202             assertTrue(actual.getName().contains("$"));
 203             assertTrue(expected.isMemberClass());
 204             assertTrue(actual.isMemberClass());
 205             if (actual.isAnnotationPresent(NativeStruct.class)) {
 206                 NativeStruct ant = actual.getAnnotation(NativeStruct.class);
 207                 assertNotNull(ant);
 208                 assertEquals(ant, expected.getAnnotation(NativeStruct.class));
 209             } else if (actual.isAnnotationPresent(NativeCallback.class)) {
 210                 NativeCallback cb = actual.getAnnotation(NativeCallback.class);
 211                 assertNotNull(cb);
 212                 assertEquals(cb, expected.getAnnotation(NativeCallback.class));
 213             }
 214 
 215             if (expected.isAnnotationPresent(NativeLocation.class)) {
 216                 NativeLocation loc = actual.getAnnotation(NativeLocation.class);
 217                 assertNotNull(loc);
 218                 verifyNativeLocation(loc, expected.getAnnotation(NativeLocation.class));
 219             }
 220         } else {
 221             NativeHeader ah = actual.getAnnotation(NativeHeader.class);
 222             assertNotNull(ah);
 223             NativeHeader eh = expected.getAnnotation(NativeHeader.class);
 224             assertNotNull(eh);
 225 
 226             assertEquals(Paths.get(ah.path()).getFileName(),
 227                     Paths.get(eh.path()).getFileName());
 228 
 229             assertEquals(ah.declarations(), eh.declarations());
 230         }
 231 
 232     }
 233 
 234     private void AsmCheckClass(String name) {
 235         ClassReader cr = new ClassReader(actualClz.get(name));
 236         PrintWriter pw = new PrintWriter(System.out);
 237         TraceClassVisitor tcv = new TraceClassVisitor(
 238                 new CheckClassAdapter(new ClassNode()),
 239                 new Textifier(), pw);
 240         try {
 241             cr.accept(tcv, ClassReader.SKIP_FRAMES);
 242         } catch (Throwable e) {
 243             tcv.p.print(pw);
 244             e.printStackTrace(pw);
 245         }
 246         pw.flush();
 247     }
 248 
 249     private void AsmifyClass(String name) throws IOException {
 250         ClassReader cr = new ClassReader(mfm.getClassData(name));
 251         PrintWriter pw = new PrintWriter(System.out);
 252         TraceClassVisitor tcv = new TraceClassVisitor(
 253                 new CheckClassAdapter(new ClassNode()),
 254                 new ASMifier(), pw);
 255         try {
 256             cr.accept(tcv, ClassReader.SKIP_FRAMES);
 257         } catch (Throwable e) {
 258             tcv.p.print(pw);
 259             e.printStackTrace(pw);
 260         }
 261         pw.flush();
 262     }
 263 
 264     @DataProvider(name = "classes")
 265     public Object[][] loadClasses() throws ClassNotFoundException, IOException {
 266         // compile();
 267         if (clz_data == null) {
 268             String[] clz_names = mfm.listClasses().toArray(new String[0]);
 269             final int len = clz_names.length;
 270             clz_data = new Object[len][2];
 271             for (int i = 0; i < len; i++) {
 272                 String name = clz_names[i];
 273                 System.err.println("Load class " + name);
 274                 // AsmCheckClass(name);
 275                 // AsmifyClass(name);
 276                 clz_data[i][0] = actualCL.loadClass(name);
 277                 clz_data[i][1] = expectedCL.loadClass(name);
 278             }
 279         }
 280         return clz_data;
 281     }
 282 
 283     @BeforeClass
 284     public void compile() throws IOException {
 285         System.out.println("Compiling...");
 286         mfm = compileJavaCode();
 287         actualClz = extract(pkg);

 288         expectedCL = mfm.getTheClassLoader();
 289         actualCL = new ClassLoader() {
 290             @Override
 291             protected Class<?> findClass(String name) throws ClassNotFoundException {
 292                 byte[] byteCode = actualClz.get(name);
 293                 if (byteCode == null) throw new ClassNotFoundException(name);
 294                 return defineClass(name, byteCode, 0, byteCode.length);
 295             }
 296         };
 297         System.out.println("Done compile, ready for test");
 298         assertEquals(actualClz.keySet(), mfm.listClasses());



 299         System.out.println("Compile result validated.");
 300     }
 301 








 302     private static Path[] paths(String testDir, String[] files) {
 303         return Arrays.stream(files)
 304                 .map(f -> Paths.get(testDir, f))
 305                 .toArray(Path[]::new);
 306     }
 307 
 308     @Factory(dataProvider = "cases")
 309     public static Object[] run(String nativeSrc, String pkgName, String[] javaSrcFiles) {
 310         String testDir = System.getProperty("test.src", ".");
 311         System.out.println("Use test case files in " + testDir);
 312         return new Object[] {
 313             new Runner(Paths.get(testDir, nativeSrc), pkgName, paths(testDir, javaSrcFiles))
 314         };
 315     }
 316 
 317     @DataProvider(name = "cases")
 318     public static Object[][] cases() {
 319         return new Object[][] {
 320             { "simple.h", "com.acme", new String[] { "simple.java" }},
 321             { "recursive.h", "com.acme", new String[] { "recursive.java" }},
 322             { "TypedefAnonStruct.h", "com.acme", new String[] { "TypedefAnonStruct.java" }},
 323             { "pad.h", "com.acme", new String[] { "pad.java" }},
 324             { "bitfields.h", "com.acme", new String[] { "bitfields.java" }},
 325             { "globalFuncPointer.h", "com.acme", new String[] { "globalFuncPointer.java" }}
 326         };
 327     }
 328 }
--- EOF ---