Skip to content

Commit

Permalink
Introduced TestDescriptorResolver for JUnit5TestEngine
Browse files Browse the repository at this point in the history
  • Loading branch information
bechte committed Oct 30, 2015
1 parent 6d1117e commit 7bce8df
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 51 deletions.
Expand Up @@ -10,9 +10,12 @@


package org.junit.gen5.commons.util; package org.junit.gen5.commons.util;


import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Optional;


/** /**
* @author Stefan Bechtold * @author Stefan Bechtold
Expand Down Expand Up @@ -42,4 +45,18 @@ public static Object invokeMethod(Method method, Object testInstance)
return method.invoke(testInstance); return method.invoke(testInstance);
} }


} public static <A extends Annotation> Optional<A> getAnnotationFrom(AnnotatedElement element, Class<A> annotation) {
return Optional.ofNullable(element.getAnnotation(annotation));
}

public static Class<?> loadClass(String name) {
try {
// TODO Use correct classloader
// TODO Add support for primitive types and arrays.
return ClassLoader.getSystemClassLoader().loadClass(name);
}
catch (ClassNotFoundException e) {
throw new IllegalStateException("Failed to load class with name '" + name + "'.", e);
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2015 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine.junit5;

import static java.util.stream.Collectors.toList;
import static org.junit.gen5.commons.util.ReflectionUtils.loadClass;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.junit.gen5.api.Test;
import org.junit.gen5.engine.ClassNameSpecification;
import org.junit.gen5.engine.EngineDescriptor;
import org.junit.gen5.engine.TestDescriptor;

/**
* @author Stefan Bechtold
* @since 5.0
*/
public class ClassNameTestDescriptorResolver
implements TestDescriptorResolver<ClassNameSpecification, JavaClassTestDescriptor> {

@Override
public JavaClassTestDescriptor resolve(TestDescriptor parent, ClassNameSpecification element) {
Class<?> clazz = loadClass(element.getClassName());
return new JavaClassTestDescriptor(clazz, parent);
}

@Override
public List<TestDescriptor> resolveChildren(JavaClassTestDescriptor parent, ClassNameSpecification element) {
if (parent.getUniqueId().endsWith(element.getClassName())) {
// TODO fetch children resolvers from according to type
List<TestDescriptor> children = new LinkedList<>();

children.addAll(Arrays.stream(parent.getTestClass().getDeclaredMethods()).filter(
method -> method.isAnnotationPresent(Test.class)).map(
method -> new JavaMethodTestDescriptor(method, parent)).collect(toList()));

List<JavaClassTestDescriptor> groups = Arrays.stream(parent.getTestClass().getDeclaredClasses()).filter(
Class::isMemberClass).map(clazz -> new JavaClassTestDescriptor(clazz, parent)).collect(toList());
children.addAll(groups);

groups.forEach(group -> children.addAll(resolveChildren(group, element)));

return children;
}
else {
JavaClassTestDescriptor child = resolve(parent, element);
List<TestDescriptor> children = resolveChildren(child, element);
List<TestDescriptor> result = new LinkedList<>();
result.add(child);
result.addAll(children);
return result;
}
}
}
Expand Up @@ -10,23 +10,14 @@


package org.junit.gen5.engine.junit5; package org.junit.gen5.engine.junit5;


import static java.lang.String.format;
import static java.util.stream.Collectors.toList;

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;


import org.junit.gen5.api.Test;
import org.junit.gen5.commons.util.Preconditions; import org.junit.gen5.commons.util.Preconditions;
import org.junit.gen5.engine.ClassNameSpecification; import org.junit.gen5.engine.*;
import org.junit.gen5.engine.EngineExecutionContext;
import org.junit.gen5.engine.TestDescriptor;
import org.junit.gen5.engine.TestEngine;
import org.junit.gen5.engine.TestPlanSpecification;
import org.junit.gen5.engine.TestPlanSpecificationElement;
import org.junit.gen5.engine.UniqueIdSpecification;
import org.opentestalliance.TestAbortedException; import org.opentestalliance.TestAbortedException;
import org.opentestalliance.TestSkippedException; import org.opentestalliance.TestSkippedException;


