27 import java.io.FileNotFoundException; |
27 import java.io.FileNotFoundException; |
28 import java.io.FileOutputStream; |
28 import java.io.FileOutputStream; |
29 import java.io.FileReader; |
29 import java.io.FileReader; |
30 import java.io.IOException; |
30 import java.io.IOException; |
31 import java.io.PrintStream; |
31 import java.io.PrintStream; |
32 import java.lang.annotation.Annotation; |
|
33 import java.lang.reflect.Method; |
|
34 import java.util.ArrayList; |
32 import java.util.ArrayList; |
35 import java.util.Collections; |
33 import java.util.Collections; |
36 import java.util.HashSet; |
|
37 import java.util.List; |
34 import java.util.List; |
38 import java.util.Map; |
35 import java.util.Map; |
39 import java.util.Optional; |
|
40 import java.util.ServiceLoader; |
36 import java.util.ServiceLoader; |
41 import java.util.Set; |
37 import java.util.Set; |
42 import java.util.regex.Matcher; |
38 import java.util.TreeSet; |
43 import java.util.regex.Pattern; |
|
44 |
39 |
45 import org.junit.internal.JUnitSystem; |
40 import org.junit.internal.JUnitSystem; |
46 import org.junit.internal.RealSystem; |
41 import org.junit.internal.RealSystem; |
47 import org.junit.runner.Description; |
42 import org.junit.runner.Description; |
48 import org.junit.runner.JUnitCore; |
43 import org.junit.runner.JUnitCore; |
56 import org.junit.runners.model.RunnerScheduler; |
51 import org.junit.runners.model.RunnerScheduler; |
57 |
52 |
58 import junit.runner.Version; |
53 import junit.runner.Version; |
59 |
54 |
60 public class MxJUnitWrapper { |
55 public class MxJUnitWrapper { |
|
56 |
|
57 // Unit tests that start a JVM subprocess can use these system properties to |
|
58 // add --add-exports and --add-opens as necessary to the JVM command line. |
|
59 // |
|
60 // Known usages: |
|
61 // org.graalvm.compiler.test.SubprocessUtil.getPackageOpeningOptions() |
|
62 public static final String OPENED_PACKAGES_PROPERTY_NAME = "com.oracle.mxtool.junit.opens"; |
|
63 public static final String EXPORTED_PACKAGES_PROPERTY_NAME = "com.oracle.mxtool.junit.exports"; |
61 |
64 |
62 public static class MxJUnitConfig { |
65 public static class MxJUnitConfig { |
63 |
66 |
64 public boolean verbose = false; |
67 public boolean verbose = false; |
65 public boolean veryVerbose = false; |
68 public boolean veryVerbose = false; |
134 MxJUnitRequest.Builder builder = new MxJUnitRequest.Builder(); |
137 MxJUnitRequest.Builder builder = new MxJUnitRequest.Builder(); |
135 MxJUnitConfig config = new MxJUnitConfig(); |
138 MxJUnitConfig config = new MxJUnitConfig(); |
136 |
139 |
137 String[] expandedArgs = expandArgs(args); |
140 String[] expandedArgs = expandArgs(args); |
138 int i = 0; |
141 int i = 0; |
|
142 List<String> testSpecs = new ArrayList<>(); |
|
143 List<String> openPackagesSpecs = new ArrayList<>(); |
139 while (i < expandedArgs.length) { |
144 while (i < expandedArgs.length) { |
140 String each = expandedArgs[i]; |
145 String each = expandedArgs[i]; |
141 if (each.charAt(0) == '-') { |
146 if (each.charAt(0) == '-') { |
142 // command line arguments |
147 // command line arguments |
143 if (each.contentEquals("-JUnitVerbose")) { |
148 if (each.contentEquals("-JUnitVerbose")) { |
144 config.verbose = true; |
149 config.verbose = true; |
|
150 config.enableTiming = true; |
|
151 } else if (each.contentEquals("-JUnitOpenPackages")) { |
|
152 if (i + 1 >= expandedArgs.length) { |
|
153 system.out().println("Must include argument for -JUnitAddExports"); |
|
154 System.exit(1); |
|
155 } |
|
156 openPackagesSpecs.add(expandedArgs[++i]); |
145 } else if (each.contentEquals("-JUnitVeryVerbose")) { |
157 } else if (each.contentEquals("-JUnitVeryVerbose")) { |
|
158 config.verbose = true; |
146 config.veryVerbose = true; |
159 config.veryVerbose = true; |
|
160 config.enableTiming = true; |
147 } else if (each.contentEquals("-JUnitFailFast")) { |
161 } else if (each.contentEquals("-JUnitFailFast")) { |
148 config.failFast = true; |
162 config.failFast = true; |
149 } else if (each.contentEquals("-JUnitEnableTiming")) { |
163 } else if (each.contentEquals("-JUnitEnableTiming")) { |
150 config.enableTiming = true; |
164 config.enableTiming = true; |
151 } else if (each.contentEquals("-JUnitColor")) { |
165 } else if (each.contentEquals("-JUnitColor")) { |
170 } else { |
184 } else { |
171 system.out().println("Unknown command line argument: " + each); |
185 system.out().println("Unknown command line argument: " + each); |
172 } |
186 } |
173 |
187 |
174 } else { |
188 } else { |
175 |
189 testSpecs.add(each); |
176 try { |
|
177 builder.addTestSpec(each); |
|
178 } catch (MxJUnitRequest.BuilderException ex) { |
|
179 system.out().println(ex.getMessage()); |
|
180 System.exit(1); |
|
181 } |
|
182 } |
190 } |
183 i++; |
191 i++; |
184 } |
192 } |
185 |
193 |
|
194 ModuleSupport moduleSupport = new ModuleSupport(system.out()); |
|
195 Set<String> opened = new TreeSet<>(); |
|
196 Set<String> exported = new TreeSet<>(); |
|
197 for (String spec : openPackagesSpecs) { |
|
198 moduleSupport.openPackages(spec, "-JUnitOpenPackages", opened, exported); |
|
199 } |
|
200 |
|
201 for (String spec : testSpecs) { |
|
202 try { |
|
203 builder.addTestSpec(spec); |
|
204 } catch (MxJUnitRequest.BuilderException ex) { |
|
205 system.out().println(ex.getMessage()); |
|
206 System.exit(1); |
|
207 } |
|
208 } |
|
209 |
186 MxJUnitRequest request = builder.build(); |
210 MxJUnitRequest request = builder.build(); |
187 |
211 moduleSupport.processAddExportsAnnotations(request.classes, opened, exported); |
188 if (System.getProperty("java.specification.version").compareTo("1.9") >= 0) { |
212 |
189 addExports(request.classes, system.out()); |
213 if (!opened.isEmpty()) { |
|
214 System.setProperty(OPENED_PACKAGES_PROPERTY_NAME, String.join(System.lineSeparator(), opened)); |
|
215 } |
|
216 if (!exported.isEmpty()) { |
|
217 System.setProperty(EXPORTED_PACKAGES_PROPERTY_NAME, String.join(System.lineSeparator(), exported)); |
190 } |
218 } |
191 |
219 |
192 for (RunListener p : ServiceLoader.load(RunListener.class)) { |
220 for (RunListener p : ServiceLoader.load(RunListener.class)) { |
193 junitCore.addListener(p); |
221 junitCore.addListener(p); |
194 } |
222 } |
338 Object[] current = timings.getCurrentTestDuration(); |
364 Object[] current = timings.getCurrentTestDuration(); |
339 if (current != null) { |
365 if (current != null) { |
340 System.out.printf("Test %s not finished after %d ms%n", current[0], current[1]); |
366 System.out.printf("Test %s not finished after %d ms%n", current[0], current[1]); |
341 } |
367 } |
342 |
368 |
343 } |
|
344 } |
|
345 |
|
346 /** |
|
347 * Adds the super types of {@code cls} to {@code supertypes}. |
|
348 */ |
|
349 private static void gatherSupertypes(Class<?> cls, Set<Class<?>> supertypes) { |
|
350 if (!supertypes.contains(cls)) { |
|
351 supertypes.add(cls); |
|
352 Class<?> superclass = cls.getSuperclass(); |
|
353 if (superclass != null) { |
|
354 gatherSupertypes(superclass, supertypes); |
|
355 } |
|
356 for (Class<?> iface : cls.getInterfaces()) { |
|
357 gatherSupertypes(iface, supertypes); |
|
358 } |
|
359 } |
|
360 } |
|
361 |
|
362 /** |
|
363 * Updates modules specified in {@code AddExport} annotations on {@code classes} to export |
|
364 * concealed packages to the annotation classes' declaring modules. |
|
365 */ |
|
366 private static void addExports(Set<Class<?>> classes, PrintStream out) { |
|
367 Set<Class<?>> types = new HashSet<>(); |
|
368 for (Class<?> cls : classes) { |
|
369 gatherSupertypes(cls, types); |
|
370 } |
|
371 for (Class<?> cls : types) { |
|
372 Annotation[] annos = cls.getAnnotations(); |
|
373 for (Annotation a : annos) { |
|
374 Class<? extends Annotation> annotationType = a.annotationType(); |
|
375 if (annotationType.getSimpleName().equals("AddExports")) { |
|
376 Optional<String[]> value = getElement("value", String[].class, a); |
|
377 if (value.isPresent()) { |
|
378 for (String export : value.get()) { |
|
379 Matcher m = MODULE_PACKAGE_RE.matcher(export); |
|
380 if (m.matches()) { |
|
381 String moduleName = m.group(1); |
|
382 String packageName = m.group(2); |
|
383 JLModule module = JLModule.find(moduleName); |
|
384 if (module == null) { |
|
385 out.printf("%s: Cannot find module named %s specified in \"AddExports\" annotation: %s%n", cls.getName(), moduleName, a); |
|
386 } else { |
|
387 if (packageName.equals("*")) { |
|
388 module.exportAllPackagesTo(JLModule.fromClass(cls)); |
|
389 } else { |
|
390 module.addExports(packageName, JLModule.fromClass(cls)); |
|
391 module.addOpens(packageName, JLModule.fromClass(cls)); |
|
392 } |
|
393 } |
|
394 } else { |
|
395 out.printf("%s: Ignoring \"AddExports\" annotation with value not matching <module>/<package> pattern: %s%n", cls.getName(), a); |
|
396 } |
|
397 } |
|
398 } else { |
|
399 out.printf("%s: Ignoring \"AddExports\" annotation without `String value` element: %s%n", cls.getName(), a); |
|
400 } |
|
401 } |
|
402 } |
|
403 } |
|
404 } |
|
405 |
|
406 /** |
|
407 * Gets the value of the element named {@code name} of type {@code type} from {@code annotation} |
|
408 * if present. |
|
409 * |
|
410 * @return the requested element value wrapped in an {@link Optional} or |
|
411 * {@link Optional#empty()} if {@code annotation} has no element named {@code name} |
|
412 * @throws AssertionError if {@code annotation} has an element of the given name but whose type |
|
413 * is not {@code type} or if there's some problem reading the value via reflection |
|
414 */ |
|
415 private static <T> Optional<T> getElement(String name, Class<T> type, Annotation annotation) { |
|
416 Class<? extends Annotation> annotationType = annotation.annotationType(); |
|
417 Method valueAccessor; |
|
418 try { |
|
419 valueAccessor = annotationType.getMethod(name); |
|
420 if (!valueAccessor.getReturnType().equals(type)) { |
|
421 throw new AssertionError(String.format("Element %s of %s is of type %s, not %s ", name, annotationType.getName(), valueAccessor.getReturnType().getName(), type.getName())); |
|
422 } |
|
423 } catch (NoSuchMethodException e) { |
|
424 return Optional.empty(); |
|
425 } |
|
426 try { |
|
427 return Optional.of(type.cast(valueAccessor.invoke(annotation))); |
|
428 } catch (Exception e) { |
|
429 throw new AssertionError(String.format("Could not read %s element from %s", name, annotation), e); |
|
430 } |
369 } |
431 } |
370 } |
432 |
371 |
433 /** |
372 /** |
434 * Expand any arguments starting with @ and return the resulting argument array. |
373 * Expand any arguments starting with @ and return the resulting argument array. |