1 /* 2 * Copyright (c) 2018, 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.lang.reflect.Field; 25 import java.lang.reflect.Method; 26 import java.io.IOException; 27 import java.io.PrintWriter; 28 import java.io.StringWriter; 29 import java.net.URL; 30 import java.net.URLClassLoader; 31 import java.nicl.metadata.Header; 32 import java.nicl.metadata.LibraryDependencies; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.nio.file.Files; 36 import java.util.Arrays; 37 import java.util.Optional; 38 import java.util.spi.ToolProvider; 39 import org.testng.annotations.Test; 40 41 import static org.testng.Assert.assertEquals; 42 import static org.testng.Assert.assertNotEquals; 43 import static org.testng.Assert.assertNotNull; 44 import static org.testng.Assert.assertNull; 45 import static org.testng.Assert.assertTrue; 46 import static org.testng.Assert.assertFalse; 47 48 /* 49 * @test 50 * @modules jdk.jextract 51 * @build JextractToolProviderTest 52 * @run testng/othervm -Duser.language=en JextractToolProviderTest 53 */ 54 public class JextractToolProviderTest { 55 private static final ToolProvider JEXTRACT_TOOL = ToolProvider.findFirst("jextract") 56 .orElseThrow(() -> 57 new RuntimeException("jextract tool not found") 58 ); 59 60 private static String testSrcDir = System.getProperty("test.src", "."); 61 private static String testClassesDir = System.getProperty("test.classes", "."); 62 63 private static Path getFilePath(String dir, String fileName) { 64 return Paths.get(dir, fileName).toAbsolutePath(); 65 } 66 67 private static Path getInputFilePath(String fileName) { 68 return getFilePath(testSrcDir, fileName); 69 } 70 71 private static Path getOutputFilePath(String fileName) { 72 return getFilePath(testClassesDir, fileName); 73 } 74 75 private static int checkJextract(String expected, String... options) { 76 StringWriter writer = new StringWriter(); 77 PrintWriter pw = new PrintWriter(writer); 78 79 int result = JEXTRACT_TOOL.run(pw, pw, options); 80 String output = writer.toString(); 81 System.err.println(output); 82 if (expected != null) { 83 if (!output.contains(expected)) { 84 throw new AssertionError("Output does not contain " + expected); 85 } 86 } 87 88 return result; 89 } 90 91 private static void checkSuccess(String expected, String... options) { 92 int result = checkJextract(null, options); 93 assertEquals(result, 0, "Sucess excepted, failed: " + result); 94 } 95 96 private static void checkFailure(String expected, String... options) { 97 int result = checkJextract(expected, options); 98 assertNotEquals(result, 0, "Failure excepted, succeeded!"); 99 } 100 101 private static void deleteFile(Path path) { 102 try { 103 Files.delete(path); 104 } catch (IOException ioExp) { 105 System.err.println(ioExp); 106 } 107 } 108 109 private static Class<?> loadClass(String className, Path...paths) { 110 try { 111 URL[] urls = new URL[paths.length]; 112 for (int i = 0; i < paths.length; i++) { 113 urls[i] = paths[i].toUri().toURL(); 114 } 115 URLClassLoader ucl = new URLClassLoader(urls, null); 116 return Class.forName(className, false, ucl); 117 } catch (RuntimeException re) { 118 throw re; 119 } catch (Exception e) { 120 throw new RuntimeException(e); 121 } 122 } 123 124 private static Field findField(Class<?> cls, String name) { 125 try { 126 return cls.getField(name); 127 } catch (Exception e) { 128 System.err.println(e); 129 return null; 130 } 131 } 132 133 private static Method findMethod(Class<?> cls, String name, Class<?>... argTypes) { 134 try { 135 return cls.getMethod(name, argTypes); 136 } catch (Exception e) { 137 System.err.println(e); 138 return null; 139 } 140 } 141 142 private static Method findFirstMethod(Class<?> cls, String name) { 143 try { 144 for (Method m : cls.getMethods()) { 145 if (name.equals(m.getName())) { 146 return m; 147 } 148 } 149 return null; 150 } catch (Exception e) { 151 System.err.println(e); 152 return null; 153 } 154 } 155 156 @Test 157 public void testHelp() { 158 checkFailure(null); // no options 159 checkSuccess(null, "--help"); 160 checkSuccess(null, "-h"); 161 checkSuccess(null, "-?"); 162 } 163 164 // error for non-existent header file 165 @Test 166 public void testNonExistentHeader() { 167 checkFailure("Cannot open header file", "--dry-run", 168 getInputFilePath("non_existent.h").toString()); 169 } 170 171 @Test 172 public void testDryRun() { 173 // only dry-run, don't produce any output 174 Path simpleJar = getOutputFilePath("simple.jar"); 175 deleteFile(simpleJar); 176 checkSuccess(null, "--dry-run", getInputFilePath("simple.h").toString()); 177 try { 178 assertFalse(Files.isRegularFile(simpleJar)); 179 } finally { 180 deleteFile(simpleJar); 181 } 182 } 183 184 @Test 185 public void testOutputFileOption() { 186 // simple output file check 187 Path simpleJar = getOutputFilePath("simple.jar"); 188 deleteFile(simpleJar); 189 checkSuccess(null, "-o", simpleJar.toString(), 190 getInputFilePath("simple.h").toString()); 191 try { 192 assertTrue(Files.isRegularFile(simpleJar)); 193 } finally { 194 deleteFile(simpleJar); 195 } 196 } 197 198 @Test 199 public void testOutputClass() { 200 Path helloJar = getOutputFilePath("hello.jar"); 201 deleteFile(helloJar); 202 Path helloH = getInputFilePath("hello.h"); 203 checkSuccess(null, "-o", helloJar.toString(), helloH.toString()); 204 try { 205 Class<?> cls = loadClass("hello", helloJar); 206 // check header annotation 207 Header header = cls.getAnnotation(Header.class); 208 assertNotNull(header); 209 assertEquals(header.path(), helloH.toString()); 210 211 // check a method for "void func()" 212 assertNotNull(findMethod(cls, "func", Object[].class)); 213 } finally { 214 deleteFile(helloJar); 215 } 216 } 217 218 private void testTargetPackage(String targetPkgOption) { 219 Path helloJar = getOutputFilePath("hello.jar"); 220 deleteFile(helloJar); 221 Path helloH = getInputFilePath("hello.h"); 222 checkSuccess(null, targetPkgOption, "com.acme", "-o", helloJar.toString(), helloH.toString()); 223 try { 224 Class<?> cls = loadClass("com.acme.hello", helloJar); 225 // check header annotation 226 Header header = cls.getAnnotation(Header.class); 227 assertNotNull(header); 228 assertEquals(header.path(), helloH.toString()); 229 230 // check a method for "void func()" 231 assertNotNull(findMethod(cls, "func", Object[].class)); 232 } finally { 233 deleteFile(helloJar); 234 } 235 } 236 237 @Test 238 public void testTargetPackageOption() { 239 testTargetPackage("-t"); 240 } 241 242 @Test 243 public void testTargetPackageLongOption() { 244 testTargetPackage("--target-package"); 245 } 246 247 private void testPackageMapping(String pkgMapOption) { 248 Path worldJar = getOutputFilePath("world.jar"); 249 deleteFile(worldJar); 250 Path mytypesJar = getOutputFilePath("mytypes.jar"); 251 deleteFile(mytypesJar); 252 253 Path worldH = getInputFilePath("world.h"); 254 Path include = getInputFilePath("include"); 255 // generate jar for mytypes.h 256 checkSuccess(null, "-t", "com.acme", "-o", mytypesJar.toString(), 257 include.resolve("mytypes.h").toString()); 258 // world.h include mytypes.h, use appropriate package for stuff from mytypes.h 259 checkSuccess(null, "-I", include.toString(), pkgMapOption, include.toString() + "=com.acme", 260 "-o", worldJar.toString(), worldH.toString()); 261 try { 262 Class<?> cls = loadClass("world", worldJar, mytypesJar); 263 Method m = findFirstMethod(cls, "distance"); 264 Class<?>[] params = m.getParameterTypes(); 265 assertEquals(params[0].getName(), "com.acme.mytypes$Point"); 266 } finally { 267 deleteFile(worldJar); 268 deleteFile(mytypesJar); 269 } 270 } 271 272 @Test 273 public void testPackageDirMappingOption() { 274 testPackageMapping("-m"); 275 } 276 277 @Test 278 public void testPackageDirMappingLongOption() { 279 testPackageMapping("--package-map"); 280 } 281 282 @Test 283 public void test_option_L_without_l() { 284 Path helloJar = getOutputFilePath("hello.jar"); 285 deleteFile(helloJar); 286 Path helloH = getInputFilePath("hello.h"); 287 Path linkDir = getInputFilePath("libs"); 288 String warning = "WARNING: -L option specified without any -l option"; 289 checkSuccess(warning, "-L", linkDir.toString(), "-o", helloJar.toString(), helloH.toString()); 290 } 291 292 @Test 293 public void test_option_rpath_without_l() { 294 Path helloJar = getOutputFilePath("hello.jar"); 295 deleteFile(helloJar); 296 Path helloH = getInputFilePath("hello.h"); 297 Path rpathDir = getInputFilePath("libs"); 298 String warning = "WARNING: -rpath option specified without any -l option"; 299 try { 300 checkSuccess(warning, "-rpath", rpathDir.toString(), "-o", helloJar.toString(), helloH.toString()); 301 } finally { 302 deleteFile(helloJar); 303 } 304 } 305 306 @Test 307 public void test_option_l() { 308 Path helloJar = getOutputFilePath("hello.jar"); 309 deleteFile(helloJar); 310 Path helloH = getInputFilePath("hello.h"); 311 checkSuccess(null, "-l", "hello", "-o", helloJar.toString(), helloH.toString()); 312 try { 313 Class<?> cls = loadClass("hello", helloJar); 314 // check LibraryDependencies annotation capture -l value 315 LibraryDependencies libDeps = cls.getAnnotation(LibraryDependencies.class); 316 assertNotNull(libDeps); 317 assertEquals(libDeps.names().length, 1); 318 assertEquals(libDeps.names()[0], "hello"); 319 // no library paths (rpath) set 320 assertEquals(libDeps.paths().length, 0); 321 } finally { 322 deleteFile(helloJar); 323 } 324 } 325 326 @Test 327 public void test_option_l_and_rpath() { 328 Path helloJar = getOutputFilePath("hello.jar"); 329 deleteFile(helloJar); 330 Path helloH = getInputFilePath("hello.h"); 331 Path rpathDir = getInputFilePath("libs"); 332 checkSuccess(null, "-l", "hello", "-rpath", rpathDir.toString(), 333 "-o", helloJar.toString(), helloH.toString()); 334 try { 335 Class<?> cls = loadClass("hello", helloJar); 336 // check LibraryDependencies annotation captures -l and -rpath values 337 LibraryDependencies libDeps = cls.getAnnotation(LibraryDependencies.class); 338 assertNotNull(libDeps); 339 assertEquals(libDeps.names().length, 1); 340 assertEquals(libDeps.names()[0], "hello"); 341 assertEquals(libDeps.paths().length, 1); 342 assertEquals(libDeps.paths()[0], rpathDir.toString()); 343 } finally { 344 deleteFile(helloJar); 345 } 346 } 347 348 @Test 349 public void testUnionDeclaration() { 350 Path uniondeclJar = getOutputFilePath("uniondecl.jar"); 351 deleteFile(uniondeclJar); 352 Path uniondeclH = getInputFilePath("uniondecl.h"); 353 try { 354 checkSuccess(null, "-o", uniondeclJar.toString(), uniondeclH.toString()); 355 Class<?> unionCls = loadClass("uniondecl", uniondeclJar); 356 assertNotNull(unionCls); 357 boolean found = Arrays.stream(unionCls.getClasses()). 358 map(Class::getSimpleName). 359 filter(n -> n.equals("IntOrFloat")). 360 findFirst().isPresent(); 361 assertTrue(found, "uniondecl.IntOrFloat not found"); 362 } finally { 363 deleteFile(uniondeclJar); 364 } 365 } 366 367 private void checkIntField(Class<?> cls, String name, int value) { 368 Field field = findField(cls, name); 369 assertNotNull(field); 370 assertEquals(field.getType(), int.class); 371 try { 372 assertEquals((int)field.get(null), value); 373 } catch (Exception exp) { 374 System.err.println(exp); 375 assertTrue(false, "should not reach here"); 376 } 377 } 378 379 @Test 380 public void testAnonymousEnum() { 381 Path anonenumJar = getOutputFilePath("anonenum.jar"); 382 deleteFile(anonenumJar); 383 Path anonenumH = getInputFilePath("anonenum.h"); 384 try { 385 checkSuccess(null, "-o", anonenumJar.toString(), anonenumH.toString()); 386 Class<?> anonenumCls = loadClass("anonenum", anonenumJar); 387 assertNotNull(anonenumCls); 388 // the nested type for anonymous enum has name starting with "enum__anonymous_at" 389 // followed by full path name of header file + line + column numbers. Any non-ident 390 // char replaced by "_". But we test only the start pattern here. 391 Optional<Class<?>> optEnumCls = Arrays.stream(anonenumCls.getClasses()). 392 filter(c -> c.getSimpleName().startsWith("enum__anonymous_at")). 393 findFirst(); 394 assertTrue(optEnumCls.isPresent()); 395 Class<?> enumCls = optEnumCls.get(); 396 checkIntField(enumCls, "RED", 0xff0000); 397 checkIntField(enumCls, "GREEN", 0x00ff00); 398 checkIntField(enumCls, "BLUE", 0x0000ff); 399 } finally { 400 deleteFile(anonenumJar); 401 } 402 } 403 404 @Test 405 public void testExcludeSymbols() { 406 Path helloJar = getOutputFilePath("hello.jar"); 407 deleteFile(helloJar); 408 Path helloH = getInputFilePath("hello.h"); 409 checkSuccess(null, "-o", helloJar.toString(), helloH.toString()); 410 try { 411 Class<?> cls = loadClass("hello", helloJar); 412 // check a method for "void func()" 413 assertNotNull(findMethod(cls, "func", Object[].class)); 414 // check a method for "void junk()" 415 assertNotNull(findMethod(cls, "junk", Object[].class)); 416 } finally { 417 deleteFile(helloJar); 418 } 419 420 // try with --exclude-symbols" this time. 421 checkSuccess(null, "--exclude-symbols", "junk", "-o", helloJar.toString(), helloH.toString()); 422 try { 423 Class<?> cls = loadClass("hello", helloJar); 424 // check a method for "void func()" 425 assertNotNull(findMethod(cls, "func", Object[].class)); 426 // check a method for "void junk()" 427 assertNull(findMethod(cls, "junk", Object[].class)); 428 } finally { 429 deleteFile(helloJar); 430 } 431 } 432 433 @Test 434 public void testNestedStructsUnions() { 435 Path nestedJar = getOutputFilePath("nested.jar"); 436 deleteFile(nestedJar); 437 Path nestedH = getInputFilePath("nested.h"); 438 try { 439 checkSuccess(null, "-o", nestedJar.toString(), nestedH.toString()); 440 assertNotNull(loadClass("nested", nestedJar)); 441 Class<?> fooCls = loadClass("nested$Foo", nestedJar); 442 assertNotNull(fooCls); 443 // struct Foo has no getters for "x", "y" etc. 444 assertNull(findMethod(fooCls, "x$get")); 445 assertNull(findMethod(fooCls, "y$get")); 446 // struct Foo has getters for bar and color 447 assertNotNull(findMethod(fooCls, "bar$get")); 448 assertNotNull(findMethod(fooCls, "color$get")); 449 // make sure nested types are handled without nested namespace! 450 assertNotNull(loadClass("nested$Bar", nestedJar)); 451 assertNotNull(loadClass("nested$Color", nestedJar)); 452 453 Class<?> uCls = loadClass("nested$U", nestedJar); 454 assertNotNull(uCls); 455 // union U has no getters for "x", "y" etc. 456 assertNull(findMethod(uCls, "x$get")); 457 assertNull(findMethod(uCls, "y$get")); 458 // union U has getters for point, rgb, i 459 assertNotNull(findMethod(uCls, "point$get")); 460 assertNotNull(findMethod(uCls, "rgb$get")); 461 assertNotNull(findMethod(uCls, "i$get")); 462 // make sure nested types are handled without nested namespace! 463 assertNotNull(loadClass("nested$Point", nestedJar)); 464 assertNotNull(loadClass("nested$RGB", nestedJar)); 465 } finally { 466 deleteFile(nestedJar); 467 } 468 } 469 }