Expand All @@ -42,43 +33,24 @@ public String getId() {


@Override @Override
public List<TestDescriptor> discoverTests(TestPlanSpecification specification, TestDescriptor root) { public List<TestDescriptor> discoverTests(TestPlanSpecification specification, TestDescriptor root) {
List<TestDescriptor> testDescriptors = new ArrayList<>(); // TODO lookup SpecificationResolverRegistry within the ApplicationExecutionContext
TestDescriptorResolverRegistry testDescriptorResolverRegistry = new TestDescriptorResolverRegistry();
testDescriptorResolverRegistry.addResolver(ClassNameSpecification.class, new ClassNameTestDescriptorResolver());
testDescriptorResolverRegistry.addResolver(UniqueIdSpecification.class, new UniqueIdTestDescriptorResolver());


Set<TestDescriptor> testDescriptors = new LinkedHashSet<>();
for (TestPlanSpecificationElement element : specification) { for (TestPlanSpecificationElement element : specification) {
if (element instanceof ClassNameSpecification) { TestDescriptorResolver testDescriptorResolver = testDescriptorResolverRegistry.forType(element.getClass());
ClassNameSpecification classNameSpecification = (ClassNameSpecification) element; TestDescriptor descriptor = testDescriptorResolver.resolve(root, element);
Class<?> testClass = discoverTestClass(classNameSpecification.getClassName()); testDescriptors.add(descriptor);
JavaClassTestDescriptor parent = new JavaClassTestDescriptor(testClass, root); testDescriptors.addAll(testDescriptorResolver.resolveChildren(descriptor, element));
testDescriptors.add(parent);
// @formatter:off
testDescriptors.addAll(Arrays.stream(testClass.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(Test.class))
.map(method -> new JavaMethodTestDescriptor( method, parent))
.collect(toList()));
// @formatter:on
}
else if (element instanceof UniqueIdSpecification) {
UniqueIdSpecification uniqueIdSpecification = (UniqueIdSpecification) element;
testDescriptors.add(JavaTestDescriptorFactory.from(uniqueIdSpecification.getUniqueId(), root));
}
}

return testDescriptors;
}

private Class<?> discoverTestClass(String className) {
// TODO Use correct ClassLoader
try {
return Class.forName(className);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException(format("Failed to load test class '%s'", className));
} }
return new ArrayList<>(testDescriptors);
} }


@Override @Override
public boolean supports(TestDescriptor testDescriptor) { public boolean supports(TestDescriptor testDescriptor) {
// Todo super class for Java test descriptors? // TODO super class for Java test descriptors?
return testDescriptor instanceof JavaMethodTestDescriptor || testDescriptor instanceof JavaClassTestDescriptor; return testDescriptor instanceof JavaMethodTestDescriptor || testDescriptor instanceof JavaClassTestDescriptor;
} }


Expand All @@ -97,7 +69,7 @@ public void execute(EngineExecutionContext context) {
// 1) retain the instance across test method invocations (if desired). // 1) retain the instance across test method invocations (if desired).
// 2) invoke class-level before & after methods _around_ the set of methods. // 2) invoke class-level before & after methods _around_ the set of methods.


// todo hierarchies of tests must be executed top down // TODO hierarchies of tests must be executed top down
for (TestDescriptor testDescriptor : context.getTestDescriptions()) { for (TestDescriptor testDescriptor : context.getTestDescriptions()) {


Preconditions.condition(supports(testDescriptor), Preconditions.condition(supports(testDescriptor),
Expand Down Expand Up @@ -137,5 +109,4 @@ else if (targetException instanceof TestAbortedException) {
} }
} }
} }

} }
@@ -0,0 +1,27 @@
/*
* Copyright 2015 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine.junit5;

import java.util.List;

import org.junit.gen5.engine.TestDescriptor;
import org.junit.gen5.engine.TestPlanSpecificationElement;

/**
* @author Stefan Bechtold
* @since 5.0
*/
interface TestDescriptorResolver<SPECIFICATION_ELEMENT extends TestPlanSpecificationElement, TEST_DESCRIPTOR extends TestDescriptor> {

TEST_DESCRIPTOR resolve(TestDescriptor parent, SPECIFICATION_ELEMENT element);

List<TestDescriptor> resolveChildren(TEST_DESCRIPTOR parent, SPECIFICATION_ELEMENT element);
}
@@ -0,0 +1,39 @@
/*
* Copyright 2015 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine.junit5;

import java.util.HashMap;
import java.util.Map;

import org.junit.gen5.engine.TestPlanSpecificationElement;

/**
* @author Stefan Bechtold
* @since 5.0
*/
class TestDescriptorResolverRegistry {

private final Map<Class<? extends TestPlanSpecificationElement>, TestDescriptorResolver> resolvers = new HashMap<>();

public TestDescriptorResolver forType(Class<? extends TestPlanSpecificationElement> type) {
if (resolvers.containsKey(type)) {
return resolvers.get(type);
}
else {
throw new UnsupportedOperationException(
"There is no specification resolver registered for type: " + type.getName());
}
}

public void addResolver(Class<? extends TestPlanSpecificationElement> element, TestDescriptorResolver resolver) {
resolvers.put(element, resolver);
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2015 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.gen5.engine.junit5;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.gen5.commons.util.Preconditions;
import org.junit.gen5.commons.util.ReflectionUtils;
import org.junit.gen5.engine.EngineDescriptor;
import org.junit.gen5.engine.TestDescriptor;
import org.junit.gen5.engine.UniqueIdSpecification;

/**
* <p>The pattern of the unique ID takes the form of
* <code>{fully qualified class name}#{method name}({comma separated list
* of method parameter types})</code>, where each method parameter type is
* a fully qualified class name or a primitive type. For example,
* {@code org.example.MyTests#test()} references the {@code test()} method
* in the {@code org.example.MyTests} class that does not accept parameters.
* Similarly, {@code org.example.MyTests#test(java.lang.String, java.math.BigDecimal)}
* references the {@code test()} method in the {@code org.example.MyTests}
* class that requires a {@code String} and {@code BigDecimal} as parameters.
*
* @author Sam Brannen
* @author Stefan Bechtold
* @since 5.0
*/
public class UniqueIdTestDescriptorResolver implements TestDescriptorResolver<UniqueIdSpecification, TestDescriptor> {

// The following pattern only supports descriptors for test methods.
// TODO Support descriptors for test classes.
// TODO Decide if we want to support descriptors for packages.
private static final Pattern UID_PATTERN = Pattern.compile("^(?:([^:]+):)?(?:([^#]+)#)?(?:([^(]+)\\(([^)]*)\\))?$");

@Override
public TestDescriptor resolve(TestDescriptor parent, UniqueIdSpecification element) {
String uid = element.getUniqueId();
Preconditions.notEmpty(uid, "UniqueID must not be empty");
Preconditions.condition(uid.startsWith(parent.getUniqueId()),
String.format("UniqueID '%s' must start with UniqueID of parent '%s'!", uid, parent.getUniqueId()));

Matcher matcher = UID_PATTERN.matcher(uid);
Preconditions.condition(matcher.matches(), () -> String.format("Given UniqueID '%s' was not recognised!", uid));

String className = matcher.group(2);
String methodName = matcher.group(3);
String methodParameters = matcher.group(4);

Class<?> clazz = ReflectionUtils.loadClass(className);
if (parent instanceof EngineDescriptor) {
return new JavaClassTestDescriptor(clazz, parent);
}
else if (parent instanceof JavaClassTestDescriptor) {
JavaClassTestDescriptor group = (JavaClassTestDescriptor) parent;

try {
List<Class<?>> paramTypeList = new ArrayList<>();
for (String type : methodParameters.split(",")) {
type = type.trim();
if (!type.isEmpty()) {
paramTypeList.add(ReflectionUtils.loadClass(type));
}
}

Class<?>[] parameterTypes = paramTypeList.toArray(new Class<?>[paramTypeList.size()]);
JavaClassTestDescriptor testClassGroup = group;
Method method = testClassGroup.getTestClass().getDeclaredMethod(methodName, parameterTypes);
return new JavaMethodTestDescriptor(method, testClassGroup);
}
catch (NoSuchMethodException e) {
throw new IllegalStateException("Failed to get method with name '" + methodName + "'.", e);
}
}
else {
throw new IllegalStateException(
String.format("Given UniqueID '%s' could not be completely resolved!", uid));
}
}

@Override
public List<TestDescriptor> resolveChildren(TestDescriptor parent, UniqueIdSpecification element) {
if (element.getUniqueId().equals(parent.getUniqueId())) {
return Collections.emptyList();
}
else {
TestDescriptor child = resolve(parent, element);
List<TestDescriptor> children = resolveChildren(child, element);

List<TestDescriptor> result = new LinkedList<>();
result.add(child);
result.addAll(children);
return result;
}
}
}

0 comments on commit 7bce8df

Please sign in to comment.