e7220f8bcec83da5f9978ce41402282f10897de7
[yangtools.git] /
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.binding.codegen;
9
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.junit.jupiter.api.Assertions.assertNotNull;
13 import static org.junit.jupiter.api.Assertions.assertThrows;
14 import static org.junit.jupiter.api.Assertions.assertTrue;
15 import static org.junit.jupiter.api.Assertions.fail;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.lang.reflect.ParameterizedType;
25 import java.lang.reflect.Type;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Comparator;
31 import java.util.List;
32 import javax.tools.Diagnostic;
33 import javax.tools.ToolProvider;
34
35 public final class CompilationTestUtils {
36     static final String BASE_PKG = "org.opendaylight.yang.gen.v1";
37
38     static final Path TEST_DIR = Path.of("target", "test");
39     static final Path GENERATOR_OUTPUT_DIR = TEST_DIR.resolve("src");
40     static final Path COMPILER_OUTPUT_DIR = TEST_DIR.resolve("bin");
41
42     static final String AUGMENTATION = "interface org.opendaylight.yangtools.binding.Augmentation";
43     static final Path BASE_PATH = Path.of("org", "opendaylight", "yang", "gen", "v1");
44     static final Path BASE_SVC_PATH = Path.of("org", "opendaylight", "yang", "svc", "v1");
45     static final Path NS_TEST = BASE_PATH.resolve(Path.of("urn", "opendaylight", "test", "rev131008"));
46     static final Path NS_SVC_TEST = BASE_SVC_PATH.resolve(Path.of("urn", "opendaylight", "test", "rev131008"));
47     static final Path NS_FOO = BASE_PATH.resolve(Path.of("urn", "opendaylight", "foo", "rev131008"));
48     static final Path NS_SVC_FOO = BASE_SVC_PATH.resolve(Path.of("urn", "opendaylight", "foo", "rev131008"));
49     static final Path NS_BAR = BASE_PATH.resolve(Path.of("urn", "opendaylight", "bar", "rev131008"));
50     static final Path NS_SVC_BAR = BASE_SVC_PATH.resolve(Path.of("urn", "opendaylight", "bar", "rev131008"));
51     static final Path NS_BAZ = BASE_PATH.resolve(Path.of("urn", "opendaylight", "baz", "rev131008"));
52     static final Path NS_SVC_BAZ = BASE_SVC_PATH.resolve(Path.of("urn", "opendaylight", "baz", "rev131008"));
53     static final Path NS_BUG5882 = BASE_PATH.resolve(Path.of("urn", "yang", "foo", "rev160102"));
54
55     private CompilationTestUtils() {
56         // Hidden on purpose
57     }
58
59     static Path compilerOutput(final String name) {
60         return createDirectory(COMPILER_OUTPUT_DIR.resolve(name));
61     }
62
63     static Path generatorOutput(final String name) {
64         return createDirectory(GENERATOR_OUTPUT_DIR.resolve(name));
65     }
66
67     private static Path createDirectory(final Path path) {
68         try {
69             Files.createDirectory(path);
70         } catch (IOException e) {
71             throw new AssertionError(e);
72         }
73         return path;
74     }
75
76     /**
77      * Method to clean resources. It is manually called at the end of each test instead of marking it with @After
78      * annotation to prevent removing generated code if test fails.
79      */
80     static void cleanUp(final Path... resourceDirs) throws IOException {
81         for (var resourceDir : resourceDirs) {
82             if (Files.exists(resourceDir)) {
83                 deleteTestDir(resourceDir);
84             }
85         }
86     }
87
88     /**
89      * Asserts that class contains field with given name and type.
90      *
91      * @param clazz class to test
92      * @param name field name
93      * @param type field type
94      * @return field with given name if present in class
95      */
96     static Field assertContainsField(final Class<?> clazz, final String name, final Class<?> type) {
97         try {
98             Field field = clazz.getDeclaredField(name);
99             assertEquals(type, field.getType());
100             return field;
101         } catch (NoSuchFieldException e) {
102             throw new AssertionError("Field " + name + " does not exist in class " + clazz.getSimpleName(), e);
103         }
104     }
105
106     /**
107      * Asserts that class contains field with given name and value. Method tries to create new instance of class
108      * and get value of field. If class constructor contains any arguments, class is instantiated with null values.
109      *
110      * @param clazz class to test
111      * @param name name of field
112      * @param returnType return type of field
113      * @param expectedValue expected value of field
114      * @param constructorArgs constructor arguments of class to test
115      */
116     static void assertContainsFieldWithValue(final Class<?> clazz, final String name, final Class<?> returnType,
117             final Object expectedValue, final Class<?>... constructorArgs) {
118         Object[] initargs = null;
119         if (constructorArgs != null && constructorArgs.length > 0) {
120             initargs = new Object[constructorArgs.length];
121             for (int i = 0; i < constructorArgs.length; i++) {
122                 initargs[i] = null;
123             }
124         }
125         assertContainsFieldWithValue(clazz, name, returnType, expectedValue, constructorArgs, initargs);
126     }
127
128     /**
129      * Asserts that class contains field with given name, return type and value.
130      *
131      * @param clazz class to test
132      * @param name name of field
133      * @param returnType return type of field
134      * @param expectedValue expected value of field
135      * @param constructorArgs array of constructor arguments classes
136      * @param initargs array of constructor values
137      */
138     static void assertContainsFieldWithValue(final Class<?> clazz, final String name, final Class<?> returnType,
139             final Object expectedValue, final Class<?>[] constructorArgs, final Object... initargs) {
140         final var field = assertContainsField(clazz, name, returnType);
141         field.setAccessible(true);
142
143         final Object obj;
144         if ((field.getModifiers() & Modifier.STATIC) == 0) {
145             try {
146                 Constructor<?> cls = clazz.getDeclaredConstructor(constructorArgs);
147                 obj = cls.newInstance(initargs);
148             } catch (ReflectiveOperationException e) {
149                 throw new AssertionError("Failed to instantiate object for " + clazz, e);
150             }
151         } else {
152             obj = null;
153         }
154
155         try {
156             assertEquals(expectedValue, field.get(obj));
157         } catch (IllegalArgumentException | IllegalAccessException e) {
158             throw new AssertionError("Failed to get field " + name + " of class " + clazz, e);
159         }
160     }
161
162     /**
163      * Asserts that class contains constructor with parameter types.
164      *
165      * @param clazz class to test
166      * @param args array of argument classes
167      */
168     static Constructor<?> assertContainsConstructor(final Class<?> clazz, final Class<?>... args) {
169         try {
170             return clazz.getDeclaredConstructor(args);
171         } catch (NoSuchMethodException e) {
172             throw new AssertionError("Constructor with args " + Arrays.toString(args) + " does not exists in class "
173                     + clazz.getSimpleName(), e);
174         }
175     }
176
177     /**
178      * Asserts that class contains method with given name, return type and parameter types.
179      *
180      * @param clazz class to test
181      * @param returnType method return type
182      * @param name method name
183      * @param args array of parameter type classes
184      * @return method with given name, return type and parameter types
185      */
186     static Method assertContainsMethod(final Class<?> clazz, final Class<?> returnType, final String name,
187             final Class<?>... args) {
188         try {
189             Method method = clazz.getDeclaredMethod(name, args);
190             assertEquals(returnType, method.getReturnType());
191             return method;
192         } catch (NoSuchMethodException e) {
193             throw new AssertionError("Method " + name + " with args " + Arrays.toString(args)
194                     + " does not exists in class " + clazz.getSimpleName(), e);
195         }
196     }
197
198     /**
199      * Asserts that class contains method with given name and return type.
200      *
201      * @param clazz class to test
202      * @param returnTypeStr name of method return type
203      * @param name method name
204      * @param loader current class loader
205      */
206     static void assertContainsMethod(final Class<?> clazz, final String returnTypeStr, final String name,
207             final ClassLoader loader) {
208         Class<?> returnType;
209         try {
210             returnType = Class.forName(returnTypeStr, true, loader);
211             Method method = clazz.getMethod(name);
212             assertEquals(returnType, method.getReturnType());
213         } catch (ClassNotFoundException e) {
214             throw new AssertionError("Return type of method '" + name + "' not found", e);
215         } catch (NoSuchMethodException e) {
216             throw new AssertionError("Method " + name + " does not exists in class " + clazz.getSimpleName(), e);
217         }
218     }
219
220     /**
221      * Asserts that class contains hashCode, equals and toString methods.
222      *
223      * @param clazz class to test
224      */
225     static void assertContainsDefaultMethods(final Class<?> clazz) {
226         assertContainsMethod(clazz, Integer.TYPE, "hashCode");
227         assertContainsMethod(clazz, Boolean.TYPE, "equals", Object.class);
228         assertContainsMethod(clazz, String.class, "toString");
229     }
230
231     /**
232      * Asserts that constructor contains check for illegal argument.
233      *
234      * @param constructor constructor to invoke
235      * @param errorMsg expected error message
236      * @param args constructor arguments
237      */
238     static void assertContainsRestrictionCheck(final Constructor<?> constructor, final String errorMsg,
239             final Object... args) {
240         final var cause = assertThrows(InvocationTargetException.class, () -> constructor.newInstance(args)).getCause();
241         assertInstanceOf(IllegalArgumentException.class, cause);
242         assertEquals(errorMsg, cause.getMessage());
243     }
244
245     /**
246      * Asserts that method contains check for illegal argument.
247      *
248      * @param obj object to test (can be null, if method is static)
249      * @param method method to invoke
250      * @param errorMsg expected error message
251      * @param args constructor arguments
252      */
253     static void assertContainsRestrictionCheck(final Object obj, final Method method, final String errorMsg,
254             final Object... args) {
255         final var cause = assertThrows(InvocationTargetException.class, () -> method.invoke(obj, args)).getCause();
256         assertInstanceOf(IllegalArgumentException.class, cause);
257         assertEquals(errorMsg, cause.getMessage());
258     }
259
260     /**
261      * Asserts that class implements given interface.
262      *
263      * @param clazz source to test
264      * @param ifc expected interface
265      */
266     static void assertImplementsIfc(final Class<?> clazz, final Class<?> ifc) {
267         final var ifcsList = Arrays.asList(clazz.getInterfaces());
268         if (!ifcsList.contains(ifc)) {
269             throw new AssertionError(clazz + " should implement " + ifc);
270         }
271     }
272
273     /**
274      * Test if interface generated from augment extends Augmentation interface with correct generic type.
275      *
276      * @param clazz interface generated from augment
277      * @param genericTypeName fully qualified name of expected parameter type
278      */
279     static void testAugmentation(final Class<?> clazz, final String genericTypeName) {
280         assertImplementsParameterizedIfc(clazz, AUGMENTATION, genericTypeName);
281     }
282
283     /**
284      * Asserts that class implements interface with given name and generic type parameter.
285      *
286      * @param clazz class to test
287      * @param ifcName name of interface
288      * @param genericTypeName name of generic type
289      */
290     static void assertImplementsParameterizedIfc(final Class<?> clazz, final String ifcName,
291             final String genericTypeName) {
292         ParameterizedType ifcType = null;
293         for (var ifc : clazz.getGenericInterfaces()) {
294             if (ifc instanceof ParameterizedType pt && ifcName.equals(pt.getRawType().toString())) {
295                 ifcType = pt;
296             }
297         }
298         assertNotNull(ifcType);
299
300         Type[] typeArg = ifcType.getActualTypeArguments();
301         assertEquals(1, typeArg.length);
302         Type typeArgument = typeArg[0];
303         assertInstanceOf(Class.class, typeArgument);
304         Class<?> argClass = (Class<?>) typeArgument;
305         assertEquals(genericTypeName, argClass.getName());
306         assertTrue(argClass.isInterface());
307     }
308
309     /**
310      * Test if source code is compilable.
311      *
312      * @param sourcesOutputDir directory containing source files
313      * @param compiledOutputDir compiler output directory
314      */
315     static void testCompilation(final Path sourcesOutputDir, final Path compiledOutputDir) {
316         final var compiler = ToolProvider.getSystemJavaCompiler();
317         final var fileManager = compiler.getStandardFileManager(null, null, null);
318         final var filesList = getJavaFiles(sourcesOutputDir);
319         final var compilationUnits = fileManager.getJavaFileObjectsFromPaths(filesList);
320         final var options = List.of("-d", compiledOutputDir.toAbsolutePath().toString());
321
322         final var diags = new ArrayList<Diagnostic<?>>();
323         if (!compiler.getTask(null, null, diags::add, options, null, compilationUnits).call()) {
324             fail("Compilation failed with " + diags);
325         }
326     }
327
328     /**
329      * Asserts that directory contains exactly given count of files.
330      *
331      * @param dir
332      *            directory to test
333      * @param count
334      *            expected count of files in directory
335      */
336     static void assertFilesCount(final Path dir, final int count) {
337         try (var files = Files.list(dir)) {
338             assertEquals(count, files.count(), "Unexpected count of generated files");
339         } catch (IOException e) {
340             throw new AssertionError(e);
341         }
342     }
343
344     /**
345      * Search recursively given directory for *.java files.
346      *
347      * @param directory directory to search
348      * @return List of java files found
349      */
350     private static List<Path> getJavaFiles(final Path directory) {
351         final var result = new ArrayList<Path>();
352         try (var stream = Files.list(directory)) {
353             stream.forEach(file -> {
354                 if (Files.isDirectory(file)) {
355                     result.addAll(getJavaFiles(file));
356                 } else if (file.getFileName().toString().endsWith(".java")) {
357                     result.add(file);
358                 }
359             });
360         } catch (IOException e) {
361             throw new AssertionError(e);
362         }
363         return result;
364     }
365
366     static void deleteTestDir(final Path file) throws IOException {
367         if (Files.isDirectory(file)) {
368             try (var paths = Files.walk(file)) {
369                 paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
370             }
371         } else {
372             Files.delete(file);
373         }
374     }
375
376     static void assertRegularFile(final Path parent, final String name) {
377         assertTrue(Files.isRegularFile(parent.resolve(name)));
378     }
379 }