package org.junit.jupiter.engine.descriptor;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Iterator;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apiguardian.api.API;
import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.execution.ExecutableInvoker;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.util.CollectionUtils;
import org.junit.platform.commons.util.PreconditionViolationException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
import org.junit.platform.engine.support.descriptor.UriSource;
@API(status = INTERNAL, since = "5.0")
public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implements Filterable {
public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container";
public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test";
private static final ExecutableInvoker executableInvoker = new ExecutableInvoker();
private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();
public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
}
@Override
public DynamicDescendantFilter getDynamicDescendantFilter() {
return dynamicDescendantFilter;
}
@Override
public Type getType() {
return Type.CONTAINER;
}
@Override
public boolean mayRegisterTests() {
return true;
}
@Override
protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
ExtensionContext extensionContext = context.getExtensionContext();
context.getThrowableCollector().execute(() -> {
Object instance = extensionContext.getRequiredTestInstance();
Object testFactoryMethodResult = executableInvoker.invoke(getTestMethod(), instance, extensionContext,
context.getExtensionRegistry());
TestSource defaultTestSource = getSource().orElseThrow(
() -> new JUnitException("Illegal state: TestSource must be present"));
try (Stream<DynamicNode> dynamicNodeStream = toDynamicNodeStream(testFactoryMethodResult)) {
int index = 1;
Iterator<DynamicNode> iterator = dynamicNodeStream.iterator();
while (iterator.hasNext()) {
DynamicNode dynamicNode = iterator.next();
Optional<JupiterTestDescriptor> descriptor = createDynamicDescriptor(this, dynamicNode, index++,
defaultTestSource, getDynamicDescendantFilter(), configuration);
descriptor.ifPresent(dynamicTestExecutor::execute);
}
}
catch (ClassCastException ex) {
throw invalidReturnTypeException(ex);
}
dynamicTestExecutor.awaitFinished();
});
}
@SuppressWarnings("unchecked")
private Stream<DynamicNode> toDynamicNodeStream(Object testFactoryMethodResult) {
if (testFactoryMethodResult instanceof DynamicNode) {
return Stream.of((DynamicNode) testFactoryMethodResult);
}
try {
return (Stream<DynamicNode>) CollectionUtils.toStream(testFactoryMethodResult);
}
catch (PreconditionViolationException ex) {
throw invalidReturnTypeException(ex);
}
}
private JUnitException invalidReturnTypeException(Throwable cause) {
String message = String.format(
"@TestFactory method [%s] must return a single %2$s or a Stream, Collection, Iterable, Iterator, or array of %2$s.",
getTestMethod().toGenericString(), DynamicNode.class.getName());
return new JUnitException(message, cause);
}
static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node,
int index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter,
JupiterConfiguration configuration) {
UniqueId uniqueId;
Supplier<JupiterTestDescriptor> descriptorCreator;
Optional<TestSource> customTestSource = node.getTestSourceUri().map(TestFactoryTestDescriptor::fromUri);
TestSource source = customTestSource.orElse(defaultTestSource);
if (node instanceof DynamicTest) {
DynamicTest test = (DynamicTest) node;
uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index);
descriptorCreator = () -> new DynamicTestTestDescriptor(uniqueId, index, test, source, configuration);
}
else {
DynamicContainer container = (DynamicContainer) node;
uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index);
descriptorCreator = () -> new DynamicContainerTestDescriptor(uniqueId, index, container, source,
dynamicDescendantFilter, configuration);
}
if (dynamicDescendantFilter.test(uniqueId)) {
JupiterTestDescriptor descriptor = descriptorCreator.get();
parent.addChild(descriptor);
return Optional.of(descriptor);
}
return Optional.empty();
}
static TestSource fromUri(URI uri) {
Preconditions.notNull(uri, "URI must not be null");
return CLASSPATH_SCHEME.equals(uri.getScheme()) ? ClasspathResourceSource.from(uri) : UriSource.from(uri);
}
@Override
public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) {
}
@Override
public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor,
TestExecutionResult result) {
}
}