Skip to content

Commit

Permalink
Allow executing dynamic tests/test template invocations by unique ID
Browse files Browse the repository at this point in the history
Resolves #1025.
  • Loading branch information
marcphilipp committed Jan 13, 2018
1 parent 7eea254 commit fbbd70b
Show file tree
Hide file tree
Showing 15 changed files with 572 additions and 94 deletions.
Expand Up @@ -118,6 +118,10 @@ _@API Guardian_ JAR _mandatory_ again.
extension context ends, the associated store is closed, and each stored value that is extension context ends, the associated store is closed, and each stored value that is
an instance of `ExtensionContext.Store.CloseableResource` is notified by an invocation an instance of `ExtensionContext.Store.CloseableResource` is notified by an invocation
of its `close()` method. of its `close()` method.
* Selected dynamic tests and test template invocations can now be executed separately
without running the complete test factory or test template. This allows to rerun single
or selected parameterized, repeated or dynamic tests by selecting their unique IDs in
subsequent discovery requests.




[[release-notes-5.1.0-M2-junit-vintage]] [[release-notes-5.1.0-M2-junit-vintage]]
Expand Down
Expand Up @@ -12,6 +12,7 @@


import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.createDynamicDescriptor; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.createDynamicDescriptor;


import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream; import java.util.stream.Stream;


Expand All @@ -32,12 +33,14 @@ class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor {


private final DynamicContainer dynamicContainer; private final DynamicContainer dynamicContainer;
private final TestSource testSource; private final TestSource testSource;
private final DynamicDescendantFilter dynamicDescendantFilter;


DynamicContainerTestDescriptor(UniqueId uniqueId, int index, DynamicContainer dynamicContainer, DynamicContainerTestDescriptor(UniqueId uniqueId, int index, DynamicContainer dynamicContainer,
TestSource testSource) { TestSource testSource, DynamicDescendantFilter dynamicDescendantFilter) {
super(uniqueId, index, dynamicContainer, testSource); super(uniqueId, index, dynamicContainer, testSource);
this.dynamicContainer = dynamicContainer; this.dynamicContainer = dynamicContainer;
this.testSource = testSource; this.testSource = testSource;
this.dynamicDescendantFilter = dynamicDescendantFilter;
} }


@Override @Override
Expand All @@ -53,13 +56,15 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte
// @formatter:off // @formatter:off
children.peek(child -> Preconditions.notNull(child, "individual dynamic node must not be null")) children.peek(child -> Preconditions.notNull(child, "individual dynamic node must not be null"))
.map(child -> toDynamicDescriptor(index.getAndIncrement(), child)) .map(child -> toDynamicDescriptor(index.getAndIncrement(), child))
.filter(Optional::isPresent)
.map(Optional::get)
.forEachOrdered(dynamicTestExecutor::execute); .forEachOrdered(dynamicTestExecutor::execute);
// @formatter:on // @formatter:on
} }
return context; return context;
} }


private JupiterTestDescriptor toDynamicDescriptor(int index, DynamicNode childNode) { private Optional<JupiterTestDescriptor> toDynamicDescriptor(int index, DynamicNode childNode) {
return createDynamicDescriptor(this, childNode, index, testSource); return createDynamicDescriptor(this, childNode, index, testSource, dynamicDescendantFilter);
} }
} }
@@ -0,0 +1,58 @@
/*
* Copyright 2015-2018 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
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.engine.descriptor;

import static org.apiguardian.api.API.Status.INTERNAL;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import org.apiguardian.api.API;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;

/**
* Filter for dynamic descendants of {@link TestDescriptor TestDescriptors} that
* implement {@link Filterable}.
*
* @since 5.1
* @see Filterable
*/
@API(status = INTERNAL, since = "5.1")
public class DynamicDescendantFilter implements Predicate<UniqueId> {

private final Set<UniqueId> allowed = new HashSet<>();
private Mode mode = Mode.EXPLICIT;

public void allow(UniqueId uniqueId) {
if (this.mode == Mode.EXPLICIT) {
this.allowed.add(uniqueId);
}
}

public void allowAll() {
this.mode = Mode.ALLOW_ALL;
this.allowed.clear();
}

public boolean test(UniqueId uniqueId) {
return allowed.isEmpty() || allowed.stream().anyMatch(allowedUniqueId -> isAllowed(uniqueId, allowedUniqueId));
}

private boolean isAllowed(UniqueId currentUniqueId, UniqueId allowedUniqueId) {
return allowedUniqueId.hasPrefix(currentUniqueId) || currentUniqueId.hasPrefix(allowedUniqueId);
}

private enum Mode {
EXPLICIT, ALLOW_ALL
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2015-2018 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
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.engine.descriptor;

import static org.apiguardian.api.API.Status.INTERNAL;

import org.apiguardian.api.API;

/**
* {@code Filterable} is implemented by
* {@link org.junit.platform.engine.TestDescriptor TestDescriptors} that may
* register dynamic tests during execution and support selective test execution.
*
* @since 5.1
* @see DynamicDescendantFilter
*/
@API(status = INTERNAL, since = "5.1")
public interface Filterable {

DynamicDescendantFilter getDynamicDescendantFilter();

}
Expand Up @@ -14,6 +14,8 @@


import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;


import org.apiguardian.api.API; import org.apiguardian.api.API;
Expand All @@ -36,17 +38,26 @@
* @since 5.0 * @since 5.0
*/ */
@API(status = INTERNAL, since = "5.0") @API(status = INTERNAL, since = "5.0")
public class TestFactoryTestDescriptor extends TestMethodTestDescriptor { public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implements Filterable {


public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container"; public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container";
public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test"; public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test";


private static final ExecutableInvoker executableInvoker = new ExecutableInvoker(); private static final ExecutableInvoker executableInvoker = new ExecutableInvoker();


private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod) { public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod) {
super(uniqueId, testClass, testMethod); super(uniqueId, testClass, testMethod);
} }


// --- Filterable ----------------------------------------------------------

@Override
public DynamicDescendantFilter getDynamicDescendantFilter() {
return dynamicDescendantFilter;
}

// --- TestDescriptor ------------------------------------------------------ // --- TestDescriptor ------------------------------------------------------


@Override @Override
Expand Down Expand Up @@ -76,8 +87,9 @@ protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTe
Iterator<DynamicNode> iterator = dynamicNodeStream.iterator(); Iterator<DynamicNode> iterator = dynamicNodeStream.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
DynamicNode dynamicNode = iterator.next(); DynamicNode dynamicNode = iterator.next();
JupiterTestDescriptor descriptor = createDynamicDescriptor(this, dynamicNode, index++, source); Optional<JupiterTestDescriptor> descriptor = createDynamicDescriptor(this, dynamicNode, index++,
dynamicTestExecutor.execute(descriptor); source, getDynamicDescendantFilter());
descriptor.ifPresent(dynamicTestExecutor::execute);
} }
} }
catch (ClassCastException ex) { catch (ClassCastException ex) {
Expand All @@ -96,21 +108,27 @@ private Stream<DynamicNode> toDynamicNodeStream(Object testFactoryMethodResult)
} }
} }


static JupiterTestDescriptor createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node, int index, static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node,
TestSource source) { int index, TestSource source, DynamicDescendantFilter dynamicDescendantFilter) {
JupiterTestDescriptor descriptor; UniqueId uniqueId;
Supplier<JupiterTestDescriptor> descriptorCreator;
if (node instanceof DynamicTest) { if (node instanceof DynamicTest) {
DynamicTest test = (DynamicTest) node; DynamicTest test = (DynamicTest) node;
UniqueId uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index); uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index);
descriptor = new DynamicTestTestDescriptor(uniqueId, index, test, source); descriptorCreator = () -> new DynamicTestTestDescriptor(uniqueId, index, test, source);
} }
else { else {
DynamicContainer container = (DynamicContainer) node; DynamicContainer container = (DynamicContainer) node;
UniqueId uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index); uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index);
descriptor = new DynamicContainerTestDescriptor(uniqueId, index, container, source); descriptorCreator = () -> new DynamicContainerTestDescriptor(uniqueId, index, container, source,
dynamicDescendantFilter);
}
if (dynamicDescendantFilter.test(uniqueId)) {
JupiterTestDescriptor descriptor = descriptorCreator.get();
parent.addChild(descriptor);
return Optional.of(descriptor);
} }
parent.addChild(descriptor); return Optional.empty();
return descriptor;
} }


private JUnitException invalidReturnTypeException(Throwable cause) { private JUnitException invalidReturnTypeException(Throwable cause) {
Expand Down
Expand Up @@ -15,6 +15,7 @@


import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;


import org.apiguardian.api.API; import org.apiguardian.api.API;
Expand All @@ -34,12 +35,23 @@
* @since 5.0 * @since 5.0
*/ */
@API(status = INTERNAL, since = "5.0") @API(status = INTERNAL, since = "5.0")
public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor { public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implements Filterable {

private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();


public TestTemplateTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method templateMethod) { public TestTemplateTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method templateMethod) {
super(uniqueId, testClass, templateMethod); super(uniqueId, testClass, templateMethod);
} }


// --- Filterable ----------------------------------------------------------

@Override
public DynamicDescendantFilter getDynamicDescendantFilter() {
return dynamicDescendantFilter;
}

// --- TestDescriptor ------------------------------------------------------

@Override @Override
public Type getType() { public Type getType() {
return Type.CONTAINER; return Type.CONTAINER;
Expand Down Expand Up @@ -83,6 +95,8 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte
providers.stream() providers.stream()
.flatMap(provider -> provider.provideTestTemplateInvocationContexts(extensionContext)) .flatMap(provider -> provider.provideTestTemplateInvocationContexts(extensionContext))
.map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet())) .map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet()))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor)); .forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor));
// @formatter:on // @formatter:on
validateWasAtLeastInvokedOnce(invocationIndex.get()); validateWasAtLeastInvokedOnce(invocationIndex.get());
Expand All @@ -103,10 +117,14 @@ private List<TestTemplateInvocationContextProvider> validateProviders(ExtensionC
TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod())); TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod()));
} }


private TestDescriptor createInvocationTestDescriptor(TestTemplateInvocationContext invocationContext, int index) { private Optional<TestDescriptor> createInvocationTestDescriptor(TestTemplateInvocationContext invocationContext,
int index) {
UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index);
return new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), invocationContext, if (getDynamicDescendantFilter().test(uniqueId)) {
index); return Optional.of(new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(),
invocationContext, index));
}
return Optional.empty();
} }


private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) {
Expand Down

0 comments on commit fbbd70b

Please sign in to comment.