package com.oracle.truffle.tck.tests;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
public class TruffleTCKFeature implements Feature {
@Override
public List<Class<? extends Feature>> getRequiredFeatures() {
try {
return Collections.singletonList(Class.forName("com.oracle.svm.reflect.hosted.ReflectionFeature").asSubclass(Feature.class));
} catch (ClassNotFoundException cnf) {
throw new RuntimeException(cnf);
}
}
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
try {
registerParameterizedRunner();
registerJavaHostLanguageProvider();
for (String testClass : findTCKTests()) {
registerTCKTest(access, testClass);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static void registerParameterizedRunner() throws NoSuchMethodException {
RuntimeReflection.register(Parameterized.class.getDeclaredConstructor(Class.class));
}
private static void registerJavaHostLanguageProvider() throws NoSuchMethodException {
RuntimeReflection.register(Supplier.class.getDeclaredMethod("get"));
RuntimeReflection.register(Function.class.getDeclaredMethod("apply", Object.class));
RuntimeReflection.register(Object.class.getDeclaredConstructor());
}
private static void registerTCKTest(BeforeAnalysisAccess access, String testClassFqn) {
Class<?> testClass = access.findClassByName(testClassFqn);
access.registerAsInHeap(testClass);
RuntimeReflection.register(testClass);
for (Constructor<?> constructor : testClass.getDeclaredConstructors()) {
RuntimeReflection.register(constructor);
}
for (Method method : testClass.getDeclaredMethods()) {
if (isJUnitEntryPoint(method)) {
RuntimeReflection.register(method);
}
}
}
private static boolean isJUnitEntryPoint(Method method) {
return method.isAnnotationPresent(After.class) || method.isAnnotationPresent(AfterClass.class) || method.isAnnotationPresent(Before.class) || method.isAnnotationPresent(BeforeClass.class) ||
method.isAnnotationPresent(Parameters.class) || method.isAnnotationPresent(Test.class);
}
private static Collection<String> findTCKTests() {
Set<File> todo = new HashSet<>();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (!(contextClassLoader instanceof URLClassLoader)) {
throw new IllegalStateException("Context ClassLoader must be URLClassLoader");
}
for (URL url : ((URLClassLoader) contextClassLoader).getURLs()) {
try {
final File file = new File(url.toURI());
todo.add(file);
} catch (URISyntaxException | IllegalArgumentException e) {
throw new IllegalStateException(String.format("Unable to handle image class path element %s.", url));
}
}
Collection<String> result = new ArrayList<>();
for (File cpEntry : todo) {
if (cpEntry.isDirectory()) {
result.addAll(findTCKTestsInDirectory(cpEntry));
} else {
result.addAll(findTCKTestsInArchive(cpEntry));
}
}
return result;
}
private static Collection<String> findTCKTestsInDirectory(File root) {
String pkgFqn = TruffleTCKFeature.class.getPackage().getName();
String folderResourceName = pkgFqn.replace('.', File.separatorChar) + File.separatorChar;
File folder = new File(root, folderResourceName);
String[] names = folder.list();
if (names == null) {
return Collections.emptySet();
}
Collection<String> result = new ArrayList<>();
for (String name : names) {
if (name.endsWith("Test.class")) {
String className = pkgFqn + '.' + name.substring(0, name.length() - 6);
result.add(className);
}
}
return result;
}
private static Collection<String> findTCKTestsInArchive(File archive) {
String folderResourceName = TruffleTCKFeature.class.getPackage().getName().replace('.', '/') + '/';
Collection<String> result = new ArrayList<>();
try (JarFile jf = new JarFile(archive)) {
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith(folderResourceName) && entryName.endsWith("Test.class")) {
String className = entryName.substring(0, entryName.length() - 6).replace('/', '.');
result.add(className);
}
}
} catch (IOException ioe) {
}
return result;
}
}