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