Skip to content

Commit

Permalink
Support global extension registration via ServiceLoader
Browse files Browse the repository at this point in the history
Prior to this commit, custom extensions could only be registered
declaratively via @ExtendWith.

This commit introduces support for auto-detection of extensions
via Java's ServiceLoader mechanism. Such extensions will be
automatically registered after the default global extensions
in JUnit Jupiter (e.g., support for TestInfo, TestReporter, etc.).

Furthermore, this feature is disabled by default but can be enabled
via a JVM system property or JUnit Platform configuration parameter.
Consult the JavaDoc and User Guide for details.

Issue: #448
  • Loading branch information
reinhapa authored and sbrannen committed Mar 25, 2017
1 parent 1977471 commit 4697f4f
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 7 deletions.
19 changes: 18 additions & 1 deletion documentation/src/docs/asciidoc/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ class, test method, or custom _<<writing-tests-meta-annotations,composed annotat
with `@ExtendWith(...)` and supplying class references for the extensions to register.

For example, to register a custom `MockitoExtension` for a particular test method, you
would annotate the test method as follows.
would annotate the test method as follows. For special cases there is also the possiblilty
to use <<extensions-registration-service-loader,service loader based>> extension
registration.

[source,java,indent=0]
[subs="verbatim"]
Expand Down Expand Up @@ -71,6 +73,21 @@ class MyTestsV2 {
The execution of tests in both `MyTestsV1` and `MyTestsV2` will be extended by the
`FooExtension` and `BarExtension`, in exactly that order.

[[extensions-registration-service-loader]]
==== Service Loader Extension Registration

Besides to the default <<extensions-registration-declarative,extension registration>>
behaviour using annotations as described before, there is a also an enhanced extension
registration mechanism using the Java service loader facility.

This feature is not enabled by default and must be activated setting the following system
property:

`-Djunit.extensions.auto-detect=true`

When being enabled the extension registry will be initialized using the global extensions
and then all extensions available thru the service loader will be added afterwards.

[[extensions-registration-inheritance]]
==== Extension Inheritance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

package org.junit.jupiter.engine;

import java.util.ServiceLoader;

/**
* Collection of constants related to the {@link JupiterTestEngine}.
*
Expand Down Expand Up @@ -59,6 +61,14 @@ public final class Constants {
*/
public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = "*";

/**
* Property name used to enable test extensions to be loaded using the {@link ServiceLoader} additional
* to the global default ones within {@code ExtensionRegistry.createRegistryWithDefaultExtensions()}.
*
* The default behavior is not to load any extensions using the service loader.
*/
public static final String EXTENSIONS_AUTODETECT_PROPERTY_NAME = "junit.extensions.auto-detect";

private Constants() {
/* no-op */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.engine.descriptor;

import static org.junit.jupiter.engine.Constants.EXTENSIONS_AUTODETECT_PROPERTY_NAME;
import static org.junit.jupiter.engine.extension.ExtensionRegistry.createRegistryWithDefaultExtensions;
import static org.junit.platform.commons.meta.API.Usage.Internal;

Expand All @@ -31,7 +32,9 @@ public JupiterEngineDescriptor(UniqueId uniqueId) {

@Override
public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) {
return context.extend().withExtensionRegistry(createRegistryWithDefaultExtensions()).build();
boolean autoDetect = Boolean.parseBoolean(
context.getConfigurationParameters().get(EXTENSIONS_AUTODETECT_PROPERTY_NAME).orElse("false"));
return context.extend().withExtensionRegistry(createRegistryWithDefaultExtensions(autoDetect)).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Stream;
Expand Down Expand Up @@ -55,11 +56,17 @@ public class ExtensionRegistry {
* Factory for creating and populating a new root registry with the default
* extensions.
*
* @param autoDetect {@code true} to detect extensions also using
* service loader, {@code false} otherwise
* @return a new {@code ExtensionRegistry}
*/
public static ExtensionRegistry createRegistryWithDefaultExtensions() {
public static ExtensionRegistry createRegistryWithDefaultExtensions(boolean autoDetect) {
ExtensionRegistry extensionRegistry = new ExtensionRegistry(null);
DEFAULT_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension);
if (autoDetect) {
ServiceLoader.load(Extension.class, ReflectionUtils.getDefaultClassLoader()).forEach(
extensionRegistry::registerDefaultExtension);
}
return extensionRegistry;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ExecutableInvokerTests {
private Method method;

private final ExtensionContext extensionContext = mock(ExtensionContext.class);
private ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions();
private ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions(false);

@Test
void constructorInjection() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void executionListenerIsHandedOnWhenContextIsExtended() {
void extendWithAllAttributes() {
ClassBasedContainerExtensionContext extensionContext = new ClassBasedContainerExtensionContext(null, null,
null);
ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions();
ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions(false);
TestInstanceProvider testInstanceProvider = mock(TestInstanceProvider.class);
JupiterEngineExecutionContext newContext = originalContext.extend() //
.withExtensionContext(extensionContext) //
Expand All @@ -65,7 +65,7 @@ void extendWithAllAttributes() {
void canOverrideAttributeWhenContextIsExtended() {
ClassBasedContainerExtensionContext extensionContext = new ClassBasedContainerExtensionContext(null, null,
null);
ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions();
ExtensionRegistry extensionRegistry = ExtensionRegistry.createRegistryWithDefaultExtensions(false);
TestInstanceProvider testInstanceProvider = mock(TestInstanceProvider.class);
ClassBasedContainerExtensionContext newExtensionContext = new ClassBasedContainerExtensionContext(
extensionContext, null, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ContainerExecutionCondition;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ParameterResolver;
Expand All @@ -34,7 +35,26 @@
*/
public class ExtensionRegistryTests {

private final ExtensionRegistry registry = createRegistryWithDefaultExtensions();
private final ExtensionRegistry registry = createRegistryWithDefaultExtensions(false);

@Test
void newRegistryWithoutParentHasDefaultExtensionsUsingServiceLocator() {
ExtensionRegistry registry = createRegistryWithDefaultExtensions(true);
List<Extension> extensions = registry.getExtensions(Extension.class);

assertEquals(5, extensions.size());
assertExtensionRegistered(registry, DisabledCondition.class);
assertExtensionRegistered(registry, RepeatedTestExtension.class);
assertExtensionRegistered(registry, TestInfoParameterResolver.class);
assertExtensionRegistered(registry, TestReporterParameterResolver.class);
assertExtensionRegistered(registry, ServiceLoaderExtension.class);

assertEquals(2, countExtensions(registry, ParameterResolver.class));
assertEquals(1, countExtensions(registry, ContainerExecutionCondition.class));
assertEquals(1, countExtensions(registry, TestExecutionCondition.class));
assertEquals(1, countExtensions(registry, TestTemplateInvocationContextProvider.class));
assertEquals(1, countExtensions(registry, BeforeAllCallback.class));
}

@Test
void newRegistryWithoutParentHasDefaultExtensions() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2015-2017 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 v1.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/

package org.junit.jupiter.engine.extension;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ContainerExtensionContext;

public class ServiceLoaderExtension implements BeforeAllCallback {

@Override
public void beforeAll(ContainerExtensionContext context) throws Exception {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.junit.jupiter.engine.extension.ServiceLoaderExtension

0 comments on commit 4697f4f

Please # to comment.