Skip to content

Commit

Permalink
Support classpath resource for custom TestSource in dynamic tests
Browse files Browse the repository at this point in the history
Issue #1178 introduced support for creating a UriSource (specifically a
FileSource, DirectorySource, or DefaultUriSource) from a URI supplied
for a dynamic container or dynamic test.

This commit further extends that feature by introducing support for
creating a ClasspathResourceSource from a URI supplied for a dynamic
container or dynamic test if the URI contains the "classpath" scheme.
Otherwise, the behavior introduced in #1178 is used.

Issue: #1467
  • Loading branch information
sbrannen committed Jul 6, 2018
1 parent cb8087d commit 03d158b
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ on GitHub.

==== New Features and Improvements

* ❓
* A `ClasspathResourceSource` can now be created from a `URI` via the new `from(URI)`
static factory method if the `URI` uses the `classpath` scheme.


[[release-notes-5.3.0-RC1-junit-jupiter]]
Expand All @@ -41,6 +42,8 @@ on GitHub.

* Although it is _highly discouraged_, it is now possible to extend the `{Assertions}`
and `{Assumptions}` classes for special use cases.
* A custom test source `URI` for a dynamic container or dynamic test will now be
registered as a `ClasspathResourceSource` if the `URI` uses the `classpath` scheme.
* New `TestInstanceFactory` extension API that enables custom creation of test class
instances.
- See <<../user-guide/index.adoc#extensions-test-instance-factories, Test Instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
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;
Expand All @@ -28,8 +30,10 @@
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.TestSource;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
import org.junit.platform.engine.support.descriptor.UriSource;

/**
Expand Down Expand Up @@ -121,7 +125,7 @@ static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescri

UniqueId uniqueId;
Supplier<JupiterTestDescriptor> descriptorCreator;
Optional<TestSource> customTestSource = node.getTestSourceUri().map(UriSource::from);
Optional<TestSource> customTestSource = node.getTestSourceUri().map(TestFactoryTestDescriptor::fromUri);
TestSource source = customTestSource.orElse(defaultTestSource);

if (node instanceof DynamicTest) {
Expand All @@ -143,4 +147,12 @@ static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescri
return Optional.empty();
}

/**
* @since 5.3
*/
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,32 @@

package org.junit.jupiter.engine.descriptor;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Optional;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
import org.junit.jupiter.engine.execution.ThrowableCollector;
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.DirectorySource;
import org.junit.platform.engine.support.descriptor.FilePosition;
import org.junit.platform.engine.support.descriptor.FileSource;
import org.junit.platform.engine.support.descriptor.UriSource;
import org.junit.platform.engine.support.hierarchical.Node;

/**
Expand All @@ -35,48 +45,115 @@
*/
class TestFactoryTestDescriptorTests {

private JupiterEngineExecutionContext context;
private ExtensionContext extensionContext;
private TestFactoryTestDescriptor descriptor;
private boolean isClosed;
/**
* @since 5.3
*/
@Nested
class TestSources {

@Test
void classpathResourceSourceFromUriWithFilePosition() {
FilePosition position = FilePosition.from(42, 21);
URI uri = URI.create("classpath:/test.js?line=42&column=21");
TestSource testSource = TestFactoryTestDescriptor.fromUri(uri);

assertThat(testSource).isInstanceOf(ClasspathResourceSource.class);
ClasspathResourceSource source = (ClasspathResourceSource) testSource;
assertThat(source.getClasspathResourceName()).isEqualTo("test.js");
assertThat(source.getPosition()).hasValue(position);
}

@BeforeEach
void before() throws Exception {
extensionContext = mock(ExtensionContext.class);
isClosed = false;
@Test
void fileSourceFromUriWithFilePosition() {
File file = new File("src/test/resources/log4j2-test.xml");
assertThat(file).isFile();

context = new JupiterEngineExecutionContext(null, null).extend().withThrowableCollector(
new ThrowableCollector()).withExtensionContext(extensionContext).build();
FilePosition position = FilePosition.from(42, 21);
URI uri = URI.create(file.toURI().toString() + "?line=42&column=21");
TestSource testSource = TestFactoryTestDescriptor.fromUri(uri);

Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream");
descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class,
testMethod);
when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod));
}
assertThat(testSource).isInstanceOf(FileSource.class);
FileSource source = (FileSource) testSource;
assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath());
assertThat(source.getPosition()).hasValue(position);
}

@Test
void streamsFromTestFactoriesShouldBeClosed() {
Stream<DynamicTest> dynamicTestStream = Stream.empty();
prepareMockForTestInstanceWithCustomStream(dynamicTestStream);
@Test
void directorySourceFromUri() {
File file = new File("src/test/resources");
assertThat(file).isDirectory();

descriptor.invokeTestMethod(context, mock(Node.DynamicTestExecutor.class));
URI uri = file.toURI();
TestSource testSource = TestFactoryTestDescriptor.fromUri(uri);

assertTrue(isClosed);
}
assertThat(testSource).isInstanceOf(DirectorySource.class);
DirectorySource source = (DirectorySource) testSource;
assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath());
}

@Test
void defaultUriSourceFromUri() {
File file = new File("src/test/resources");
assertThat(file).isDirectory();

@Test
void streamsFromTestFactoriesShouldBeClosedWhenTheyThrow() {
Stream<Integer> integerStream = Stream.of(1, 2);
prepareMockForTestInstanceWithCustomStream(integerStream);
URI uri = URI.create("http://example.com?foo=bar&line=42");
TestSource testSource = TestFactoryTestDescriptor.fromUri(uri);

descriptor.invokeTestMethod(context, mock(Node.DynamicTestExecutor.class));
assertThat(testSource).isInstanceOf(UriSource.class);
assertThat(testSource.getClass().getSimpleName()).isEqualTo("DefaultUriSource");
UriSource source = (UriSource) testSource;
assertThat(source.getUri()).isEqualTo(uri);
}

assertTrue(isClosed);
}

private void prepareMockForTestInstanceWithCustomStream(Stream<?> stream) {
Stream<?> mockStream = stream.onClose(() -> isClosed = true);
when(extensionContext.getRequiredTestInstance()).thenReturn(new CustomStreamTestCase(mockStream));
@Nested
class Streams {

private JupiterEngineExecutionContext context;
private ExtensionContext extensionContext;
private TestFactoryTestDescriptor descriptor;
private boolean isClosed;

@BeforeEach
void before() throws Exception {
extensionContext = mock(ExtensionContext.class);
isClosed = false;

context = new JupiterEngineExecutionContext(null, null).extend().withThrowableCollector(
new ThrowableCollector()).withExtensionContext(extensionContext).build();

Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream");
descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class,
testMethod);
when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod));
}

@Test
void streamsFromTestFactoriesShouldBeClosed() {
Stream<DynamicTest> dynamicTestStream = Stream.empty();
prepareMockForTestInstanceWithCustomStream(dynamicTestStream);

descriptor.invokeTestMethod(context, mock(Node.DynamicTestExecutor.class));

assertTrue(isClosed);
}

@Test
void streamsFromTestFactoriesShouldBeClosedWhenTheyThrow() {
Stream<Integer> integerStream = Stream.of(1, 2);
prepareMockForTestInstanceWithCustomStream(integerStream);

descriptor.invokeTestMethod(context, mock(Node.DynamicTestExecutor.class));

assertTrue(isClosed);
}

private void prepareMockForTestInstanceWithCustomStream(Stream<?> stream) {
Stream<?> mockStream = stream.onClose(() -> isClosed = true);
when(extensionContext.getRequiredTestInstance()).thenReturn(new CustomStreamTestCase(mockStream));
}

}

private static class CustomStreamTestCase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@

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

import java.net.URI;
import java.util.Objects;
import java.util.Optional;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.PreconditionViolationException;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;
import org.junit.platform.engine.TestSource;
Expand All @@ -32,6 +34,14 @@ public class ClasspathResourceSource implements TestSource {

private static final long serialVersionUID = 1L;

/**
* {@link URI} {@linkplain URI#getScheme() scheme} for classpath
* resources: {@value}
*
* @since 1.3
*/
public static final String CLASSPATH_SCHEME = "classpath";

/**
* Create a new {@code ClasspathResourceSource} using the supplied classpath
* resource name.
Expand Down Expand Up @@ -70,6 +80,33 @@ public static ClasspathResourceSource from(String classpathResourceName, FilePos
return new ClasspathResourceSource(classpathResourceName, filePosition);
}

/**
* Create a new {@code ClasspathResourceSource} from the supplied {@link URI}.
*
* <p>The {@link URI#getPath() path} component of the {@code URI} (excluding
* the query) will be used as the classpath resource name. The
* {@linkplain URI#getQuery() query} component of the {@code URI}, if present,
* will be used to retrieve the {@link FilePosition} via
* {@link FilePosition#fromQuery(String)}.
*
* @param uri the {@code URI} for the classpath resource; never {@code null}
* @return a new {@code ClasspathResourceSource}; never {@code null}
* @throws PreconditionViolationException if the supplied {@code URI} is
* {@code null} or if the scheme of the supplied {@code URI} is not equal
* to the {@link #CLASSPATH_SCHEME}
* @since 1.3
* @see #CLASSPATH_SCHEME
*/
public static ClasspathResourceSource from(URI uri) {
Preconditions.notNull(uri, "URI must not be null");
Preconditions.condition(CLASSPATH_SCHEME.equals(uri.getScheme()),
() -> "URI [" + uri + "] must have [" + CLASSPATH_SCHEME + "] scheme");

String classpathResource = ResourceUtils.stripQueryComponent(uri).getPath();
FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null);
return ClasspathResourceSource.from(classpathResource, filePosition);
}

private final String classpathResourceName;
private final FilePosition filePosition;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.platform.engine.support.descriptor;

import java.net.URI;

import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;

/**
* Collection of static utility methods for working with resources.
*
* @since 1.3
*/
final class ResourceUtils {

private ResourceUtils() {
/* no-op */
}

/**
* Strip the {@link URI#getQuery() query} component from the supplied
* {@link URI}.
*
* @param uri the {@code URI} from which to strip the query component
* @return a new {@code URI} with the query component removed, or the
* original {@code URI} unmodified if it does not have a query component
*/
static URI stripQueryComponent(URI uri) {
Preconditions.notNull(uri, "URI must not be null");

if (StringUtils.isBlank(uri.getQuery())) {
return uri;
}

String uriAsString = uri.toString();
return URI.create(uriAsString.substring(0, uriAsString.indexOf('?')));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.apiguardian.api.API;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.engine.TestSource;

/**
Expand Down Expand Up @@ -58,22 +57,17 @@ static UriSource from(URI uri) {
Preconditions.notNull(uri, "URI must not be null");

try {
URI pathBasedUriWithoutQuery = uri;
String query = uri.getQuery();
if (StringUtils.isNotBlank(query)) {
String uriAsString = uri.toString();
pathBasedUriWithoutQuery = URI.create(uriAsString.substring(0, uriAsString.indexOf('?')));
}
Path path = Paths.get(pathBasedUriWithoutQuery);
URI uriWithoutQuery = ResourceUtils.stripQueryComponent(uri);
Path path = Paths.get(uriWithoutQuery);
if (Files.isRegularFile(path)) {
return FileSource.from(path.toFile(), FilePosition.fromQuery(query).orElse(null));
return FileSource.from(path.toFile(), FilePosition.fromQuery(uri.getQuery()).orElse(null));
}
if (Files.isDirectory(path)) {
return DirectorySource.from(path.toFile());
}
}
catch (RuntimeException e) {
LoggerFactory.getLogger(UriSource.class).debug(e, () -> String.format(
catch (RuntimeException ex) {
LoggerFactory.getLogger(UriSource.class).debug(ex, () -> String.format(
"The supplied URI [%s] is not path-based. Falling back to default UriSource implementation.", uri));
}

Expand Down
Loading

0 comments on commit 03d158b

Please sign in to comment.