1 /*
   2  * Copyright (c) 2019, 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.foreign.annotations.NativeAddressof;
  25 import java.foreign.annotations.NativeGetter;
  26 import java.foreign.annotations.NativeHeader;
  27 import java.foreign.annotations.NativeLocation;
  28 import java.foreign.annotations.NativeSetter;
  29 import java.foreign.annotations.NativeStruct;
  30 import java.foreign.memory.Pointer;
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.lang.reflect.Method;
  34 import java.lang.reflect.Modifier;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.nio.file.Paths;
  38 import java.util.Arrays;
  39 import java.util.ArrayList;
  40 import java.util.spi.ToolProvider;
  41 import org.testng.annotations.Test;
  42 import static org.testng.Assert.assertTrue;
  43 
  44 /*
  45  * @test
  46  * @bug 8221154 8221228 8221336 8221419 8221443 8222274 8222288
  47  * @summary jextract should generate java source files
  48  * @library ..
  49  * @run testng SrcGenTest
  50  */
  51 public class SrcGenTest extends JextractToolRunner {
  52     private static final ToolProvider JEXTRACT = ToolProvider.findFirst("jextract")
  53             .orElseThrow(() ->
  54                     new RuntimeException("jextract tool not found")
  55             );
  56 
  57     private static final ToolProvider JAVAC = ToolProvider.findFirst("javac")
  58             .orElseThrow(() ->
  59                     new RuntimeException("javac tool not found")
  60             );
  61 
  62     @Test
  63     public void test() throws IOException {
  64         Path inputDir = Paths.get(System.getProperty("test.src", "."));
  65         Path outputDir = Paths.get(System.getProperty("test.classes", "."));
  66         inputDir = inputDir.toAbsolutePath();
  67         outputDir = outputDir.toAbsolutePath();
  68         String pkgName = "test8221154";
  69         Path jarPath = outputDir.resolve(pkgName + ".jar");
  70 
  71         // run jextract with --src-dump-dir option
  72         ArrayList<String> jextrOpts = new ArrayList<>();
  73         jextrOpts.add("-C-nostdinc");
  74         jextrOpts.add("-I");
  75         jextrOpts.add(inputDir.toString());
  76         jextrOpts.add("-o");
  77         jextrOpts.add(jarPath.toString());
  78         jextrOpts.add("--src-dump-dir");
  79         jextrOpts.add(outputDir.toString());
  80         jextrOpts.add("-t");
  81         jextrOpts.add(pkgName);
  82         jextrOpts.add("-l");
  83         jextrOpts.add("srcgentest");
  84         jextrOpts.add(inputDir + File.separator + "srcgentest.h");
  85 
  86         int result = JEXTRACT.run(System.out, System.err, jextrOpts.toArray(String[]::new));
  87         if (result != 0) {
  88             throw new RuntimeException(JEXTRACT.name() + " returns non-zero value");
  89         }
  90 
  91         // delete .jar file generated by jextract
  92         Files.delete(jarPath);
  93 
  94         Path pkgDir = outputDir.resolve(pkgName);
  95 
  96         String srcgentestIfaceName = headerInterfaceName("srcgentest.h");
  97         String srcgentestForwarderName = staticForwarderName("srcgentest.h");
  98         String dupnameIfaceName = headerInterfaceName("dupname.h");
  99         String dupnameDupnameNotName = structInterfaceName("dupname.h", "dupnameNot");
 100         String dupnameForwarderName = staticForwarderName("dupname.h");
 101         String srcgentestPointName = structInterfaceName("srcgentest.h", "Point");
 102         String srcgentestColorName = enumInterfaceName("srcgentest.h", "Color");
 103         String srcgentestForwarderEnumName = enumForwarderInterfaceName("srcgentest.h", "Color");
 104 
 105         // compile jextract generated java sources
 106         ArrayList<String> javacOpts = new ArrayList<>();
 107         javacOpts.add("-d");
 108         javacOpts.add(outputDir.toString());
 109         javacOpts.add(outputDir.resolve("clang_support").resolve("stdbool_h.java").toString());
 110         javacOpts.add(pkgDir.resolve(srcgentestIfaceName + ".java").toString());
 111         javacOpts.add(pkgDir.resolve(srcgentestForwarderName + ".java").toString());
 112         javacOpts.add(pkgDir.resolve("sub").resolve(dupnameIfaceName + ".java").toString());
 113         javacOpts.add(pkgDir.resolve("sub").resolve(dupnameForwarderName + ".java").toString());
 114         result = JAVAC.run(System.out, System.err, javacOpts.toArray(String[]::new));
 115         if (result != 0) {
 116             throw new RuntimeException(JAVAC.name() + " returns non-zero value");
 117         }
 118 
 119         // sanity checks for .class file existence
 120         assertTrue(Files.isRegularFile(outputDir.resolve("clang_support").resolve("stdbool_h.class")));
 121         assertTrue(Files.isRegularFile(pkgDir.resolve(srcgentestIfaceName + ".class")));
 122         assertTrue(Files.isRegularFile(pkgDir.resolve(srcgentestPointName + ".class")));
 123         assertTrue(Files.isRegularFile(pkgDir.resolve(srcgentestColorName + ".class")));
 124         assertTrue(Files.isRegularFile(pkgDir.resolve(srcgentestForwarderName + ".class")));
 125         assertTrue(Files.isRegularFile(pkgDir.resolve(srcgentestForwarderEnumName + ".class")));
 126         assertTrue(Files.isRegularFile(pkgDir.resolve("sub").resolve(dupnameForwarderName + ".class")));
 127         assertTrue(Files.isRegularFile(pkgDir.resolve("sub").resolve(dupnameIfaceName + ".class")));
 128         assertTrue(Files.isRegularFile(pkgDir.resolve("sub").resolve(dupnameDupnameNotName + ".class")));
 129 
 130         checkClasses(outputDir, pkgName);
 131     }
 132 
 133     private void checkClasses(Path outputDir, String pkgName) {
 134         Loader loader = classLoader(outputDir);
 135         checkSubdirClass(loader, pkgName);
 136         checkHeaderClass(loader, pkgName);
 137         checkForwarderClass(loader, pkgName);
 138     }
 139 
 140     private void checkSubdirClass(Loader loader, String pkgName) {
 141         Class<?> dupname = loader.loadClass(pkgName + ".sub." + headerInterfaceName("dupname.h"));
 142         assertTrue(dupname != null);
 143     }
 144 
 145     private void checkHeaderClass(Loader loader, String pkgName) {
 146         Class<?> headerCls = loader.loadClass(pkgName + "." + headerInterfaceName("srcgentest.h"));
 147         assertTrue(headerCls != null);
 148         assertTrue(headerCls.getAnnotation(NativeHeader.class) != null);
 149 
 150         // global 'num' getter, pointer getter
 151         Method numGetter = findGlobalVariableGet(headerCls, "num");
 152         assertTrue(numGetter != null);
 153         assertTrue(numGetter.getAnnotation(NativeGetter.class) != null);
 154         assertTrue(numGetter.getAnnotation(NativeLocation.class) != null);
 155 
 156         Method numPtrGetter = findGlobalVariablePointerGet(headerCls, "num");
 157         assertTrue(numPtrGetter != null);
 158         assertTrue(numPtrGetter.getAnnotation(NativeAddressof.class) != null);
 159 
 160         // global 'num' setter
 161         Method numSetter = findGlobalVariableSet(headerCls, "num", int.class);
 162         assertTrue(numSetter != null);
 163         assertTrue(numSetter.getAnnotation(NativeSetter.class) != null);
 164 
 165         // check "x_coord" method
 166         Method xCoordMethod = findFirstMethod(headerCls, "x_coord");
 167         assertTrue(xCoordMethod.getReturnType() == int.class);
 168         Class<?>[] xCoordParamTypes = xCoordMethod.getParameterTypes();
 169         assertTrue(xCoordParamTypes.length == 1);
 170         assertTrue(xCoordParamTypes[0] == Pointer.class);
 171         assertTrue(xCoordMethod.getAnnotation(NativeLocation.class) != null);
 172         assertTrue(!Modifier.isStatic(xCoordMethod.getModifiers()));
 173 
 174         // check "y_coord" method
 175         Method yCoordMethod = findFirstMethod(headerCls, "y_coord");
 176         assertTrue(yCoordMethod.getReturnType() == int.class);
 177         Class<?>[] yCoordParamTypes = yCoordMethod.getParameterTypes();
 178         assertTrue(yCoordParamTypes.length == 1);
 179         assertTrue(yCoordParamTypes[0] == Pointer.class);
 180         assertTrue(yCoordMethod.getAnnotation(NativeLocation.class) != null);
 181         assertTrue(!Modifier.isStatic(yCoordMethod.getModifiers()));
 182 
 183         // check "sum" method
 184         Method sumMethod = findFirstMethod(headerCls, "sum");
 185         assertTrue(sumMethod.getReturnType() == int.class);
 186         Class<?>[] sumParamTypes = sumMethod.getParameterTypes();
 187         assertTrue(sumParamTypes.length == 2);
 188         assertTrue(sumParamTypes[0] == int.class);
 189         assertTrue(sumParamTypes[1] == Object[].class);
 190         assertTrue(sumMethod.getAnnotation(NativeLocation.class) != null);
 191         assertTrue(!Modifier.isStatic(sumMethod.getModifiers()));
 192 
 193         // struct Point
 194         Class<?> pointCls = Arrays.stream(headerCls.getClasses())
 195             .filter(c -> c.getSimpleName().equals("Point")).findFirst().get();
 196         assertTrue(Modifier.isInterface(pointCls.getModifiers()));
 197         assertTrue(pointCls.getAnnotation(NativeStruct.class) != null);
 198         assertTrue(pointCls.getAnnotation(NativeLocation.class) != null);
 199 
 200         // Point extends Struct
 201         Class<?> pointSuper = pointCls.getInterfaces()[0];
 202         assertTrue(pointSuper.getName().equals("java.foreign.memory.Struct"));
 203 
 204         // x, y getters, pointer getters
 205         Method xGetter = findStructFieldGet(pointCls, "x");
 206         assertTrue(xGetter != null);
 207         assertTrue(xGetter.getAnnotation(NativeGetter.class) != null);
 208         assertTrue(xGetter.getAnnotation(NativeLocation.class) != null);
 209 
 210         Method yGetter = findStructFieldGet(pointCls, "y");
 211         assertTrue(yGetter != null);
 212         assertTrue(yGetter.getAnnotation(NativeGetter.class) != null);
 213         assertTrue(yGetter.getAnnotation(NativeLocation.class) != null);
 214 
 215         Method xPtrGetter = findStructFieldPointerGet(pointCls, "x");
 216         assertTrue(xPtrGetter != null);
 217         assertTrue(xPtrGetter.getAnnotation(NativeAddressof.class) != null);
 218 
 219         Method yPtrGetter = findStructFieldPointerGet(pointCls, "y");
 220         assertTrue(yPtrGetter != null);
 221         assertTrue(yPtrGetter.getAnnotation(NativeAddressof.class) != null);
 222 
 223         // x, y setters
 224         Method xSetter = findStructFieldSet(pointCls, "x", int.class);
 225         assertTrue(xSetter != null);
 226         assertTrue(xSetter.getAnnotation(NativeSetter.class) != null);
 227 
 228         Method ySetter = findStructFieldSet(pointCls, "y", int.class);
 229         assertTrue(ySetter != null);
 230         assertTrue(ySetter.getAnnotation(NativeSetter.class) != null);
 231     }
 232 
 233     private void checkForwarderClass(Loader loader, String pkgName) {
 234         Class<?> forwarderCls = loader.loadClass(pkgName + "." + staticForwarderName("srcgentest.h"));
 235         assertTrue(forwarderCls != null);
 236 
 237         // check "sum" method
 238         Method sumMethod = findFirstMethod(forwarderCls, "sum");
 239         assertTrue(sumMethod.getReturnType() == int.class);
 240         Class<?>[] sumParamTypes = sumMethod.getParameterTypes();
 241         assertTrue(sumParamTypes.length == 2);
 242         assertTrue(sumParamTypes[0] == int.class);
 243         assertTrue(sumParamTypes[1] == Object[].class);
 244         assertTrue(Modifier.isStatic(sumMethod.getModifiers()));
 245 
 246         // check "x_coord" method
 247         Method xCoordMethod = findFirstMethod(forwarderCls, "x_coord");
 248         assertTrue(xCoordMethod.getReturnType() == int.class);
 249         Class<?>[] xCoordParamTypes = xCoordMethod.getParameterTypes();
 250         assertTrue(xCoordParamTypes.length == 1);
 251         assertTrue(xCoordParamTypes[0] == Pointer.class);
 252         assertTrue(Modifier.isStatic(xCoordMethod.getModifiers()));
 253 
 254         // check "y_coord" method
 255         Method yCoordMethod = findFirstMethod(forwarderCls, "y_coord");
 256         assertTrue(yCoordMethod.getReturnType() == int.class);
 257         Class<?>[] yCoordParamTypes = yCoordMethod.getParameterTypes();
 258         assertTrue(yCoordParamTypes.length == 1);
 259         assertTrue(yCoordParamTypes[0] == Pointer.class);
 260         assertTrue(Modifier.isStatic(xCoordMethod.getModifiers()));
 261 
 262         // global variable "num" getter
 263         Method numGet = findGlobalVariableGet(forwarderCls, "num");
 264         assertTrue(numGet != null);
 265 
 266         // anonymous enum fields
 267         assertTrue(findField(forwarderCls, "R") != null);
 268         assertTrue(findField(forwarderCls, "G") != null);
 269         assertTrue(findField(forwarderCls, "B") != null);
 270 
 271         // enum interface class
 272         Class<?> colorCls = Arrays.stream(forwarderCls.getClasses())
 273             .filter(c -> c.getSimpleName().equals("Color")).findFirst().get();
 274         assertTrue(Modifier.isInterface(colorCls.getModifiers()));
 275         checkIntField(colorCls, "RED", 0);
 276         checkIntField(colorCls, "GREEN", 1);
 277         checkIntField(colorCls, "BLUE", 2);
 278     }
 279 }