1 /*
  2  * Copyright (c) 2020, 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.io.IOException;
 25 import java.io.PrintWriter;
 26 import java.io.StringWriter;
 27 import java.lang.invoke.MethodType;
 28 import java.lang.reflect.Field;
 29 import java.lang.reflect.Method;
 30 import java.net.URL;
 31 import java.net.URLClassLoader;
 32 import java.nio.file.FileVisitResult;
 33 import java.nio.file.Files;
 34 import java.nio.file.Path;
 35 import java.nio.file.Paths;
 36 import java.nio.file.SimpleFileVisitor;
 37 import java.nio.file.attribute.BasicFileAttributes;
 38 import java.util.Arrays;
 39 import java.util.Objects;
 40 import java.util.spi.ToolProvider;
 41 
 42 import jdk.incubator.foreign.GroupLayout;
 43 import jdk.incubator.foreign.MemoryLayout;
 44 import jdk.incubator.foreign.MemoryLayout.PathElement;
 45 import jdk.incubator.foreign.ValueLayout;
 46 import jdk.incubator.jextract.Type;
 47 import jdk.test.lib.util.FileUtils;
 48 
 49 import static org.testng.Assert.assertEquals;
 50 import static org.testng.Assert.assertNotEquals;
 51 import static org.testng.Assert.assertNotNull;
 52 import static org.testng.Assert.assertTrue;
 53 import static org.testng.Assert.fail;
 54 
 55 public class JextractToolRunner {
 56 
 57     private static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows");
 58 
 59     public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
 60     public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
 61     public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
 62     public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
 63     public static final ValueLayout C_LONG = IS_WINDOWS ? ValueLayout.JAVA_INT : ValueLayout.JAVA_LONG;
 64     public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
 65     public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
 66     public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
 67     public static final ValueLayout.OfAddress C_POINTER = ValueLayout.ADDRESS;
 68 
 69     // (private) exit codes from jextract tool. Copied from JextractTool.
 70     static final int SUCCESS       = 0;
 71     static final int OPTION_ERROR  = 1;
 72     static final int INPUT_ERROR   = 2;
 73     static final int CLANG_ERROR   = 3;
 74     static final int RUNTIME_ERROR = 4;
 75     static final int OUTPUT_ERROR  = 5;
 76 
 77     private static String safeFileName(String filename) {
 78         int ext = filename.lastIndexOf('.');
 79         return ext != -1 ? filename.substring(0, ext) : filename;
 80     }
 81 
 82     private static final ToolProvider JEXTRACT_TOOL = ToolProvider.findFirst("jextract")
 83             .orElseThrow(() ->
 84                     new RuntimeException("jextract tool not found")
 85             );
 86 
 87     private final Path inputDir;
 88     private final Path outputDir;
 89 
 90     protected JextractToolRunner() {
 91         this(null, null);
 92     }
 93 
 94     protected JextractToolRunner(Path input, Path output) {
 95         inputDir = (input != null) ? input :
 96                 Paths.get(System.getProperty("test.src", "."));
 97         outputDir = (output != null) ? output :
 98                 Paths.get(System.getProperty("test.classes", "."));
 99     }
100 
101     protected Path getInputFilePath(String fileName) {
102         return inputDir.resolve(fileName).toAbsolutePath();
103     }
104 
105     protected Path getOutputFilePath(String fileName) {
106         return outputDir.resolve(fileName).toAbsolutePath();
107     }
108 
109     protected static class JextractResult {
110         private int exitCode;
111         private String output;
112 
113         JextractResult(int exitCode, String output) {
114             this.exitCode = exitCode;
115             this.output = output;
116         }
117 
118         protected JextractResult checkSuccess() {
119             assertEquals(exitCode, SUCCESS, "Sucess expected, failed: " + exitCode);
120             return this;
121         }
122 
123         protected JextractResult checkFailure() {
124             assertNotEquals(exitCode, SUCCESS, "Failure expected, succeeded!");
125             return this;
126         }
127 
128         protected JextractResult checkFailure(int expectedExitCode) {
129             assertEquals(exitCode, expectedExitCode, "Expected error code " + expectedExitCode);
130             return this;
131         }
132 
133         protected JextractResult checkContainsOutput(String expected) {
134             Objects.requireNonNull(expected);
135             assertTrue(output.contains(expected), "Output does not contain string: " + expected);
136             return this;
137         }
138 
139         protected JextractResult checkMatchesOutput(String regex) {
140             Objects.requireNonNull(regex);
141             assertTrue(output.trim().matches(regex), "Output does not match regex: " + regex);
142             return this;
143         }
144     }
145 
146     protected static JextractResult run(Object... options) {
147         return run(Arrays.stream(options).map(Objects::toString).toArray(String[]::new));
148     }
149 
150     protected static JextractResult run(String... options) {
151         StringWriter writer = new StringWriter();
152         PrintWriter pw = new PrintWriter(writer);
153         String[] args = new String[options.length + 1];
154         int result = JEXTRACT_TOOL.run(pw, pw, options);
155         String output = writer.toString();
156         System.err.println(output);
157         return new JextractResult(result, output);
158     }
159 
160     protected static void deleteFile(Path path) {
161         try {
162             FileUtils.deleteFileIfExistsWithRetry(path);
163         } catch (IOException ioExp) {
164             throw new RuntimeException(ioExp);
165         }
166     }
167 
168     protected static void deleteDir(Path path) {
169         try {
170             FileUtils.deleteFileTreeWithRetry(path);
171         } catch (IOException ioExp) {
172             throw new RuntimeException(ioExp);
173         }
174     }
175 
176     protected static Loader classLoader(Path... paths) {
177         try {
178             URL[] urls = new URL[paths.length];
179             for (int i = 0; i < paths.length; i++) {
180                 urls[i] = paths[i].toUri().toURL();
181             }
182             URLClassLoader ucl = new URLClassLoader(urls,
183                     JextractToolRunner.class.getClassLoader());
184             return new Loader(ucl);
185         } catch (RuntimeException re) {
186             throw re;
187         } catch (Exception e) {
188             throw new RuntimeException(e);
189         }
190     }
191 
192     protected static Field findField(Class<?> cls, String name) {
193         try {
194             return cls.getField(name);
195         } catch (Exception e) {
196             System.err.println(e);
197             return null;
198         }
199     }
200 
201     protected Method checkIntGetter(Class<?> cls, String name, int value) {
202         Method method = findMethod(cls, name);
203         assertNotNull(method);
204         assertEquals(method.getReturnType(), int.class);
205         try {
206             assertEquals((int)method.invoke(null), value);
207         } catch (Exception exp) {
208             System.err.println(exp);
209             assertTrue(false, "should not reach here");
210         }
211         return method;
212     }
213 
214     protected static Method findMethod(Class<?> cls, String name, Class<?>... argTypes) {
215         try {
216             return cls.getMethod(name, argTypes);
217         } catch (Exception e) {
218             System.err.println(e);
219             return null;
220         }
221     }
222 
223     protected static Method findFirstMethod(Class<?> cls, String name) {
224         try {
225             for (Method m : cls.getMethods()) {
226                 if (name.equals(m.getName())) {
227                     return m;
228                 }
229             }
230             return null;
231         } catch (Exception e) {
232             System.err.println(e);
233             return null;
234         }
235     }
236 
237     protected static Class<?> findNestedClass(Class<?> clz, String name) {
238         return findClass(clz.getClasses(), name);
239     }
240 
241     protected static Class<?> findClass(Class<?>[] clz, String name) {
242         for (Class<?> cls: clz) {
243             if (cls.getSimpleName().equals(name)) {
244                 return cls;
245             }
246         }
247         return null;
248     }
249 
250     protected Method checkMethod(Class<?> cls, String name, MethodType type) {
251         return checkMethod(cls, name, type.returnType(), type.parameterArray());
252     }
253 
254     protected Method checkMethod(Class<?> cls, String name, Class<?> returnType, Class<?>... args) {
255         Method m = findMethod(cls, name, args);
256         assertNotNull(m);
257         assertEquals(m.getReturnType(), returnType);
258         assertEquals(m.getParameterTypes(), args);
259         return m;
260     }
261 
262     protected static MemoryLayout findLayout(Class<?> cls, String name) {
263         Method method = findMethod(cls, name + "$LAYOUT");
264         assertNotNull(method);
265         assertEquals(method.getReturnType(), MemoryLayout.class);
266         try {
267             return (MemoryLayout)method.invoke(null);
268         } catch (Exception exp) {
269             System.err.println(exp);
270             assertTrue(false, "should not reach here");
271         }
272         return null;
273     }
274 
275     protected static MemoryLayout findLayout(Class<?> cls) {
276         return findLayout(cls, "");
277     }
278 
279     protected static void checkField(MemoryLayout group, String fieldName, MemoryLayout expected) {
280         assertEquals(group.select(PathElement.groupElement(fieldName)), expected.withName(fieldName));
281     }
282 
283     protected static class Loader implements AutoCloseable {
284 
285         private final URLClassLoader loader;
286 
287         public Loader(URLClassLoader loader) {
288             this.loader = loader;
289         }
290 
291         public Class<?> loadClass(String className) {
292             try {
293                 return Class.forName(className, false, loader);
294             } catch (ClassNotFoundException e) {
295                 // return null so caller can check if class loading
296                 // was successful with assertNotNull/assertNull
297                 return null;
298             }
299         }
300 
301         @Override
302         public void close() {
303             try {
304                 loader.close();
305             } catch (IOException e) {
306                 throw new RuntimeException(e);
307             }
308         }
309     }
310 }