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.path(), helloH.toString()); 219 assertFalse(header.declarations().isEmpty()); 220 221 // check a method for "void func()" 222 assertNotNull(findMethod(cls, "func", Object[].class)); 223 } finally { 224 deleteFile(helloJar); 225 } 226 } 227 228 private void testTargetPackage(String targetPkgOption) { 229 Path helloJar = getOutputFilePath("hello.jar"); 230 deleteFile(helloJar); 231 Path helloH = getInputFilePath("hello.h"); 232 checkSuccess(null, targetPkgOption, "com.acme", "-o", helloJar.toString(), helloH.toString()); 233 try { 234 Class<?> cls = loadClass("com.acme.hello", helloJar); 235 // check NativeHeader annotation 236 NativeHeader header = cls.getAnnotation(NativeHeader.class); 237 assertNotNull(header); 238 assertEquals(header.path(), helloH.toString()); 239 240 // check a method for "void func()" 241 assertNotNull(findMethod(cls, "func", Object[].class)); 242 } finally { 243 deleteFile(helloJar); 244 } 245 } 246 247 @Test 248 public void testTargetPackageOption() { 249 testTargetPackage("-t"); 250 } 251 252 @Test 253 public void testTargetPackageLongOption() { 254 testTargetPackage("--target-package"); 255 } 256 257 private void testPackageMapping(String pkgMapOption) { 258 Path worldJar = getOutputFilePath("world.jar"); 259 deleteFile(worldJar); 260 Path mytypesJar = getOutputFilePath("mytypes.jar"); 261 deleteFile(mytypesJar); 262 263 Path worldH = getInputFilePath("world.h"); 264 Path include = getInputFilePath("include"); 265 // generate jar for mytypes.h 266 checkSuccess(null, "-t", "com.acme", "-o", mytypesJar.toString(), 267 include.resolve("mytypes.h").toString()); 268 // world.h include mytypes.h, use appropriate package for stuff from mytypes.h 269 checkSuccess(null, "-I", include.toString(), pkgMapOption, include.toString() + "=com.acme", 270 "-o", worldJar.toString(), worldH.toString()); 271 try { 272 Class<?> cls = loadClass("world", worldJar, mytypesJar); 273 Method m = findFirstMethod(cls, "distance"); 274 Class<?>[] params = m.getParameterTypes(); 275 assertEquals(params[0].getName(), "com.acme.mytypes$Point"); 276 } finally { 277 deleteFile(worldJar); 278 deleteFile(mytypesJar); 279 } 280 } 281 282 @Test 283 public void testPackageDirMappingOption() { 284 testPackageMapping("-m"); 285 } 286 287 @Test 288 public void testPackageDirMappingLongOption() { 289 testPackageMapping("--package-map"); 290 } 291 292 @Test 293 public void test_option_L_without_l() { 294 Path helloJar = getOutputFilePath("hello.jar"); 295 deleteFile(helloJar); 296 Path helloH = getInputFilePath("hello.h"); 297 Path linkDir = getInputFilePath("libs"); 298 String warning = "WARNING: -L option specified without any -l option"; 299 checkSuccess(warning, "-L", linkDir.toString(), "-o", helloJar.toString(), helloH.toString()); 300 } 301 302 @Test 303 public void test_option_rpath_without_l() { 304 Path helloJar = getOutputFilePath("hello.jar"); 305 deleteFile(helloJar); 306 Path helloH = getInputFilePath("hello.h"); 307 Path rpathDir = getInputFilePath("libs"); 308 String warning = "WARNING: -rpath option specified without any -l option"; 309 try { 310 checkSuccess(warning, "-rpath", rpathDir.toString(), "-o", helloJar.toString(), helloH.toString()); 311 } finally { 312 deleteFile(helloJar); 313 } 314 } 315 316 @Test 317 public void test_option_l() { 318 Path helloJar = getOutputFilePath("hello.jar"); 319 deleteFile(helloJar); 320 Path helloH = getInputFilePath("hello.h"); 321 checkSuccess(null, "-l", "hello", "-o", helloJar.toString(), helloH.toString()); 322 try { 323 Class<?> cls = loadClass("hello", helloJar); 324 // check that NativeHeader annotation captures -l value 325 NativeHeader header = cls.getAnnotation(NativeHeader.class); 326 assertNotNull(header); 327 assertEquals(header.libraries().length, 1); 328 assertEquals(header.libraries()[0], "hello"); 329 // no library paths (rpath) set 330 assertEquals(header.libraryPaths().length, 0); 331 } finally { 332 deleteFile(helloJar); 333 } 334 } 335 336 @Test 337 public void test_option_l_and_rpath() { 338 Path helloJar = getOutputFilePath("hello.jar"); 339 deleteFile(helloJar); 340 Path helloH = getInputFilePath("hello.h"); 341 Path rpathDir = getInputFilePath("libs"); 342 checkSuccess(null, "-l", "hello", "-rpath", rpathDir.toString(), 343 "-o", helloJar.toString(), helloH.toString()); 344 try { 345 Class<?> cls = loadClass("hello", helloJar); 346 // check that NativeHeader annotation captures -l and -rpath values 347 NativeHeader header = cls.getAnnotation(NativeHeader.class); 348 assertNotNull(header); 349 assertEquals(header.libraries().length, 1); 350 assertEquals(header.libraries()[0], "hello"); 351 assertEquals(header.libraryPaths().length, 1); 352 assertEquals(header.libraryPaths()[0], rpathDir.toString()); 353 } finally { 354 deleteFile(helloJar); 355 } 356 } 357 358 @Test 359 public void testUnionDeclaration() { 360 Path uniondeclJar = getOutputFilePath("uniondecl.jar"); 361 deleteFile(uniondeclJar); 362 Path uniondeclH = getInputFilePath("uniondecl.h"); 363 try { 364 checkSuccess(null, "-o", uniondeclJar.toString(), uniondeclH.toString()); 365 Class<?> unionCls = loadClass("uniondecl", uniondeclJar); 366 assertNotNull(unionCls); 367 boolean found = Arrays.stream(unionCls.getClasses()). 368 map(Class::getSimpleName). 369 filter(n -> n.equals("IntOrFloat")). 370 findFirst().isPresent(); 371 assertTrue(found, "uniondecl.IntOrFloat not found"); 372 } finally { 373 deleteFile(uniondeclJar); 374 } 375 } 376 377 private void checkIntField(Class<?> cls, String name, int value) { 378 Field field = findField(cls, name); 379 assertNotNull(field); 380 assertEquals(field.getType(), int.class); 381 try { 382 assertEquals((int)field.get(null), value); 383 } catch (Exception exp) { 384 System.err.println(exp); 385 assertTrue(false, "should not reach here"); 386 } 387 } 388 389 private Class<?> findClass(Class<?>[] clz, String name) { 390 for (Class<?> cls: clz) { 391 if (cls.getSimpleName().equals(name)) { 392 return cls; 393 } 394 } 395 return null; 396 } 397 398 private void testEnumValue(Class<?> enumCls, Map<String, Integer> values) { 399 values.entrySet().stream(). 400 forEach(e -> checkIntField(enumCls, e.getKey(), e.getValue())); 401 } 402 403 @Test 404 public void testAnonymousEnum() { 405 Path anonenumJar = getOutputFilePath("anonenum.jar"); 406 deleteFile(anonenumJar); 407 Path anonenumH = getInputFilePath("anonenum.h"); 408 try { 409 checkSuccess(null, "-o", anonenumJar.toString(), anonenumH.toString()); 410 Class<?> anonenumCls = loadClass("anonenum", anonenumJar); 411 assertNotNull(anonenumCls); 412 checkIntField(anonenumCls, "RED", 0xff0000); 413 checkIntField(anonenumCls, "GREEN", 0x00ff00); 414 checkIntField(anonenumCls, "BLUE", 0x0000ff); 415 testEnumValue(anonenumCls, Map.of( 416 "Java", 0, 417 "C", 1, 418 "CPP", 2, 419 "Python", 3, 420 "Ruby", 4)); 421 testEnumValue(anonenumCls, Map.of( 422 "XS", 0, 423 "S", 1, 424 "M", 2, 425 "L", 3, 426 "XL", 4, 427 "XXL", 5)); 428 testEnumValue(anonenumCls, Map.of( 429 "ONE", 1, 430 "TWO", 2)); 431 432 Class<?> enumClz[] = anonenumCls.getClasses(); 433 assert(enumClz.length >= 4); 434 435 Class<?> enumCls = findClass(enumClz, "codetype_t"); 436 assertNotNull(enumCls); 437 438 enumCls = findClass(enumClz, "SIZE"); 439 assertNotNull(enumCls); 440 441 enumCls = findClass(enumClz, "temp"); 442 assertNotNull(enumCls); 443 444 enumCls = findClass(enumClz, "temp_t"); 445 assertNotNull(enumCls); 446 } finally { 447 deleteFile(anonenumJar); 448 } 449 } 450 451 @Test 452 public void testExcludeSymbols() { 453 Path helloJar = getOutputFilePath("hello.jar"); 454 deleteFile(helloJar); 455 Path helloH = getInputFilePath("hello.h"); 456 checkSuccess(null, "-o", helloJar.toString(), helloH.toString()); 457 try { 458 Class<?> cls = loadClass("hello", helloJar); 459 // check a method for "void func()" 460 assertNotNull(findMethod(cls, "func", Object[].class)); 461 // check a method for "void junk()" 462 assertNotNull(findMethod(cls, "junk", Object[].class)); 463 } finally { 464 deleteFile(helloJar); 465 } 466 467 // try with --exclude-symbols" this time. 468 checkSuccess(null, "--exclude-symbols", "junk", "-o", helloJar.toString(), helloH.toString()); 469 try { 470 Class<?> cls = loadClass("hello", helloJar); 471 // check a method for "void func()" 472 assertNotNull(findMethod(cls, "func", Object[].class)); 473 // check a method for "void junk()" 474 assertNull(findMethod(cls, "junk", Object[].class)); 475 } finally { 476 deleteFile(helloJar); 477 } 478 } 479 480 @Test 481 public void testNestedStructsUnions() { 482 Path nestedJar = getOutputFilePath("nested.jar"); 483 deleteFile(nestedJar); 484 Path nestedH = getInputFilePath("nested.h"); 485 try { 486 checkSuccess(null, "-o", nestedJar.toString(), nestedH.toString()); 487 Class<?> headerCls = loadClass("nested", nestedJar); 488 assertNotNull(headerCls); 489 490 Class<?> fooCls = loadClass("nested$Foo", nestedJar); 491 assertNotNull(fooCls); 492 // struct Foo has no getters for "x", "y" etc. 493 assertNull(findStructFieldGet(fooCls, "x")); 494 assertNull(findStructFieldGet(fooCls, "y")); 495 // struct Foo has getters for bar and color 496 assertNotNull(findStructFieldGet(fooCls, "bar")); 497 assertNotNull(findStructFieldGet(fooCls, "color")); 498 // make sure nested types are handled without nested namespace! 499 assertNotNull(loadClass("nested$Bar", nestedJar)); 500 assertNotNull(loadClass("nested$Color", nestedJar)); 501 502 Class<?> uCls = loadClass("nested$U", nestedJar); 503 assertNotNull(uCls); 504 // union U has no getters for "x", "y" etc. 505 assertNull(findStructFieldGet(uCls, "x")); 506 assertNull(findStructFieldGet(uCls, "y")); 507 // union U has getters for point, rgb, i 508 assertNotNull(findStructFieldGet(uCls, "point")); 509 assertNotNull(findStructFieldGet(uCls, "rgb")); 510 assertNotNull(findStructFieldGet(uCls, "i")); 511 // make sure nested types are handled without nested namespace! 512 assertNotNull(loadClass("nested$Point", nestedJar)); 513 assertNotNull(loadClass("nested$RGB", nestedJar)); 514 515 Class<?> myStructCls = loadClass("nested$MyStruct", nestedJar); 516 assertNotNull(findStructFieldGet(myStructCls, "a")); 517 assertNotNull(findStructFieldGet(myStructCls, "b")); 518 assertNotNull(findStructFieldGet(myStructCls, "c")); 519 assertNotNull(findStructFieldGet(myStructCls, "d")); 520 // 'e' is named struct element - should not be in MyStruct 521 assertNull(findStructFieldGet(myStructCls, "e")); 522 assertNotNull(findStructFieldGet(myStructCls, "f")); 523 assertNotNull(findStructFieldGet(myStructCls, "g")); 524 assertNotNull(findStructFieldGet(myStructCls, "h")); 525 // 'i' is named struct element - should not be in MyStruct 526 assertNull(findStructFieldGet(myStructCls, "i")); 527 // 'j' is named struct element - should not be in MyStruct 528 assertNull(findStructFieldGet(myStructCls, "j")); 529 assertNotNull(findStructFieldGet(myStructCls, "k")); 530 // "X", "Y", "Z" are enum constants -should not be in MyStruct 531 assertNull(findStructFieldGet(myStructCls, "X")); 532 assertNull(findStructFieldGet(myStructCls, "Y")); 533 assertNull(findStructFieldGet(myStructCls, "Z")); 534 // anonymous enum constants are hoisted to containing scope 535 assertNotNull(findField(headerCls, "X")); 536 assertNotNull(findField(headerCls, "Y")); 537 assertNotNull(findField(headerCls, "Z")); 538 539 Class<?> myUnionCls = loadClass("nested$MyUnion", nestedJar); 540 assertNotNull(findStructFieldGet(myUnionCls, "a")); 541 assertNotNull(findStructFieldGet(myUnionCls, "b")); 542 assertNotNull(findStructFieldGet(myUnionCls, "c")); 543 assertNotNull(findStructFieldGet(myUnionCls, "d")); 544 // 'e' is named struct element - should not be in MyUnion 545 assertNull(findStructFieldGet(myUnionCls, "e")); 546 assertNotNull(findStructFieldGet(myUnionCls, "f")); 547 assertNotNull(findStructFieldGet(myUnionCls, "g")); 548 assertNotNull(findStructFieldGet(myUnionCls, "h")); 549 // 'i' is named struct element - should not be in MyUnion 550 assertNull(findStructFieldGet(myUnionCls, "i")); 551 // 'j' is named struct element - should not be in MyUnion 552 assertNull(findStructFieldGet(myUnionCls, "j")); 553 assertNotNull(findStructFieldGet(myUnionCls, "k")); 554 // "A", "B", "C" are enum constants -should not be in MyUnion 555 assertNull(findStructFieldGet(myUnionCls, "A")); 556 assertNull(findStructFieldGet(myUnionCls, "B")); 557 assertNull(findStructFieldGet(myUnionCls, "C")); 558 // anonymous enum constants are hoisted to containing scope 559 assertNotNull(findField(headerCls, "A")); 560 assertNotNull(findField(headerCls, "B")); 561 assertNotNull(findField(headerCls, "C")); 562 } finally { 563 deleteFile(nestedJar); 564 } 565 } 566 567 @Test 568 public void testAnonymousStructTypeGlobalVar() { 569 Path elaboratedTypeJar = getOutputFilePath("elaboratedtype.jar"); 570 deleteFile(elaboratedTypeJar); 571 Path elaboratedTypeH = getInputFilePath("elaboratedtype.h"); 572 try { 573 checkSuccess(null, "-o", elaboratedTypeJar.toString(), elaboratedTypeH.toString()); 574 Class<?> headerCls = loadClass("elaboratedtype", elaboratedTypeJar); 575 assertNotNull(findGlobalVariableGet(headerCls, "point")); 576 assertNotNull(findGlobalVariableGet(headerCls, "long_or_int")); 577 assertNotNull(findMethod(headerCls, "func", Pointer.class)); 578 } finally { 579 deleteFile(elaboratedTypeJar); 580 } 581 } 582 } --- EOF ---