All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.engine.discovery.HierarchicalJavaResolver Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.discovery;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;

import org.junit.platform.engine.*;

import net.jqwik.engine.*;
import net.jqwik.engine.descriptor.*;
import net.jqwik.engine.discovery.predicates.*;
import net.jqwik.engine.support.*;

import static java.lang.String.*;
import static java.util.stream.Collectors.*;
import static org.junit.platform.commons.support.HierarchyTraversalMode.*;
import static org.junit.platform.commons.support.ReflectionSupport.*;
import static org.junit.platform.engine.SelectorResolutionResult.*;

class HierarchicalJavaResolver {

	private static final Logger LOG = Logger.getLogger(HierarchicalJavaResolver.class.getName());

	private final static IsContainerAGroup isContainerAGroup = new IsContainerAGroup();
	private final static IsDiscoverableTestMethod isDiscoverableTestMethod = new IsDiscoverableTestMethod();

	private final TestDescriptor engineDescriptor;
	private final Set resolvers;

	HierarchicalJavaResolver(TestDescriptor engineDescriptor, Set resolvers) {
		this.engineDescriptor = engineDescriptor;
		this.resolvers = resolvers;
	}

	SelectorResolutionResult resolveClass(Class testClass) {
		return resolveSafely(() -> {
			Set resolvedDescriptors = resolveContainerWithParents(testClass);
			resolvedDescriptors.forEach(this::resolveChildren);
			warnWhenJUnitAnnotationsArePresent(resolvedDescriptors);

			if (resolvedDescriptors.isEmpty()) {
				return SelectorResolutionResult.unresolved();
			}

			return SelectorResolutionResult.resolved();
		});
	}

	private void warnWhenJUnitAnnotationsArePresent(Set resolvedDescriptors) {
		resolvedDescriptors
			.stream()
			.filter(d -> !d.getChildren().isEmpty())
			.filter(d -> d instanceof ContainerClassDescriptor)
			.map(d -> (ContainerClassDescriptor) d)
			.forEach(d -> DiscoverySupport.warnWhenJunitAnnotationsArePresent(d.getContainerClass()));
	}

	SelectorResolutionResult resolveMethod(Class testClass, Method testMethod) {
		return resolveSafely(() -> {
			Set potentialParents = resolveContainerWithParents(testClass);
			Set resolvedDescriptors = resolveForAllParents(testMethod, potentialParents);

			if (resolvedDescriptors.isEmpty()) {
				return SelectorResolutionResult.unresolved();
			}
			return SelectorResolutionResult.resolved();
		});
	}

	SelectorResolutionResult resolveUniqueId(UniqueId uniqueId) {
		return resolveSafely(() -> {
			// Silently ignore request to resolve foreign ID
			if (uniqueId.getEngineId().isPresent()) {
				if (!uniqueId.getEngineId().get().equals(JqwikTestEngine.ENGINE_ID)) {
					return SelectorResolutionResult.unresolved();
				}
			}

			List segments = new ArrayList<>(uniqueId.getSegments());
			segments.remove(0); // Ignore engine unique ID

			if (!resolveUniqueId(this.engineDescriptor, segments)) {
				// This is more severe than unresolvable methods or classes because only suitable IDs should get here anyway
				LOG.warning(() -> format("Received request to resolve unique id '%s' as test or test container but could not fulfill it", uniqueId));
				return SelectorResolutionResult.unresolved();
			}
			return SelectorResolutionResult.resolved();
		});
	}

	private SelectorResolutionResult resolveSafely(Callable callable) {
		try {
			return callable.call();
		} catch (Throwable t) {
			JqwikExceptionSupport.rethrowIfBlacklisted(t);
			return failed(t);
		}
	}

	private Set resolveContainerWithParents(Class testClass) {
		Set potentialParents =
			isContainerAGroup.test(testClass)
				? resolveContainerWithParents(testClass.getDeclaringClass())
				: Collections.singleton(engineDescriptor);
		return resolveForAllParents(testClass, potentialParents);
	}

