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 }