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 org.testng.annotations.Test;
  25 
  26 import java.lang.reflect.Method;
  27 import java.foreign.annotations.NativeHeader;
  28 import java.foreign.memory.Pointer;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 import java.util.Arrays;
  32 import java.util.Map;
  33 
  34 import static org.testng.Assert.assertEquals;
  35 import static org.testng.Assert.assertFalse;
  36 import static org.testng.Assert.assertNotNull;
  37 import static org.testng.Assert.assertNull;
  38 import static org.testng.Assert.assertTrue;
  39 
  40 /*
  41  * @test
  42  * @modules jdk.jextract
  43  * @build JextractToolProviderTest
  44  * @run testng/othervm -Duser.language=en JextractToolProviderTest
  45  */
  46 public class JextractToolProviderTest extends JextractToolRunner {
  47     @Test
  48     public void testHelp() {
  49         checkFailure(null); // no options
  50         checkSuccess(null, "--help");
  51         checkSuccess(null, "-h");
  52         checkSuccess(null, "-?");
  53     }
  54 
  55     // error for non-existent header file
  56     @Test
  57     public void testNonExistentHeader() {
  58         checkFailure("Cannot open header file", "--dry-run",
  59             getInputFilePath("non_existent.h").toString());
  60     }
  61 
  62     @Test
  63     public void testDryRun() {
  64         // only dry-run, don't produce any output
  65         Path simpleJar = getOutputFilePath("simple.jar");
  66         deleteFile(simpleJar);
  67         checkSuccess(null, "--dry-run", getInputFilePath("simple.h").toString());
  68         try {
  69             assertFalse(Files.isRegularFile(simpleJar));
  70         } finally {
  71             deleteFile(simpleJar);
  72         }
  73     }
  74 
  75     @Test
  76     public void testOutputFileOption() {
  77         // simple output file check
  78         Path simpleJar = getOutputFilePath("simple.jar");
  79         deleteFile(simpleJar);
  80         checkSuccess(null, "-o", simpleJar.toString(),
  81             getInputFilePath("simple.h").toString());
  82         try {
  83             assertTrue(Files.isRegularFile(simpleJar));
  84         } finally {
  85             deleteFile(simpleJar);
  86         }
  87     }
  88 
  89     @Test
  90     public void testOutputClass() {
  91         Path helloJar = getOutputFilePath("hello.jar");
  92         deleteFile(helloJar);
  93         Path helloH = getInputFilePath("hello.h");
  94         checkSuccess(null, "-o", helloJar.toString(), helloH.toString());
  95         try {
  96             Class<?> cls = loadClass("hello", helloJar);
  97             // check NativeHeader annotation
  98             NativeHeader header = cls.getAnnotation(NativeHeader.class);
  99             assertNotNull(header);
 100             assertEquals(header.path(), helloH.toString());
 101             assertFalse(header.declarations().isEmpty());
 102 
 103             // check a method for "void func()"
 104             assertNotNull(findMethod(cls, "func", Object[].class));
 105         } finally {
 106             deleteFile(helloJar);
 107         }
 108     }
 109 
 110     private void testTargetPackage(String targetPkgOption) {
 111         Path helloJar = getOutputFilePath("hello.jar");
 112         deleteFile(helloJar);
 113         Path helloH = getInputFilePath("hello.h");
 114         checkSuccess(null, targetPkgOption, "com.acme", "-o", helloJar.toString(), helloH.toString());
 115         try {
 116             Class<?> cls = loadClass("com.acme.hello", helloJar);
 117             // check NativeHeader annotation
 118             NativeHeader header = cls.getAnnotation(NativeHeader.class);
 119             assertNotNull(header);
 120             assertEquals(header.path(), helloH.toString());
 121 
 122             // check a method for "void func()"
 123             assertNotNull(findMethod(cls, "func", Object[].class));
 124         } finally {
 125             deleteFile(helloJar);
 126         }
 127     }
 128 
 129     @Test
 130     public void testTargetPackageOption() {
 131         testTargetPackage("-t");
 132     }
 133 
 134     @Test
 135     public void testTargetPackageLongOption() {
 136         testTargetPackage("--target-package");
 137     }
 138 
 139     private void testPackageMapping(String pkgMapOption) {
 140         Path worldJar = getOutputFilePath("world.jar");
 141         deleteFile(worldJar);
 142         Path mytypesJar = getOutputFilePath("mytypes.jar");
 143         deleteFile(mytypesJar);
 144 
 145         Path worldH = getInputFilePath("world.h");
 146         Path include = getInputFilePath("include");
 147         // generate jar for mytypes.h
 148         checkSuccess(null, "-t", "com.acme", "-o", mytypesJar.toString(),
 149             include.resolve("mytypes.h").toString());
 150         // world.h include mytypes.h, use appropriate package for stuff from mytypes.h
 151         checkSuccess(null, "-I", include.toString(), pkgMapOption, include.toString() + "=com.acme",
 152             "-o", worldJar.toString(), worldH.toString());
 153         try {
 154             Class<?> cls = loadClass("world", worldJar, mytypesJar);
 155             Method m = findFirstMethod(cls, "distance");
 156             Class<?>[] params = m.getParameterTypes();
 157             assertEquals(params[0].getName(), "com.acme.mytypes$Point");
 158         } finally {
 159             deleteFile(worldJar);
 160             deleteFile(mytypesJar);
 161         }
 162     }
 163 
 164     @Test
 165     public void testPackageDirMappingOption() {
 166         testPackageMapping("-m");
 167     }
 168 
 169     @Test
 170     public void testPackageDirMappingLongOption() {
 171         testPackageMapping("--package-map");
 172     }
 173 
 174     @Test
 175     public void test_option_L_without_l() {
 176         Path helloJar = getOutputFilePath("hello.jar");
 177         deleteFile(helloJar);
 178         Path helloH = getInputFilePath("hello.h");
 179         Path linkDir = getInputFilePath("libs");
 180         String warning = "WARNING: -L option specified without any -l option";
 181         checkSuccess(warning, "-L", linkDir.toString(), "-o", helloJar.toString(), helloH.toString());
 182     }
 183 
 184     @Test
 185     public void test_option_rpath_without_l() {
 186         Path helloJar = getOutputFilePath("hello.jar");
 187         deleteFile(helloJar);
 188         Path helloH = getInputFilePath("hello.h");
 189         Path rpathDir = getInputFilePath("libs");
 190         String warning = "WARNING: -rpath option specified without any -l option";
 191         try {
 192             checkSuccess(warning, "-rpath", rpathDir.toString(), "-o", helloJar.toString(), helloH.toString());
 193         } finally {
 194             deleteFile(helloJar);
 195         }
 196     }
 197 
 198     @Test
 199     public void test_option_l() {
 200         Path helloJar = getOutputFilePath("hello.jar");
 201         deleteFile(helloJar);
 202         Path helloH = getInputFilePath("hello.h");
 203         checkSuccess(null, "-l", "hello", "-o", helloJar.toString(), helloH.toString());
 204         try {
 205             Class<?> cls = loadClass("hello", helloJar);
 206             // check that NativeHeader annotation captures -l value
 207             NativeHeader header = cls.getAnnotation(NativeHeader.class);
 208             assertNotNull(header);
 209             assertEquals(header.libraries().length, 1);
 210             assertEquals(header.libraries()[0], "hello");
 211             // no library paths (rpath) set
 212             assertEquals(header.libraryPaths().length, 0);
 213         } finally {
 214             deleteFile(helloJar);
 215         }
 216     }
 217 
 218     @Test
 219     public void test_option_l_and_rpath() {
 220         Path helloJar = getOutputFilePath("hello.jar");
 221         deleteFile(helloJar);
 222     Path helloH = getInputFilePath("hello.h");
 223         Path rpathDir = getInputFilePath("libs");
 224         checkSuccess(null, "-l", "hello", "-rpath", rpathDir.toString(),
 225              "-o", helloJar.toString(), helloH.toString());
 226         try {
 227             Class<?> cls = loadClass("hello", helloJar);
 228             // check that NativeHeader annotation captures -l and -rpath values
 229             NativeHeader header = cls.getAnnotation(NativeHeader.class);
 230             assertNotNull(header);
 231             assertEquals(header.libraries().length, 1);
 232             assertEquals(header.libraries()[0], "hello");
 233             assertEquals(header.libraryPaths().length, 1);
 234             assertEquals(header.libraryPaths()[0], rpathDir.toString());
 235         } finally {
 236             deleteFile(helloJar);
 237         }
 238     }
 239 
 240     @Test
 241     public void testUnionDeclaration() {
 242         Path uniondeclJar = getOutputFilePath("uniondecl.jar");
 243         deleteFile(uniondeclJar);
 244         Path uniondeclH = getInputFilePath("uniondecl.h");
 245         try {
 246             checkSuccess(null, "-o", uniondeclJar.toString(), uniondeclH.toString());
 247             Class<?> unionCls = loadClass("uniondecl", uniondeclJar);
 248             assertNotNull(unionCls);
 249             boolean found = Arrays.stream(unionCls.getClasses()).
 250                 map(Class::getSimpleName).
 251                 filter(n -> n.equals("IntOrFloat")).
 252                 findFirst().isPresent();
 253             assertTrue(found, "uniondecl.IntOrFloat not found");
 254         } finally {
 255             deleteFile(uniondeclJar);
 256         }
 257     }
 258 
 259     private void testEnumValue(Class<?> enumCls, Map<String, Integer> values) {
 260         values.entrySet().stream().
 261                 forEach(e -> checkIntField(enumCls, e.getKey(), e.getValue()));
 262     }
 263 
 264     @Test
 265     public void testAnonymousEnum() {
 266         Path anonenumJar = getOutputFilePath("anonenum.jar");
 267         deleteFile(anonenumJar);
 268         Path anonenumH = getInputFilePath("anonenum.h");
 269         try {
 270             checkSuccess(null, "-o", anonenumJar.toString(), anonenumH.toString());
 271             Class<?> anonenumCls = loadClass("anonenum", anonenumJar);
 272             assertNotNull(anonenumCls);
 273             checkIntField(anonenumCls, "RED", 0xff0000);
 274             checkIntField(anonenumCls, "GREEN", 0x00ff00);
 275             checkIntField(anonenumCls, "BLUE", 0x0000ff);
 276             testEnumValue(anonenumCls, Map.of(
 277                     "Java", 0,
 278                     "C", 1,
 279                     "CPP", 2,
 280                     "Python", 3,
 281                     "Ruby", 4));
 282             testEnumValue(anonenumCls, Map.of(
 283                     "XS", 0,
 284                     "S", 1,
 285                     "M", 2,
 286                     "L", 3,
 287                     "XL", 4,
 288                     "XXL", 5));
 289             testEnumValue(anonenumCls, Map.of(
 290                     "ONE", 1,
 291                     "TWO", 2));
 292 
 293             Class<?> enumClz[] = anonenumCls.getClasses();
 294             assert(enumClz.length >= 4);
 295 
 296             Class<?> enumCls = findClass(enumClz, "codetype_t");
 297             assertNotNull(enumCls);
 298 
 299             enumCls = findClass(enumClz, "SIZE");
 300             assertNotNull(enumCls);
 301 
 302             enumCls = findClass(enumClz, "temp");
 303             assertNotNull(enumCls);
 304 
 305             enumCls = findClass(enumClz, "temp_t");
 306             assertNotNull(enumCls);
 307         } finally {
 308             deleteFile(anonenumJar);
 309         }
 310     }
 311 
 312     @Test
 313     public void testExcludeSymbols() {
 314         Path helloJar = getOutputFilePath("hello.jar");
 315         deleteFile(helloJar);
 316         Path helloH = getInputFilePath("hello.h");
 317         checkSuccess(null, "-o", helloJar.toString(), helloH.toString());
 318         try {
 319             Class<?> cls = loadClass("hello", helloJar);
 320             // check a method for "void func()"
 321             assertNotNull(findMethod(cls, "func", Object[].class));
 322             // check a method for "void junk()"
 323             assertNotNull(findMethod(cls, "junk", Object[].class));
 324         } finally {
 325             deleteFile(helloJar);
 326         }
 327 
 328         // try with --exclude-symbols" this time.
 329         checkSuccess(null, "--exclude-symbols", "junk", "-o", helloJar.toString(), helloH.toString());
 330         try {
 331             Class<?> cls = loadClass("hello", helloJar);
 332             // check a method for "void func()"
 333             assertNotNull(findMethod(cls, "func", Object[].class));
 334             // check a method for "void junk()"
 335             assertNull(findMethod(cls, "junk", Object[].class));
 336         } finally {
 337             deleteFile(helloJar);
 338         }
 339     }
 340 
 341     @Test
 342     public void testNestedStructsUnions() {
 343         Path nestedJar = getOutputFilePath("nested.jar");
 344         deleteFile(nestedJar);
 345     Path nestedH = getInputFilePath("nested.h");
 346         try {
 347             checkSuccess(null, "-o", nestedJar.toString(), nestedH.toString());
 348             Class<?> headerCls = loadClass("nested", nestedJar);
 349             assertNotNull(headerCls);
 350 
 351             Class<?> fooCls = loadClass("nested$Foo", nestedJar);
 352             assertNotNull(fooCls);
 353             // struct Foo has no getters for "x", "y" etc.
 354             assertNull(findStructFieldGet(fooCls, "x"));
 355             assertNull(findStructFieldGet(fooCls, "y"));
 356             // struct Foo has getters for bar and color
 357             assertNotNull(findStructFieldGet(fooCls, "bar"));
 358             assertNotNull(findStructFieldGet(fooCls, "color"));
 359             // make sure nested types are handled without nested namespace!
 360             assertNotNull(loadClass("nested$Bar", nestedJar));
 361             assertNotNull(loadClass("nested$Color", nestedJar));
 362 
 363             Class<?> uCls = loadClass("nested$U", nestedJar);
 364             assertNotNull(uCls);
 365             // union U has no getters for "x", "y" etc.
 366             assertNull(findStructFieldGet(uCls, "x"));
 367             assertNull(findStructFieldGet(uCls, "y"));
 368             // union U has getters for point, rgb, i
 369             assertNotNull(findStructFieldGet(uCls, "point"));
 370             assertNotNull(findStructFieldGet(uCls, "rgb"));
 371             assertNotNull(findStructFieldGet(uCls, "i"));
 372             // make sure nested types are handled without nested namespace!
 373             assertNotNull(loadClass("nested$Point", nestedJar));
 374             assertNotNull(loadClass("nested$RGB", nestedJar));
 375 
 376             Class<?> myStructCls = loadClass("nested$MyStruct", nestedJar);
 377             assertNotNull(findStructFieldGet(myStructCls, "a"));
 378             assertNotNull(findStructFieldGet(myStructCls, "b"));
 379             assertNotNull(findStructFieldGet(myStructCls, "c"));
 380             assertNotNull(findStructFieldGet(myStructCls, "d"));
 381             // 'e' is named struct element - should not be in MyStruct
 382             assertNull(findStructFieldGet(myStructCls, "e"));
 383             assertNotNull(findStructFieldGet(myStructCls, "f"));
 384             assertNotNull(findStructFieldGet(myStructCls, "g"));
 385             assertNotNull(findStructFieldGet(myStructCls, "h"));
 386             // 'i' is named struct element - should not be in MyStruct
 387             assertNull(findStructFieldGet(myStructCls, "i"));
 388             // 'j' is named struct element - should not be in MyStruct
 389             assertNull(findStructFieldGet(myStructCls, "j"));
 390             assertNotNull(findStructFieldGet(myStructCls, "k"));
 391             // "X", "Y", "Z" are enum constants -should not be in MyStruct
 392             assertNull(findStructFieldGet(myStructCls, "X"));
 393             assertNull(findStructFieldGet(myStructCls, "Y"));
 394             assertNull(findStructFieldGet(myStructCls, "Z"));
 395             // anonymous enum constants are hoisted to containing scope
 396             assertNotNull(findField(headerCls, "X"));
 397             assertNotNull(findField(headerCls, "Y"));
 398             assertNotNull(findField(headerCls, "Z"));
 399 
 400             Class<?> myUnionCls = loadClass("nested$MyUnion", nestedJar);
 401             assertNotNull(findStructFieldGet(myUnionCls, "a"));
 402             assertNotNull(findStructFieldGet(myUnionCls, "b"));
 403             assertNotNull(findStructFieldGet(myUnionCls, "c"));
 404             assertNotNull(findStructFieldGet(myUnionCls, "d"));
 405             // 'e' is named struct element - should not be in MyUnion
 406             assertNull(findStructFieldGet(myUnionCls, "e"));
 407             assertNotNull(findStructFieldGet(myUnionCls, "f"));
 408             assertNotNull(findStructFieldGet(myUnionCls, "g"));
 409             assertNotNull(findStructFieldGet(myUnionCls, "h"));
 410             // 'i' is named struct element - should not be in MyUnion
 411             assertNull(findStructFieldGet(myUnionCls, "i"));
 412             // 'j' is named struct element - should not be in MyUnion
 413             assertNull(findStructFieldGet(myUnionCls, "j"));
 414             assertNotNull(findStructFieldGet(myUnionCls, "k"));
 415             // "A", "B", "C" are enum constants -should not be in MyUnion
 416             assertNull(findStructFieldGet(myUnionCls, "A"));
 417             assertNull(findStructFieldGet(myUnionCls, "B"));
 418             assertNull(findStructFieldGet(myUnionCls, "C"));
 419             // anonymous enum constants are hoisted to containing scope
 420             assertNotNull(findField(headerCls, "A"));
 421             assertNotNull(findField(headerCls, "B"));
 422             assertNotNull(findField(headerCls, "C"));
 423         } finally {
 424             deleteFile(nestedJar);
 425         }
 426     }
 427 
 428     @Test
 429     public void testAnonymousStructTypeGlobalVar() {
 430         Path elaboratedTypeJar = getOutputFilePath("elaboratedtype.jar");
 431         deleteFile(elaboratedTypeJar);
 432         Path elaboratedTypeH = getInputFilePath("elaboratedtype.h");
 433         try {
 434             checkSuccess(null, "-o", elaboratedTypeJar.toString(), elaboratedTypeH.toString());
 435             Class<?> headerCls = loadClass("elaboratedtype", elaboratedTypeJar);
 436             assertNotNull(findGlobalVariableGet(headerCls, "point"));
 437             assertNotNull(findGlobalVariableGet(headerCls, "long_or_int"));
 438             assertNotNull(findMethod(headerCls, "func", Pointer.class));
 439         } finally {
 440             deleteFile(elaboratedTypeJar);
 441         }
 442     }
 443 
 444     @Test
 445     public void testBuiltinHeader() {
 446         Path stdargincJar = getOutputFilePath("stdarginc.jar");
 447         deleteFile(stdargincJar);
 448         Path stdargincH = getInputFilePath("stdarginc.h");
 449         checkSuccess(null, "-o", stdargincJar.toString(), stdargincH.toString());
 450         deleteFile(stdargincJar);
 451     }
 452 
 453     @Test
 454     public void testGlobalFuncPointerCallback() {
 455         Path globalFuncPointerJar = getOutputFilePath("globalFuncPointer.jar");
 456         deleteFile(globalFuncPointerJar);
 457         Path globalFuncPointerH = getInputFilePath("globalFuncPointer.h");
 458         checkSuccess(null, "-o", globalFuncPointerJar.toString(), globalFuncPointerH.toString());
 459         Class<?> callbackCls = loadClass("globalFuncPointer$FI1", globalFuncPointerJar);
 460         Method callback = findFirstMethod(callbackCls, "fn");
 461         assertNotNull(callback);
 462         assertTrue(callback.isVarArgs());
 463         deleteFile(globalFuncPointerJar);
 464     }
 465 
 466     @Test
 467     public void testFuncPtrTypedef() {
 468         Path funcPtrTypedefJar = getOutputFilePath("funcPtrTypedef.jar");
 469         deleteFile(funcPtrTypedefJar);
 470         Path funcPtrTypedefH = getInputFilePath("funcPtrTypedef.h");
 471         checkSuccess(null, "-o", funcPtrTypedefJar.toString(), funcPtrTypedefH.toString());
 472         // force parsing of class, method
 473         Class<?> headerCls = loadClass("funcPtrTypedef", funcPtrTypedefJar);
 474         Method getter = findFirstMethod(headerCls, "my_function$get");
 475         assertNotNull(getter);
 476         assertNotNull(getter.getGenericParameterTypes());
 477         deleteFile(funcPtrTypedefJar);
 478     }
 479 }