	/**
	 * Return true if all segments of unique ID could be resolved
	 */
	private boolean resolveUniqueId(TestDescriptor parent, List remainingSegments) {
		if (remainingSegments.isEmpty()) {
			resolveChildren(parent);
			return true;
		}

		UniqueId.Segment head = remainingSegments.remove(0);
		for (ElementResolver resolver : resolvers) {
			Optional resolvedDescriptor = resolver.resolveUniqueId(head, parent);
			if (!resolvedDescriptor.isPresent())
				continue;

			Optional foundTestDescriptor = findTestDescriptorByUniqueId(resolvedDescriptor.get().getUniqueId());
			TestDescriptor descriptor = foundTestDescriptor.orElseGet(() -> {
				TestDescriptor newDescriptor = resolvedDescriptor.get();
				parent.addChild(newDescriptor);
				return newDescriptor;
			});
			return resolveUniqueId(descriptor, remainingSegments);
		}
		return false;
	}

	private Set resolveContainerWithChildren(Class containerClass, Set potentialParents) {
		Set resolvedDescriptors = resolveForAllParents(containerClass, potentialParents);
		resolvedDescriptors.forEach(this::resolveChildren);
		return resolvedDescriptors;
	}

	private Set resolveForAllParents(AnnotatedElement element, Set potentialParents) {
		Set resolvedDescriptors = new HashSet<>();
		potentialParents.forEach(parent -> resolvedDescriptors.addAll(resolve(element, parent)));
		return resolvedDescriptors;
	}

	private void resolveChildren(TestDescriptor descriptor) {
		if (descriptor instanceof ContainerClassDescriptor) {
			ContainerClassDescriptor containerClassDescriptor = (ContainerClassDescriptor) descriptor;
			Class containerClass = containerClassDescriptor.getContainerClass();
			resolveContainedMethods(descriptor, containerClass);

			resolveContainedGroups(containerClassDescriptor, containerClass);
		}
	}

	private void resolveContainedGroups(ContainerClassDescriptor containerClassDescriptor, Class containerClass) {
		Predicate> isGroup = new IsContainerAGroup();
		List> containedContainersCandidates = findNestedClasses(containerClass, isGroup);
		containedContainersCandidates
			.forEach(nestedClass -> resolveContainerWithChildren(nestedClass, Collections.singleton(containerClassDescriptor)));
	}

	private void resolveContainedMethods(TestDescriptor containerDescriptor, Class testClass) {
		List testMethodCandidates = findMethods(testClass, isDiscoverableTestMethod, TOP_DOWN);
		testMethodCandidates.forEach(method -> resolve(method, containerDescriptor));
	}

	private Set resolve(AnnotatedElement element, TestDescriptor parent) {
		return this.resolvers
				   .stream()
				   .map(resolver -> tryToResolveWithResolver(element, parent, resolver))
				   .filter(testDescriptors -> !testDescriptors.isEmpty())
				   .flatMap(Collection::stream)
				   .collect(toSet());
	}

	private Set tryToResolveWithResolver(AnnotatedElement element, TestDescriptor parent, ElementResolver resolver) {

		Set resolvedDescriptors = resolver.resolveElement(element, parent);
		Set result = new LinkedHashSet<>();

		resolvedDescriptors.forEach(testDescriptor -> {
			Optional existingTestDescriptor = findTestDescriptorByUniqueId(testDescriptor.getUniqueId());
			if (existingTestDescriptor.isPresent()) {
				result.add(existingTestDescriptor.get());
			} else {
				parent.addChild(testDescriptor);
				result.add(testDescriptor);
			}
		});

		return result;
	}

	@SuppressWarnings("unchecked")
	private Optional findTestDescriptorByUniqueId(UniqueId uniqueId) {
		return (Optional) this.engineDescriptor.findByUniqueId(uniqueId);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy