Skip to content

Commit

Permalink
Add TestSource support to dynamic containers and tests
Browse files Browse the repository at this point in the history
This commit introduces the support to add an instance of `TestSource`
to a dynamic container or test by adding static factory methods that
take a test source as a parameter.

Prior to this commit the source for dynamic tests was always a
`MethodSource` pointing to the `@TestFactory` annotated method.
Now, for example, it is possible to refer to external files, that
are the underlying sources of generated containers and tests.

Addresses part of: #1178
  • Loading branch information
sormuras committed Jun 5, 2018
1 parent ff8bdfb commit 13c013c
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ on GitHub.
* New overloaded variant of `isAnnotated()` in `AnnotationSupport` that accepts
`Optional<? extends AnnotatedElement>` instead of `AnnotatedElement`.


[[release-notes-5.3.0-M1-junit-jupiter]]
=== JUnit Jupiter

Expand All @@ -51,7 +52,10 @@ on GitHub.
_alias_ for `Arguments.of()`. `arguments()` is intended to be used via `import static`.
* New `get<Class>(index)` Kotlin extension method to make `ArgumentsAccessor` friendlier
to use from Kotlin.

* New support for supplying a custom test source when creating a dynamic container or
test. See factory methods `dynamicContainer(String, TestSource, ...)` in
`DynamicContainer` and `dynamicTest.(String, TestSource, Executable)` in `DynamicTest`
for details.

[[release-notes-5.3.0-M1-junit-vintage]]
=== JUnit Vintage
Expand Down
9 changes: 7 additions & 2 deletions junit-jupiter-api/junit-jupiter-api.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
plugins {
id 'nebula.optional-base' version '3.0.3'
}

description = 'JUnit Jupiter API'

dependencies {
api("org.opentest4j:opentest4j:${ota4jVersion}")
api(project(":junit-platform-commons"))
compileOnly("org.jetbrains.kotlin:kotlin-stdlib")
api(project(':junit-platform-commons'))
compile(project(':junit-platform-engine'), optional)
compileOnly('org.jetbrains.kotlin:kotlin-stdlib')
}

jar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.apiguardian.api.API;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.TestSource;

/**
* A {@code DynamicContainer} is a container generated at runtime.
Expand Down Expand Up @@ -49,7 +50,7 @@ public class DynamicContainer extends DynamicNode {
* @see #dynamicContainer(String, Stream)
*/
public static DynamicContainer dynamicContainer(String displayName, Iterable<? extends DynamicNode> dynamicNodes) {
return new DynamicContainer(displayName, StreamSupport.stream(dynamicNodes.spliterator(), false));
return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false));
}

/**
Expand All @@ -65,13 +66,31 @@ public static DynamicContainer dynamicContainer(String displayName, Iterable<? e
* @see #dynamicContainer(String, Iterable)
*/
public static DynamicContainer dynamicContainer(String displayName, Stream<? extends DynamicNode> dynamicNodes) {
return new DynamicContainer(displayName, dynamicNodes);
return dynamicContainer(displayName, null, dynamicNodes);
}

/**
* Factory for creating a new {@code DynamicContainer} for the supplied display
* name, the test source and stream of dynamic nodes.
*
* <p>The stream of dynamic nodes must not contain {@code null} elements.
*
* @param displayName the display name for the dynamic container; never
* {@code null} or blank
* @param testSource the test source for the dynamic test; can be {@code null}
* @param dynamicNodes stream of dynamic nodes to execute;
* never {@code null}
* @see #dynamicContainer(String, Iterable)
*/
public static DynamicContainer dynamicContainer(String displayName, TestSource testSource,
Stream<? extends DynamicNode> dynamicNodes) {
return new DynamicContainer(displayName, testSource, dynamicNodes);
}

private final Stream<? extends DynamicNode> children;

private DynamicContainer(String displayName, Stream<? extends DynamicNode> children) {
super(displayName);
private DynamicContainer(String displayName, TestSource testSource, Stream<? extends DynamicNode> children) {
super(displayName, testSource);
Preconditions.notNull(children, "children must not be null");
this.children = children;
}
Expand All @@ -83,5 +102,4 @@ private DynamicContainer(String displayName, Stream<? extends DynamicNode> child
public Stream<? extends DynamicNode> getChildren() {
return children;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@

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

import java.util.Optional;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;
import org.junit.platform.engine.TestSource;

/**
* {@code DynamicNode} serves as the abstract base class for a container or a
Expand All @@ -29,8 +32,12 @@ public abstract class DynamicNode {

private final String displayName;

DynamicNode(String displayName) {
/** Custom test source instance associated with this node; potentially {@code null}. */
private final TestSource testSource;

DynamicNode(String displayName, TestSource testSource) {
this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank");
this.testSource = testSource;
}

/**
Expand All @@ -40,6 +47,13 @@ public String getDisplayName() {
return this.displayName;
}

/**
* Get the optional test source of this {@code DynamicNode}.
*/
public Optional<TestSource> getTestSource() {
return Optional.ofNullable(testSource);
}

@Override
public String toString() {
return new ToStringBuilder(this).append("displayName", displayName).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.TestSource;

/**
* A {@code DynamicTest} is a test case generated at runtime.
Expand Down Expand Up @@ -59,7 +60,22 @@ public class DynamicTest extends DynamicNode {
* @see #stream(Iterator, Function, ThrowingConsumer)
*/
public static DynamicTest dynamicTest(String displayName, Executable executable) {
return new DynamicTest(displayName, executable);
return new DynamicTest(displayName, null, executable);
}

/**
* Factory for creating a new {@code DynamicTest} for the supplied display
* name, the test source and executable code block.
*
* @param displayName the display name for the dynamic test; never
* {@code null} or blank
* @param testSource the test source for the dynamic test; can be {@code null}
* @param executable the executable code block for the dynamic test;
* never {@code null}
* @see #stream(Iterator, Function, ThrowingConsumer)
*/
public static DynamicTest dynamicTest(String displayName, TestSource testSource, Executable executable) {
return new DynamicTest(displayName, testSource, executable);
}

/**
Expand Down Expand Up @@ -101,8 +117,8 @@ public static <T> Stream<DynamicTest> stream(Iterator<T> inputGenerator,

private final Executable executable;

private DynamicTest(String displayName, Executable executable) {
super(displayName);
private DynamicTest(String displayName, TestSource testSource, Executable executable) {
super(displayName, testSource);
this.executable = Preconditions.notNull(executable, "executable must not be null");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ private Stream<DynamicNode> toDynamicNodeStream(Object testFactoryMethodResult)
}

static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node,
int index, TestSource source, DynamicDescendantFilter dynamicDescendantFilter) {
int index, TestSource testSource, DynamicDescendantFilter dynamicDescendantFilter) {
UniqueId uniqueId;
Supplier<JupiterTestDescriptor> descriptorCreator;
TestSource source = node.getTestSource().orElse(testSource);
if (node instanceof DynamicTest) {
DynamicTest test = (DynamicTest) node;
uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.lang.reflect.InvocationTargetException;
Expand All @@ -22,14 +23,21 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.function.Executable;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.descriptor.PackageSource;
import org.opentest4j.AssertionFailedError;

/**
* @since 5.0
*/
class DynamicTestTests {

private static final Executable nix = () -> {
};

private final List<String> assertedValues = new ArrayList<>();

@Test
Expand Down Expand Up @@ -79,6 +87,24 @@ void reflectiveOperationThrowingInvocationTargetException() {
assertThat(t50.getCause()).hasMessage("expected: <1> but was: <50>");
}

@Test
void testSourceIsNotPresentByDefault() {
DynamicTest test = dynamicTest("foo", nix);
assertThat(test.getTestSource()).isNotPresent();
assertThat(dynamicContainer("bar", Stream.of(test)).getTestSource()).isNotPresent();
}

@Test
void testSourceIsReturnedWhenSupplied() {
TestSource testSource = ClassSource.from(getClass());
DynamicTest test = dynamicTest("foo", testSource, nix);
TestSource containerSource = PackageSource.from("org.junit.jupiter.api");
DynamicContainer container = dynamicContainer("bar", containerSource, Stream.of(test));

assertThat(test.getTestSource().get()).isSameAs(testSource);
assertThat(container.getTestSource().get()).isSameAs(containerSource);
}

private void assert1Equals48Directly() {
Assertions.assertEquals(1, 48);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
import static org.junit.jupiter.api.Assertions.assertLinesMatch;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.junit.platform.commons.util.ReflectionUtils.findMethods;
import static org.junit.platform.commons.util.ReflectionUtils.getFullyQualifiedMethodName;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
Expand All @@ -33,7 +36,6 @@

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
Expand All @@ -42,6 +44,9 @@
import org.junit.jupiter.api.function.Executable;
import org.junit.platform.console.options.Details;
import org.junit.platform.console.options.Theme;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.support.descriptor.ClassSource;
import org.junit.platform.engine.support.descriptor.FileSource;

/**
* @since 1.0
Expand Down Expand Up @@ -95,11 +100,15 @@ private List<DynamicNode> scanContainerClassAndCreateDynamicTests(Class<?> conta
String displayName = methodName + "() " + theme.name();
String dirName = "console/details/" + containerName.toLowerCase();
String outName = caption + ".out.txt";
tests.add(DynamicTest.dynamicTest(displayName, new Runner(dirName, outName, args)));
Runner runner = new Runner(dirName, outName, args);
FileSource source = FileSource.from(new File(dirName, outName));
tests.add(dynamicTest(displayName, source, runner));
}
}
}
map.forEach((details, tests) -> nodes.add(DynamicContainer.dynamicContainer(details.name(), tests)));
// TestSource source = DirectorySource.from(new File("src/test/resources/console/details"));
TestSource source = ClassSource.from(Details.class);
map.forEach((details, tests) -> nodes.add(dynamicContainer(details.name(), source, tests.stream())));
return nodes;
}

Expand Down

0 comments on commit 13c013c

Please sign in to comment.