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