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.NativeHeader; 32 import java.nicl.types.Pointer; 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.Map; 38 import java.util.Optional; 39 import java.util.spi.ToolProvider; 40 import org.testng.annotations.Test; 41 42 import static org.testng.Assert.assertEquals; 43 import static org.testng.Assert.assertNotEquals; 44 import static org.testng.Assert.assertNotNull; 45 import static org.testng.Assert.assertNull; 46 import static org.testng.Assert.assertTrue; 47 import static org.testng.Assert.assertFalse; 48 49 /* 50 * @test 51 * @modules jdk.jextract 52 * @build JextractToolProviderTest 53 * @run testng/othervm -Duser.language=en JextractToolProviderTest 54 */ 55 public class JextractToolProviderTest { 56 private static final ToolProvider JEXTRACT_TOOL = ToolProvider.findFirst("jextract") 57 .orElseThrow(() -> 58 new RuntimeException("jextract tool not found") 59 ); 60 61 private static String testSrcDir = System.getProperty("test.src", "."); 62 private static String testClassesDir = System.getProperty("test.classes", "."); 63 64 private static Path getFilePath(String dir, String fileName) { 65 return Paths.get(dir, fileName).toAbsolutePath(); 66 } 67 68 private static Path getInputFilePath(String fileName) { 69 return getFilePath(testSrcDir, fileName); 70 } 71 72 private static Path getOutputFilePath(String fileName) { 73 return getFilePath(testClassesDir, fileName); 74 } 75 76 private static int checkJextract(String expected, String... options) { 77 StringWriter writer = new StringWriter(); 78 PrintWriter pw = new PrintWriter(writer); 79 80 int result = JEXTRACT_TOOL.run(pw, pw, options); 81 String output = writer.toString(); 82 System.err.println(output); 83 if (expected != null) { 84 if (!output.contains(expected)) { 85 throw new AssertionError("Output does not contain " + expected); 86 } 87 } 88 89 return result; 90 } 91 92 private static void checkSuccess(String expected, String... options) { 93 int result = checkJextract(null, options); 94 assertEquals(result, 0, "Sucess excepted, failed: " + result); 95 } 96 97 private static void checkFailure(String expected, String... options) { 98 int result = checkJextract(expected, options); 99 assertNotEquals(result, 0, "Failure excepted, succeeded!"); 100 } 101 102 private static void deleteFile(Path path) { 103 try { 104 Files.delete(path); 105 } catch (IOException ioExp) { 106 System.err.println(ioExp); 107 } 108 } 109 110 private static Class<?> loadClass(String className, Path...paths) { 111 try { 112 URL[] urls = new URL[paths.length]; 113 for (int i = 0; i < paths.length; i++) { 114 urls[i] = paths[i].toUri().toURL(); 115 } 116 URLClassLoader ucl = new URLClassLoader(urls, null); 117 return Class.forName(className, false, ucl); 118 } catch (RuntimeException re) { 119 throw re; 120 } catch (Exception e) { 121 throw new RuntimeException(e); 122 } 123 } 124 125 private static Field findField(Class<?> cls, String name) { 126 try { 127 return cls.getField(name); 128 } catch (Exception e) { 129 System.err.println(e); 130 return null; 131 } 132 } 133 134 private static Method findMethod(Class<?> cls, String name, Class<?>... argTypes) { 135 try { 136 return cls.getMethod(name, argTypes); 137 } catch (Exception e) { 138 System.err.println(e); 139 return null; 140 } 141 } 142 143 private static Method findStructFieldGet(Class<?> cls, String name) { 144 return findMethod(cls, name + "$get"); 145 } 146 147 private static Method findGlobalVariableGet(Class<?> cls, String name) { 148 return findMethod(cls, name + "$get"); 149 } 150 151 private static Method findFirstMethod(Class<?> cls, String name) { 152 try { 153 for (Method m : cls.getMethods()) { 154 if (name.equals(m.getName())) { 155 return m; 156 } 157 } 158 return null; 159 } catch (Exception e) { 160 System.err.println(e); 161 return null; 162 } 163 } 164 165 @Test 166 public void testHelp() { 167 checkFailure(null); // no options 168 checkSuccess(null, "--help"); 169 checkSuccess(null, "-h"); 170 checkSuccess(null, "-?"); 171 } 172 173 // error for non-existent header file 174 @Test 175 public void testNonExistentHeader() { 176 checkFailure("Cannot open header file", "--dry-run", 177 getInputFilePath("non_existent.h").toString()); 178 } 179 180 @Test 181 public void testDryRun() { 182 // only dry-run, don't produce any output 183 Path simpleJar = getOutputFilePath("simple.jar"); 184 deleteFile(simpleJar); 185 checkSuccess(null, "--dry-run", getInputFilePath("simple.h").toString()); 186 try { 187 assertFalse(Files.isRegularFile(simpleJar)); 188 } finally { 189 deleteFile(simpleJar); 190 } 191 } 192 193 @Test 194 public void testOutputFileOption() { 195 // simple output file check 196 Path simpleJar = getOutputFilePath("simple.jar"); 197 deleteFile(simpleJar); 198 checkSuccess(null, "-o", simpleJar.toString(), 199 getInputFilePath("simple.h").toString()); 200 try { 201 assertTrue(Files.isRegularFile(simpleJar)); 202 } finally { 203 deleteFile(simpleJar); 204 } 205 } 206 207 @Test 208 public void testOutputClass() { 209 Path helloJar = getOutputFilePath("hello.jar"); 210 deleteFile(helloJar); 211 Path helloH = getInputFilePath("hello.h"); 212 checkSuccess(null, "-o", helloJar.toString(), helloH.toString()); 213 try { 214 Class<?> cls = loadClass("hello", helloJar); 215 // check NativeHeader annotation 216 NativeHeader header = cls.getAnnotation(NativeHeader.class); 217 assertNotNull(header); 218 assertEquals(header.headerPath(), helloH.toString()); 219 220 // check a method for "void func()" 221 assertNotNull(findMethod(cls, "func", Object[].class)); 222 } finally { 223 deleteFile(helloJar); 224 } 225 } 226 227 private void testTargetPackage(String targetPkgOption) { 228 Path helloJar = getOutputFilePath("hello.jar"); 229 deleteFile(helloJar); 230 Path helloH = getInputFilePath("hello.h"); 231 checkSuccess(null, targetPkgOption, "com.acme", "-o", helloJar.toString(), helloH.toString()); 232 try { 233 Class<?> cls = loadClass("com.acme.hello", helloJar); 234 // check NativeHeader annotation 235 NativeHeader header = cls.getAnnotation(NativeHeader.class); 236 assertNotNull(header); 237 assertEquals(header.headerPath(), helloH.toString()); 238 239 // check a method for "void func()" 240 assertNotNull(findMethod(cls, "func", Object[].class)); 241 } finally { 242 deleteFile(helloJar); 243 } 244 } 245 246 @Test 247 public void testTargetPackageOption() { 248 testTargetPackage("-t"); 249 } 250 251 @Test 252 public void testTargetPackageLongOption() { 253 testTargetPackage("--target-package"); 254 } 255 256 private void testPackageMapping(String pkgMapOption) { 257 Path worldJar = getOutputFilePath("world.jar"); 258 deleteFile(worldJar); 259 Path mytypesJar = getOutputFilePath("mytypes.jar"); 260 deleteFile(mytypesJar); 261 262 Path worldH = getInputFilePath("world.h"); 263 Path include = getInputFilePath("include"); 264 // generate jar for mytypes.h 265 checkSuccess(null, "-t", "com.acme", "-o", mytypesJar.toString(), 266 include.resolve("mytypes.h").toString()); 267 // world.h include mytypes.h, use appropriate package for stuff from mytypes.h 268 checkSuccess(null, "-I", include.toString(), pkgMapOption, include.toString() + "=com.acme", 269 "-o", worldJar.toString(), worldH.toString()); 270 try { 271 Class<?> cls = loadClass("world", worldJar, mytypesJar); 272 Method m = findFirstMethod(cls, "distance"); 273 Class<?>[] params = m.getParameterTypes(); 274 assertEquals(params[0].getName(), "com.acme.mytypes$Point"); 275 } finally { 276 deleteFile(worldJar); 277 deleteFile(mytypesJar); 278 } 279 } 280 281 @Test 282 public void testPackageDirMappingOption() { 283 testPackageMapping("-m"); 284 } 285 286 @Test 287 public void testPackageDirMappingLongOption() { 288 testPackageMapping("--package-map"); 289 } 290 291 @Test 292 public void test_option_L_without_l() { 293 Path helloJar = getOutputFilePath("hello.jar"); 294 deleteFile(helloJar); 295 Path helloH = getInputFilePath("hello.h"); 296 Path linkDir = getInputFilePath("libs"); 297 String warning = "WARNING: -L option specified without any -l option"; 298 checkSuccess(warning, "-L", linkDir.toString(), "-o", helloJar.toString(), helloH.toString()); 299 } 300 301 @Test 302 public void test_option_rpath_without_l() { 303 Path helloJar = getOutputFilePath("hello.jar"); 304 deleteFile(helloJar); 305 Path helloH = getInputFilePath("hello.h"); 306 Path rpathDir = getInputFilePath("libs"); 307 String warning = "WARNING: -rpath option specified without any -l option"; 308 try { 309 checkSuccess(warning, "-rpath", rpathDir.toString(), "-o", helloJar.toString(), helloH.toString()); 310 } finally { 311 deleteFile(helloJar); 312 } 313 } 314 315 @Test 316 public void test_option_l() { 317 Path helloJar = getOutputFilePath("hello.jar"); 318 deleteFile(helloJar); 319 Path helloH = getInputFilePath("hello.h"); 320 checkSuccess(null, "-l", "hello", "-o", helloJar.toString(), helloH.toString()); 321 try { 322 Class<?> cls = loadClass("hello", helloJar); 323 // check that NativeHeader annotation captures -l value 324 NativeHeader header = cls.getAnnotation(NativeHeader.class); 325 assertNotNull(header); 326 assertEquals(header.libraries().length, 1); 327 assertEquals(header.libraries()[0], "hello"); 328 // no library paths (rpath) set 329 assertEquals(header.libraryPaths().length, 0); 330 } finally { 331 deleteFile(helloJar); 332 } 333 } 334 335 @Test 336 public void test_option_l_and_rpath() { 337 Path helloJar = getOutputFilePath("hello.jar"); 338 deleteFile(helloJar); 339 Path helloH = getInputFilePath("hello.h"); 340 Path rpathDir = getInputFilePath("libs"); 341 checkSuccess(null, "-l", "hello", "-rpath", rpathDir.toString(), 342 "-o", helloJar.toString(), helloH.toString()); 343 try { 344 Class<?> cls = loadClass("hello", helloJar); 345 // check that NativeHeader annotation captures -l and -rpath values 346 NativeHeader header = cls.getAnnotation(NativeHeader.class); 347 assertNotNull(header); 348 assertEquals(header.libraries().length, 1); 349 assertEquals(header.libraries()[0], "hello"); 350 assertEquals(header.libraryPaths().length, 1); 351 assertEquals(header.libraryPaths()[0], rpathDir.toString()); 352 } finally { 353 deleteFile(helloJar); 354 } 355 } 356 357 @Test 358 public void testUnionDeclaration() { 359 Path uniondeclJar = getOutputFilePath("uniondecl.jar"); 360 deleteFile(uniondeclJar); 361 Path uniondeclH = getInputFilePath("uniondecl.h"); 362 try { 363 checkSuccess(null, "-o", uniondeclJar.toString(), uniondeclH.toString()); 364 Class<?> unionCls = loadClass("uniondecl", uniondeclJar); 365 assertNotNull(unionCls); 366 boolean found = Arrays.stream(unionCls.getClasses()). 367 map(Class::getSimpleName). 368 filter(n -> n.equals("IntOrFloat")). 369 findFirst().isPresent(); 370 assertTrue(found, "uniondecl.IntOrFloat not found"); 371 } finally { 372 deleteFile(uniondeclJar); 373 } 374 } 375 376 private void checkIntField(Class<?> cls, String name, int value) { 377 Field field = findField(cls, name); 378 assertNotNull(field); 379 assertEquals(field.getType(), int.class); 380 try { 381 assertEquals((int)field.get(null), value); 382 } catch (Exception exp) { 383 System.err.println(exp); 384 assertTrue(false, "should not reach here"); 385 } 386 } 387 388 private Class<?> findClass(Class<?>[] clz, String name) { 389 for (Class<?> cls: clz) { 390 if (cls.getSimpleName().equals(name)) { 391 return cls; 392 } 393 } 394 return null; 395 } 396 397 private void testEnumValue(Class<?> enumCls, Map<String, Integer> values) { 398 values.entrySet().stream(). 399 forEach(e -> checkIntField(enumCls, e.getKey(), e.getValue())); 400 } 401 402 @Test 403 public void testAnonymousEnum() { 404 Path anonenumJar = getOutputFilePath("anonenum.jar"); 405 deleteFile(anonenumJar); 406 Path anonenumH = getInputFilePath("anonenum.h"); 407 try { 408 checkSuccess(null, "-o", anonenumJar.toString(), anonenumH.toString()); 409 Class<?> anonenumCls = loadClass("anonenum", anonenumJar); 410 assertNotNull(anonenumCls); 411 checkIntField(anonenumCls, "RED", 0xff0000); 412 checkIntField(anonenumCls, "GREEN", 0x00ff00); 413 checkIntField(anonenumCls, "BLUE", 0x0000ff); 414 testEnumValue(anonenumCls, Map.of( 415 "Java", 0, 416 "C", 1, 417 "CPP", 2, 418 "Python", 3, 419 "Ruby", 4)); 420 testEnumValue(anonenumCls, Map.of( 421 "XS", 0, 422 "S", 1, 423 "M", 2, 424 "L", 3, 425 "XL", 4, 426 "XXL", 5)); 427 testEnumValue(anonenumCls, Map.of( 428 "ONE", 1, 429 "TWO", 2)); 430 431 Class<?> enumClz[] = anonenumCls.getClasses(); 432 assert(enumClz.length >= 4); 433 434 Class<?> enumCls = findClass(enumClz, "codetype_t"); 435 assertNotNull(enumCls); 436 437 enumCls = findClass(enumClz, "SIZE"); 438 assertNotNull(enumCls); 439 440 enumCls = findClass(enumClz, "temp"); 441 assertNotNull(enumCls); 442 443 enumCls = findClass(enumClz, "temp_t"); 444 assertNotNull(enumCls); 445 } finally { 446 deleteFile(anonenumJar); 447 } 448 } 449 450 @Test 451 public void testExcludeSymbols() { 452 Path helloJar = getOutputFilePath("hello.jar"); 453 deleteFile(helloJar); 454 Path helloH = getInputFilePath("hello.h"); 455 checkSuccess(null, "-o", helloJar.toString(), helloH.toString()); 456 try { 457 Class<?> cls = loadClass("hello", helloJar); 458 // check a method for "void func()" 459 assertNotNull(findMethod(cls, "func", Object[].class)); 460 // check a method for "void junk()" 461 assertNotNull(findMethod(cls, "junk", Object[].class)); 462 } finally { 463 deleteFile(helloJar); 464 } 465 466 // try with --exclude-symbols" this time. 467 checkSuccess(null, "--exclude-symbols", "junk", "-o", helloJar.toString(), helloH.toString()); 468 try { 469 Class<?> cls = loadClass("hello", helloJar); 470 // check a method for "void func()" 471 assertNotNull(findMethod(cls, "func", Object[].class)); 472 // check a method for "void junk()" 473 assertNull(findMethod(cls, "junk", Object[].class)); 474 } finally { 475 deleteFile(helloJar); 476 } 477 } 478 479 @Test 480 public void testNestedStructsUnions() { 481 Path nestedJar = getOutputFilePath("nested.jar"); 482 deleteFile(nestedJar); 483 Path nestedH = getInputFilePath("nested.h"); 484 try { 485 checkSuccess(null, "-o", nestedJar.toString(), nestedH.toString()); 486 Class<?> headerCls = loadClass("nested", nestedJar); 487 assertNotNull(headerCls); 488 489 Class<?> fooCls = loadClass("nested$Foo", nestedJar); 490 assertNotNull(fooCls); 491 // struct Foo has no getters for "x", "y" etc. 492 assertNull(findStructFieldGet(fooCls, "x")); 493 assertNull(findStructFieldGet(fooCls, "y")); 494 // struct Foo has getters for bar and color 495 assertNotNull(findStructFieldGet(fooCls, "bar")); 496 assertNotNull(findStructFieldGet(fooCls, "color")); 497 // make sure nested types are handled without nested namespace! 498 assertNotNull(loadClass("nested$Bar", nestedJar)); 499 assertNotNull(loadClass("nested$Color", nestedJar)); 500 501 Class<?> uCls = loadClass("nested$U", nestedJar); 502 assertNotNull(uCls); 503 // union U has no getters for "x", "y" etc. 504 assertNull(findStructFieldGet(uCls, "x")); 505 assertNull(findStructFieldGet(uCls, "y")); 506 // union U has getters for point, rgb, i 507 assertNotNull(findStructFieldGet(uCls, "point")); 508 assertNotNull(findStructFieldGet(uCls, "rgb")); 509 assertNotNull(findStructFieldGet(uCls, "i")); 510 // make sure nested types are handled without nested namespace! 511 assertNotNull(loadClass("nested$Point", nestedJar)); 512 assertNotNull(loadClass("nested$RGB", nestedJar)); 513 514 Class<?> myStructCls = loadClass("nested$MyStruct", nestedJar); 515 assertNotNull(findStructFieldGet(myStructCls, "a")); 516 assertNotNull(findStructFieldGet(myStructCls, "b")); 517 assertNotNull(findStructFieldGet(myStructCls, "c")); 518 assertNotNull(findStructFieldGet(myStructCls, "d")); 519 // 'e' is named struct element - should not be in MyStruct 520 assertNull(findStructFieldGet(myStructCls, "e")); 521 assertNotNull(findStructFieldGet(myStructCls, "f")); 522 assertNotNull(findStructFieldGet(myStructCls, "g")); 523 assertNotNull(findStructFieldGet(myStructCls, "h")); 524 // 'i' is named struct element - should not be in MyStruct 525 assertNull(findStructFieldGet(myStructCls, "i")); 526 // 'j' is named struct element - should not be in MyStruct 527 assertNull(findStructFieldGet(myStructCls, "j")); 528 assertNotNull(findStructFieldGet(myStructCls, "k")); 529 // "X", "Y", "Z" are enum constants -should not be in MyStruct 530 assertNull(findStructFieldGet(myStructCls, "X")); 531 assertNull(findStructFieldGet(myStructCls, "Y")); 532 assertNull(findStructFieldGet(myStructCls, "Z")); 533 // anonymous enum constants are hoisted to containing scope 534 assertNotNull(findField(headerCls, "X")); 535 assertNotNull(findField(headerCls, "Y")); 536 assertNotNull(findField(headerCls, "Z")); 537 538 Class<?> myUnionCls = loadClass("nested$MyUnion", nestedJar); 539 assertNotNull(findStructFieldGet(myUnionCls, "a")); 540 assertNotNull(findStructFieldGet(myUnionCls, "b")); 541 assertNotNull(findStructFieldGet(myUnionCls, "c")); 542 assertNotNull(findStructFieldGet(myUnionCls, "d")); 543 // 'e' is named struct element - should not be in MyUnion 544 assertNull(findStructFieldGet(myUnionCls, "e")); 545 assertNotNull(findStructFieldGet(myUnionCls, "f")); 546 assertNotNull(findStructFieldGet(myUnionCls, "g")); 547 assertNotNull(findStructFieldGet(myUnionCls, "h")); 548 // 'i' is named struct element - should not be in MyUnion 549 assertNull(findStructFieldGet(myUnionCls, "i")); 550 // 'j' is named struct element - should not be in MyUnion 551 assertNull(findStructFieldGet(myUnionCls, "j")); 552 assertNotNull(findStructFieldGet(myUnionCls, "k")); 553 // "A", "B", "C" are enum constants -should not be in MyUnion 554 assertNull(findStructFieldGet(myUnionCls, "A")); 555 assertNull(findStructFieldGet(myUnionCls, "B")); 556 assertNull(findStructFieldGet(myUnionCls, "C")); 557 // anonymous enum constants are hoisted to containing scope 558 assertNotNull(findField(headerCls, "A")); 559 assertNotNull(findField(headerCls, "B")); 560 assertNotNull(findField(headerCls, "C")); 561 } finally { 562 deleteFile(nestedJar); 563 } 564 } 565 566 @Test 567 public void testAnonymousStructTypeGlobalVar() { 568 Path elaboratedTypeJar = getOutputFilePath("elaboratedtype.jar"); 569 deleteFile(elaboratedTypeJar); 570 Path elaboratedTypeH = getInputFilePath("elaboratedtype.h"); 571 try { 572 checkSuccess(null, "-o", elaboratedTypeJar.toString(), elaboratedTypeH.toString()); 573 Class<?> headerCls = loadClass("elaboratedtype", elaboratedTypeJar); 574 assertNotNull(findGlobalVariableGet(headerCls, "point")); 575 assertNotNull(findGlobalVariableGet(headerCls, "long_or_int")); 576 assertNotNull(findMethod(headerCls, "func", Pointer.class)); 577 } finally { 578 deleteFile(elaboratedTypeJar); 579 } 580 } 581 }