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

org.junit.jupiter.engine.discovery.AbstractOrderingVisitor Maven / Gradle / Ivy

There is a newer version: 5.11.3
Show newest version
/*
 * Copyright 2015-2022 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 v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.engine.discovery;

import static java.util.stream.Collectors.toCollection;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.junit.platform.engine.TestDescriptor;

/**
 * Abstract base class for {@linkplain TestDescriptor.Visitor visitors} that
 * order children nodes.
 *
 * @param  the parent container type to search in for matching children
 * @param  the type of children (containers or tests) to order
 * @param  the wrapper type for the children to order
 * @since 5.8
 */
abstract class AbstractOrderingVisitor>
		implements TestDescriptor.Visitor {

	private static final Logger logger = LoggerFactory.getLogger(AbstractOrderingVisitor.class);

	@SuppressWarnings("unchecked")
	protected void doWithMatchingDescriptor(Class parentTestDescriptorType, TestDescriptor testDescriptor,
			Consumer action, Function errorMessageBuilder) {

		if (parentTestDescriptorType.isInstance(testDescriptor)) {
			PARENT parentTestDescriptor = (PARENT) testDescriptor;
			try {
				action.accept(parentTestDescriptor);
			}
			catch (Throwable t) {
				UnrecoverableExceptions.rethrowIfUnrecoverable(t);
				logger.error(t, () -> errorMessageBuilder.apply(parentTestDescriptor));
			}
		}
	}

	protected void orderChildrenTestDescriptors(TestDescriptor parentTestDescriptor, Class matchingChildrenType,
			Function descriptorWrapperFactory, DescriptorWrapperOrderer descriptorWrapperOrderer) {

		Set children = parentTestDescriptor.getChildren();

		List matchingDescriptorWrappers = children.stream()//
				.filter(matchingChildrenType::isInstance)//
				.map(matchingChildrenType::cast)//
				.map(descriptorWrapperFactory)//
				.collect(toCollection(ArrayList::new));

		// If there are no children to order, abort early.
		if (matchingDescriptorWrappers.isEmpty()) {
			return;
		}

		if (descriptorWrapperOrderer.canOrderWrappers()) {
			List nonMatchingTestDescriptors = children.stream()//
					.filter(childTestDescriptor -> !matchingChildrenType.isInstance(childTestDescriptor))//
					.collect(Collectors.toList());

			// Make a local copy for later validation
			Set originalWrappers = new LinkedHashSet<>(matchingDescriptorWrappers);

			descriptorWrapperOrderer.orderWrappers(matchingDescriptorWrappers);

			int difference = matchingDescriptorWrappers.size() - originalWrappers.size();
			if (difference > 0) {
				descriptorWrapperOrderer.logDescriptorsAddedWarning(difference);
			}
			else if (difference < 0) {
				descriptorWrapperOrderer.logDescriptorsRemovedWarning(difference);
			}

			Set orderedTestDescriptors = matchingDescriptorWrappers.stream()//
					.filter(originalWrappers::contains)//
					.map(AbstractAnnotatedDescriptorWrapper::getTestDescriptor)//
					.collect(toCollection(LinkedHashSet::new));

			// There is currently no way to removeAll or addAll children at once, so we
			// first remove them all and then add them all back.
			Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())//
					.forEach(parentTestDescriptor::removeChild);

			// If we are ordering children of type ClassBasedTestDescriptor, that means we
			// are ordering top-level classes or @Nested test classes. Thus, the
			// nonMatchingTestDescriptors list is either empty (for top-level classes) or
			// contains only local test methods (for @Nested classes) which must be executed
			// before tests in @Nested test classes. So we add the test methods before adding
			// the @Nested test classes.
			if (matchingChildrenType == ClassBasedTestDescriptor.class) {
				Stream.concat(nonMatchingTestDescriptors.stream(), orderedTestDescriptors.stream())//
						.forEach(parentTestDescriptor::addChild);
			}
			// Otherwise, we add the ordered descriptors before the non-matching descriptors,
			// which is the case when we are ordering test methods. In other words, local
			// test methods always get added before @Nested test classes.
			else {
				Stream.concat(orderedTestDescriptors.stream(), nonMatchingTestDescriptors.stream())//
						.forEach(parentTestDescriptor::addChild);
			}
		}

		// Recurse through the children in order to support ordering for @Nested test classes.
		matchingDescriptorWrappers.forEach(descriptorWrapper -> {
			TestDescriptor newParentTestDescriptor = descriptorWrapper.getTestDescriptor();
			DescriptorWrapperOrderer newDescriptorWrapperOrderer = getDescriptorWrapperOrderer(descriptorWrapperOrderer,
				descriptorWrapper);

			orderChildrenTestDescriptors(newParentTestDescriptor, matchingChildrenType, descriptorWrapperFactory,
				newDescriptorWrapperOrderer);
		});
	}

	/**
	 * Get the {@link DescriptorWrapperOrderer} for the supplied {@link AbstractAnnotatedDescriptorWrapper}.
	 *
	 * 

The default implementation returns the supplied {@code DescriptorWrapperOrderer}. * * @return a new {@code DescriptorWrapperOrderer} or the one supplied as an argument */ protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( DescriptorWrapperOrderer inheritedDescriptorWrapperOrderer, AbstractAnnotatedDescriptorWrapper descriptorWrapper) { return inheritedDescriptorWrapperOrderer; } protected class DescriptorWrapperOrderer { private final Consumer> orderingAction; private final MessageGenerator descriptorsAddedMessageGenerator; private final MessageGenerator descriptorsRemovedMessageGenerator; DescriptorWrapperOrderer(Consumer> orderingAction, MessageGenerator descriptorsAddedMessageGenerator, MessageGenerator descriptorsRemovedMessageGenerator) { this.orderingAction = orderingAction; this.descriptorsAddedMessageGenerator = descriptorsAddedMessageGenerator; this.descriptorsRemovedMessageGenerator = descriptorsRemovedMessageGenerator; } private boolean canOrderWrappers() { return this.orderingAction != null; } private void orderWrappers(List wrappers) { this.orderingAction.accept(wrappers); } private void logDescriptorsAddedWarning(int number) { logger.warn(() -> this.descriptorsAddedMessageGenerator.generateMessage(number)); } private void logDescriptorsRemovedWarning(int number) { logger.warn(() -> this.descriptorsRemovedMessageGenerator.generateMessage(Math.abs(number))); } } @FunctionalInterface protected interface MessageGenerator { String generateMessage(int number); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy