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.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 findFirstMethod(Class<?> cls, String name) {
 144         try {
 145             for (Method m : cls.getMethods()) {
 146                 if (name.equals(m.getName())) {
 147                     return m;
 148                 }
 149             }
 150             return null;
 151         } catch (Exception e) {
 152             System.err.println(e);
 153             return null;
 154         }
 155     }
 156 
 157     @Test
 158     public void testHelp() {
 159         checkFailure(null); // no options
 160         checkSuccess(null, "--help");
 161         checkSuccess(null, "-h");
 162         checkSuccess(null, "-?");
 163     }
 164 
 165     // error for non-existent header file
 166     @Test
 167     public void testNonExistentHeader() {
 168         checkFailure("Cannot open header file", "--dry-run",
 169             getInputFilePath("non_existent.h").toString());
 170     }
 171 
 172     @Test
 173     public void testDryRun() {
 174         // only dry-run, don't produce any output
 175         Path simpleJar = getOutputFilePath("simple.jar");
 176         deleteFile(simpleJar);
 177         checkSuccess(null, "--dry-run", getInputFilePath("simple.h").toString());
 178         try {
 179             assertFalse(Files.isRegularFile(simpleJar));
 180         } finally {
 181             deleteFile(simpleJar);
 182         }
 183     }
 184 
 185     @Test
 186     public void testOutputFileOption() {
 187         // simple output file check
 188         Path simpleJar = getOutputFilePath("simple.jar");
 189         deleteFile(simpleJar);
 190         checkSuccess(null, "-o", simpleJar.toString(),
 191             getInputFilePath("simple.h").toString());
 192         try {
 193             assertTrue(Files.isRegularFile(simpleJar));
 194         } finally {
 195             deleteFile(simpleJar);
 196         }
 197     }
 198 
 199     @Test
 200     public void testOutputClass() {
 201         Path helloJar = getOutputFilePath("hello.jar");
 202         deleteFile(helloJar);
 203         Path helloH = getInputFilePath("hello.h");
 204         checkSuccess(null, "-o", helloJar.toString(), helloH.toString());
 205         try {
 206             Class<?> cls = loadClass("hello", helloJar);
 207             // check header annotation
 208             Header header = cls.getAnnotation(Header.class);
 209             assertNotNull(header);
 210             assertEquals(header.path(), helloH.toString());
 211 
 212             // check a method for "void func()"
 213             assertNotNull(findMethod(cls, "func", Object[].class));
 214         } finally {
 215             deleteFile(helloJar);
 216         }
 217     }
 218 
 219     private void testTargetPackage(String targetPkgOption) {
 220         Path helloJar = getOutputFilePath("hello.jar");
 221         deleteFile(helloJar);
 222         Path helloH = getInputFilePath("hello.h");
 223         checkSuccess(null, targetPkgOption, "com.acme", "-o", helloJar.toString(), helloH.toString());
 224         try {
 225             Class<?> cls = loadClass("com.acme.hello", helloJar);
 226             // check header annotation
 227             Header header = cls.getAnnotation(Header.class);
 228             assertNotNull(header);
 229             assertEquals(header.path(), helloH.toString());
 230 
 231             // check a method for "void func()"
 232             assertNotNull(findMethod(cls, "func", Object[].class));
 233         } finally {
 234             deleteFile(helloJar);
 235         }
 236     }
 237 
 238     @Test
 239     public void testTargetPackageOption() {
 240         testTargetPackage("-t");
 241     }
 242 
 243     @Test
 244     public void testTargetPackageLongOption() {
 245         testTargetPackage("--target-package");
 246     }
 247 
 248     private void testPackageMapping(String pkgMapOption) {
 249         Path worldJar = getOutputFilePath("world.jar");
 250         deleteFile(worldJar);
 251         Path mytypesJar = getOutputFilePath("mytypes.jar");
 252         deleteFile(mytypesJar);
 253 
 254         Path worldH = getInputFilePath("world.h");
 255         Path include = getInputFilePath("include");
 256         // generate jar for mytypes.h
 257         checkSuccess(null, "-t", "com.acme", "-o", mytypesJar.toString(),
 258             include.resolve("mytypes.h").toString());
 259         // world.h include mytypes.h, use appropriate package for stuff from mytypes.h
 260         checkSuccess(null, "-I", include.toString(), pkgMapOption, include.toString() + "=com.acme",
 261             "-o", worldJar.toString(), worldH.toString());
 262         try {
 263             Class<?> cls = loadClass("world", worldJar, mytypesJar);
 264             Method m = findFirstMethod(cls, "distance");
 265             Class<?>[] params = m.getParameterTypes();
 266             assertEquals(params[0].getName(), "com.acme.mytypes$Point");
 267         } finally {
 268             deleteFile(worldJar);
 269             deleteFile(mytypesJar);
 270         }
 271     }
 272 
 273     @Test
 274     public void testPackageDirMappingOption() {
 275         testPackageMapping("-m");
 276     }
 277 
 278     @Test
 279     public void testPackageDirMappingLongOption() {
 280         testPackageMapping("--package-map");
 281     }
 282 
 283     @Test
 284     public void test_option_L_without_l() {
 285         Path helloJar = getOutputFilePath("hello.jar");
 286         deleteFile(helloJar);
 287         Path helloH = getInputFilePath("hello.h");
 288         Path linkDir = getInputFilePath("libs");
 289         String warning = "WARNING: -L option specified without any -l option";
 290         checkSuccess(warning, "-L", linkDir.toString(), "-o", helloJar.toString(), helloH.toString());
 291     }
 292 
 293     @Test
 294     public void test_option_rpath_without_l() {
 295         Path helloJar = getOutputFilePath("hello.jar");
 296         deleteFile(helloJar);
 297         Path helloH = getInputFilePath("hello.h");
 298         Path rpathDir = getInputFilePath("libs");
 299         String warning = "WARNING: -rpath option specified without any -l option";
 300         try {
 301             checkSuccess(warning, "-rpath", rpathDir.toString(), "-o", helloJar.toString(), helloH.toString());
 302         } finally {
 303             deleteFile(helloJar);
 304         }
 305     }
 306 
 307     @Test
 308     public void test_option_l() {
 309         Path helloJar = getOutputFilePath("hello.jar");
 310         deleteFile(helloJar);
 311         Path helloH = getInputFilePath("hello.h");
 312         checkSuccess(null, "-l", "hello", "-o", helloJar.toString(), helloH.toString());
 313         try {
 314             Class<?> cls = loadClass("hello", helloJar);
 315             // check LibraryDependencies annotation capture -l value
 316             LibraryDependencies libDeps = cls.getAnnotation(LibraryDependencies.class);
 317             assertNotNull(libDeps);
 318             assertEquals(libDeps.names().length, 1);
 319             assertEquals(libDeps.names()[0], "hello");
 320             // no library paths (rpath) set
 321             assertEquals(libDeps.paths().length, 0);
 322         } finally {
 323             deleteFile(helloJar);
 324         }
 325     }
 326 
 327     @Test
 328     public void test_option_l_and_rpath() {
 329         Path helloJar = getOutputFilePath("hello.jar");
 330         deleteFile(helloJar);
 331         Path helloH = getInputFilePath("hello.h");
 332         Path rpathDir = getInputFilePath("libs");
 333         checkSuccess(null, "-l", "hello", "-rpath", rpathDir.toString(),
 334              "-o", helloJar.toString(), helloH.toString());
 335         try {
 336             Class<?> cls = loadClass("hello", helloJar);
 337             // check LibraryDependencies annotation captures -l and -rpath values
 338             LibraryDependencies libDeps = cls.getAnnotation(LibraryDependencies.class);
 339             assertNotNull(libDeps);
 340             assertEquals(libDeps.names().length, 1);
 341             assertEquals(libDeps.names()[0], "hello");
 342             assertEquals(libDeps.paths().length, 1);
 343             assertEquals(libDeps.paths()[0], rpathDir.toString());
 344         } finally {
 345             deleteFile(helloJar);
 346         }
 347     }
 348 
 349     @Test
 350     public void testUnionDeclaration() {
 351         Path uniondeclJar = getOutputFilePath("uniondecl.jar");
 352         deleteFile(uniondeclJar);
 353         Path uniondeclH = getInputFilePath("uniondecl.h");
 354         try {
 355             checkSuccess(null, "-o", uniondeclJar.toString(), uniondeclH.toString());
 356             Class<?> unionCls = loadClass("uniondecl", uniondeclJar);
 357             assertNotNull(unionCls);
 358             boolean found = Arrays.stream(unionCls.getClasses()).
 359                 map(Class::getSimpleName).
 360                 filter(n -> n.equals("IntOrFloat")).
 361                 findFirst().isPresent();
 362             assertTrue(found, "uniondecl.IntOrFloat not found");
 363         } finally {
 364             deleteFile(uniondeclJar);
 365         }
 366     }
 367 
 368     private void checkIntField(Class<?> cls, String name, int value) {
 369         Field field = findField(cls, name);
 370         assertNotNull(field);
 371         assertEquals(field.getType(), int.class);
 372         try {
 373             assertEquals((int)field.get(null), value);
 374         } catch (Exception exp) {
 375             System.err.println(exp);
 376             assertTrue(false, "should not reach here");
 377         }
 378     }
 379 
 380     private Class<?> findClass(Class<?>[] clz, String name) {
 381         for (Class<?> cls: clz) {
 382             if (cls.getSimpleName().equals(name)) {
 383                 return cls;
 384             }
 385         }
 386         return null;
 387     }
 388 
 389     private void testEnumValue(Class<?> enumCls, Map<String, Integer> values) {
 390         values.entrySet().stream().
 391                 forEach(e -> checkIntField(enumCls, e.getKey(), e.getValue()));
 392     }
 393 
 394     @Test
 395     public void testAnonymousEnum() {
 396         Path anonenumJar = getOutputFilePath("anonenum.jar");
 397         deleteFile(anonenumJar);
 398         Path anonenumH = getInputFilePath("anonenum.h");
 399         try {
 400             checkSuccess(null, "-o", anonenumJar.toString(), anonenumH.toString());
 401             Class<?> anonenumCls = loadClass("anonenum", anonenumJar);
 402             assertNotNull(anonenumCls);
 403             checkIntField(anonenumCls, "RED", 0xff0000);
 404             checkIntField(anonenumCls, "GREEN", 0x00ff00);
 405             checkIntField(anonenumCls, "BLUE", 0x0000ff);
 406 
 407             Class<?> enumClz[] = anonenumCls.getClasses();
 408             assert(enumClz.length >= 3);
 409 
 410             Class<?> enumCls = findClass(enumClz, "codetype_t");
 411             assertNotNull(enumCls);
 412             testEnumValue(enumCls, Map.of(
 413                     "Java", 0,
 414                     "C", 1,
 415                     "CPP", 2,
 416                     "Python", 3,
 417                     "Ruby", 4));
 418 
 419             enumCls = findClass(enumClz, "SIZE");
 420             assertNotNull(enumCls);
 421             testEnumValue(enumCls, Map.of(
 422                     "XS", 0,
 423                     "S", 1,
 424                     "M", 2,
 425                     "L", 3,
 426                     "XL", 4,
 427                     "XXL", 5));
 428 
 429             enumCls = findClass(enumClz, "temp");
 430             assertNotNull(enumCls);
 431             testEnumValue(enumCls, Map.of(
 432                     "ONE", 1,
 433                     "TWO", 2));
 434 
 435             enumCls = findClass(enumClz, "temp_t");
 436             assertNull(enumCls);
 437         } finally {
 438             deleteFile(anonenumJar);
 439         }
 440     }
 441 
 442     @Test
 443     public void testExcludeSymbols() {
 444         Path helloJar = getOutputFilePath("hello.jar");
 445         deleteFile(helloJar);
 446         Path helloH = getInputFilePath("hello.h");
 447         checkSuccess(null, "-o", helloJar.toString(), helloH.toString());
 448         try {
 449             Class<?> cls = loadClass("hello", helloJar);
 450             // check a method for "void func()"
 451             assertNotNull(findMethod(cls, "func", Object[].class));
 452             // check a method for "void junk()"
 453             assertNotNull(findMethod(cls, "junk", Object[].class));
 454         } finally {
 455             deleteFile(helloJar);
 456         }
 457 
 458         // try with --exclude-symbols" this time.
 459         checkSuccess(null, "--exclude-symbols", "junk", "-o", helloJar.toString(), helloH.toString());
 460         try {
 461             Class<?> cls = loadClass("hello", helloJar);
 462             // check a method for "void func()"
 463             assertNotNull(findMethod(cls, "func", Object[].class));
 464             // check a method for "void junk()"
 465             assertNull(findMethod(cls, "junk", Object[].class));
 466         } finally {
 467             deleteFile(helloJar);
 468         }
 469     }
 470 }