Skip to content

Instantly share code, notes, and snippets.

@cthornton
Created July 21, 2021 20:35
Show Gist options
  • Save cthornton/a06807fff78d54018086c00d44f5b068 to your computer and use it in GitHub Desktop.
Save cthornton/a06807fff78d54018086c00d44f5b068 to your computer and use it in GitHub Desktop.
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.platform.commons.util.ReflectionUtils;
class Pair<L,R> {
final L left;
final R right;
Pair(L left, R right) {
this.left = left;
this.right = right;
}
}
class JUnit5ReflectionUtils {
private JUnit5ReflectionUtils() { }
/**
* Tries parsing class names that could be nested. For example, given the properly named class:
*
* com.my.SomeClass$MyNested
*
* intellij reports it as:
*
* com.my.SomeClass.MyNested
*
* Useful for @Nested support
*/
static Class<?> parseClassNameWithNestedSupport(String className) {
return tryParseClassNameWithNestedSupport(className)
.orElseThrow(() -> new IllegalStateException("Could not find test class " + className));
}
static Optional<Class<?>> tryParseClassNameWithNestedSupport(String className) {
int guessCount = 0;
String guess = className;
do {
try {
return Optional.of(Class.forName(guess));
} catch (ClassNotFoundException e) {
// continue guessing
}
guessCount++;
if (guessCount >= 10) {
break;
}
guess = StringUtils.replaceLast(".", "$", guess);
} while (guess.contains("."));
return Optional.empty();
}
static Optional<Class<?>> tryParseClass(String className) {
try {
return Optional.of(Class.forName(className));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
}
static boolean isPackage(String maybePackage) {
return Package.getPackage(maybePackage) != null;
}
// Parses a Kotlin test filter, and attempts to get the class it belongs to, and any methods selected.
//
// Cases:
//
// A: Selecting a class: com.myorg.bazel.junit.kotlin.faketests.FakeTest
// B: a nested class: com.myorg.bazel.junit.kotlin.faketests.FakeTest.MyNested
// C: a method: com.myorg.bazel.junit.kotlin.faketests.FakeTest.testTrue
//
// For scenarios A & B, this simply re-uses tryParseClassNameWithNestedSupport() directly.
// For scenario C, we assume that methods can't have a . in them, so we will try assuming
// that "com.myorg.bazel.junit.kotlin.faketests.FakeTest" is the class name, and assume "testTrue"
// is the method name.
static Optional<Pair<Class<?>, List<String>>> parseKotlin(String filter) {
Optional<Pair<Class<?>, List<String>>> classPair = tryParseClassNameWithNestedSupport(filter)
.map(klass -> new Pair<>(klass, ImmutableList.of()));
if (classPair.isPresent()) {
return classPair;
}
String methodName = StringUtils.lastSplit(filter, "\\.");
String filterWithoutMethod = StringUtils.replaceLast("." + methodName, "", filter);
return tryParseClassNameWithNestedSupport(filterWithoutMethod).map(aKlass -> {
List<String> fullyQualifiedMethods = getFullyQualifiedMethods(aKlass, ImmutableList.of(methodName));
return new Pair<>(aKlass, fullyQualifiedMethods);
});
}
static List<String> getFullyQualifiedMethods(Class<?> klass, List<String> methodNames) {
ListMultimap<String, Method> methodsByName = Stream.of(klass.getDeclaredMethods())
.collect(ImmutableListMultimap.toImmutableListMultimap(Method::getName, Function.identity()));
// In the case of a test with an argument (i.e. parameterized tests), the test filter from
// intellij doesn't provide us with the argument name. So given something like:
//
// com.myorg.MyClass#myParameterizedMethod
//
// where it has a boolean argument, we need to adjust the actual method sent to JUnit as:
//
//
// Hence why we reference the list of declared methods on the Class object
return methodNames.stream()
.flatMap(methodName -> {
List<Method> methods = methodsByName.get(methodName);
Preconditions.checkState(
!methods.isEmpty(),
"Class '%s' does not have method '%s' defined",
klass.getName(), methodName);
if (methods.size() > 1) {
System.err.printf(
"More than one test method named '%s' exists on class '%s' -- %d methods will be run in no particular order\n",
methodName,
klass.getName(),
methods.size());
}
return methods.stream();
})
.map(method -> {
return ReflectionUtils.getFullyQualifiedMethodName(klass, method);
})
.collect(ImmutableList.toImmutableList());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment