Skip to content

Commit

Permalink
Read ConfigurationParameters from properties file in classpath
Browse files Browse the repository at this point in the history
WIP

Issue: #1003
  • Loading branch information
sbrannen committed Aug 14, 2017
1 parent d30eb02 commit f4027b5
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 26 deletions.
15 changes: 8 additions & 7 deletions documentation/src/docs/asciidoc/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ name in a file named `org.junit.jupiter.api.extension.Extension` within the
===== Enabling Automatic Extension Detection

Auto-detection is an advanced feature and is therefore not enabled by default. To enable
it, simply set the `junit.jupiter.extensions.autodetection.enabled` configuration
parameter to `true`. This can be supplied as a JVM system property or as a _configuration
parameter_ in the `LauncherDiscoveryRequest` that is passed to the `Launcher`.
it, simply set the `junit.jupiter.extensions.autodetection.enabled` _configuration
parameter_ to `true`. This can be supplied as a JVM system property, as a _configuration
parameter_ in the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the
JUnit Platform configuration file (see <<running-tests-config-params>> for details).

For example, to enable auto-detection of extensions, you can start your JVM with the
following system property.
Expand Down Expand Up @@ -134,10 +135,10 @@ See the source code of `{DisabledCondition}` and `{Disabled}` for concrete examp
Sometimes it can be useful to run a test suite _without_ certain conditions being active.
For example, you may wish to run tests even if they are annotated with `@Disabled` in
order to see if they are still _broken_. To do this, simply provide a pattern for the
`junit.jupiter.conditions.deactivate` configuration parameter to specify which conditions
should be deactivated (i.e., not evaluated) for the current test run. The pattern can be
supplied as a JVM system property or as a _configuration parameter_ in the
`LauncherDiscoveryRequest` that is passed to the `Launcher`.
`junit.jupiter.conditions.deactivate` _configuration parameter_ to specify which
conditions should be deactivated (i.e., not evaluated) for the current test run. The
pattern can be supplied as a JVM system property, as a _configuration parameter_ in the
`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform configuration file (see <<running-tests-config-params>> for details).

For example, to deactivate JUnit's `@Disabled` condition, you can start your JVM with the
following system property.
Expand Down
29 changes: 29 additions & 0 deletions documentation/src/docs/asciidoc/running-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,32 @@ NOTE: There are more configuration options for discovering and filtering tests t
`@SelectPackages`. Please consult the
{javadoc-root}/org/junit/platform/suite/api/package-summary.html[Javadoc] for further
details.

[[running-tests-config-params]]
=== Configuration Parameters

In addition to instructing the platform which test classes and test engines to include,
which packages to scan, etc., it is sometimes necessary to provide additional custom
configuration parameters that are specific to a particular test engine. For example, the
JUnit Jupiter `TestEngine` supports _configuration parameters_ for the following use
cases.

- <<writing-tests-test-instance-lifecycle-changing-default>>
- <<extensions-registration-automatic-enabling>>
- <<extensions-conditions-deactivation>>

_Configuration Parameters_ are text-based key-value pairs that can be supplied to test
engines running on the JUnit Platform via one of the following mechanisms.

1. The `configurationParameter()` and `configurationParameters()` methods in the
`LauncherDiscoveryRequestBuilder` which is used to build a request supplied to the
<<launcher-api, `Launcher` API>>.
2. JVM system properties.
3. The JUnit Platform configuration file: a file named `junit-platform.properties` in the
root of the class path that follows the syntax rules for a Java `Properties` file.

NOTE: Configuration parameters are looked up in the exact order defined above.
Consequently, configuration parameters supplied directly to the `Launcher` take
precedence over those supplied via system properties and the configuration file.
Similarly, configuration parameters supplied via system properties take precedence over
those supplied via the configuration file.
23 changes: 18 additions & 5 deletions documentation/src/docs/asciidoc/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,34 @@ If a test class or test interface is not annotated with `@TestInstance`, JUnit J
will use a _default_ lifecycle mode. The standard _default_ mode is `PER_METHOD`;
however, it is possible to change the _default_ for the execution of an entire test plan.
To change the default test instance lifecycle mode, simply set the
`junit.jupiter.testinstance.lifecycle.default` configuration parameter to the name of an
enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied as
a JVM system property or as a _configuration parameter_ in the `LauncherDiscoveryRequest`
that is passed to the `Launcher`.
`junit.jupiter.testinstance.lifecycle.default` _configuration parameter_ to the name of
an enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied
as a JVM system property, as a _configuration parameter_ in the
`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform
configuration file (see <<running-tests-config-params>> for details).

For example, to set the default test instance lifecycle mode to `Lifecycle.PER_CLASS`,
you can start your JVM with the following system property.

`-Djunit.jupiter.testinstance.lifecycle.default=per_class`

Note, however, that setting the default test instance lifecycle mode via the JUnit
Platform configuration file is a more robust solution since the configuration file can be
checked into a version control system along with your project and can therefore be used
within IDEs and your build software.

To set the default test instance lifecycle mode to `Lifecycle.PER_CLASS` via the JUnit
Platform configuration file, create a file named `junit-platform.properties` in the root
of the class path (e.g., `src/test/resources`) with the following content.

`junit.jupiter.testinstance.lifecycle.default = per_class`

WARNING: Changing the _default_ test instance lifecycle mode can lead to unpredictable
results and fragile builds if not applied consistently. For example, if the build
configures "per-class" semantics as the default but tests in the IDE are executed using
"per-method" semantics, that can make it difficult to debug errors that occur on the
build server.
build server. It is therefore recommended to change the default in the JUnit Platform
configuration file instead of via a JVM system property.

[[writing-tests-nested]]
=== Nested Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,59 @@
public interface ConfigurationParameters {

/**
* Get the configuration property stored under the specified {@code key}.
* Name of the JUnit Platform configuration file: {@value}.
*
* <p>If a properties file with this name is present in the root of the
* classpath, it will be used as a source for <em>configuration
* parameters</em>. If multiple files are present, only the first one
* detected in the classpath will be used.
*
* @see java.util.Properties
*/
String CONFIG_FILE_NAME = "junit-platform.properties";

/**
* Get the configuration parameter stored under the specified {@code key}.
*
* <p>If no such key is present in this {@code ConfigurationParameters},
* an attempt will be made to look up the value as a Java system property.
* an attempt will be made to look up the value as a JVM system property.
* If no such system property exists, an attempt will be made to look up
* the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties
* file}.
*
* @param key the key to look up; never {@code null} or blank
* @return an {@code Optional} containing the potential value
* @return an {@code Optional} containing the value; never {@code null}
* but potentially empty
*
* @see #getBoolean(String)
* @see System#getProperty(String)
* @see #CONFIG_FILE_NAME
*/
Optional<String> get(String key);

/**
* Get the boolean configuration property stored under the specified {@code key}.
* Get the boolean configuration parameter stored under the specified
* {@code key}.
*
* <p>If no such key is present in this {@code ConfigurationParameters},
* an attempt will be made to look up the value as a Java system property.
* an attempt will be made to look up the value as a JVM system property.
* If no such system property exists, an attempt will be made to look up
* the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties
* file}.
*
* @param key the key to look up; never {@code null} or blank
* @return an {@code Optional} containing the potential value
* @return an {@code Optional} containing the value; never {@code null}
* but potentially empty
*
* @see #get(String)
* @see Boolean#parseBoolean(String)
* @see System#getProperty(String)
* @see #CONFIG_FILE_NAME
*/
Optional<Boolean> getBoolean(String key);

/**
* Get the number of configuration properties stored directly in this
* Get the number of configuration parameters stored directly in this
* {@code ConfigurationParameters}.
*/
int size();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@

package org.junit.platform.launcher.core;

import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.junit.platform.commons.util.ClassLoaderUtils;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;
import org.junit.platform.engine.ConfigurationParameters;
Expand All @@ -22,11 +30,52 @@
*/
class LauncherConfigurationParameters implements ConfigurationParameters {

private final Map<String, String> configurationParameters;
private static final Logger LOG = Logger.getLogger(LauncherConfigurationParameters.class.getName());

LauncherConfigurationParameters(Map<String, String> configurationParameters) {
Preconditions.notNull(configurationParameters, "configuration parameters must not be null");
this.configurationParameters = configurationParameters;
private final Map<String, String> explicitConfigParams;
private final Properties configParamsFromFile;

LauncherConfigurationParameters(Map<String, String> configParams) {
this(configParams, ConfigurationParameters.CONFIG_FILE_NAME);
}

LauncherConfigurationParameters(Map<String, String> configParams, String configFileName) {
Preconditions.notNull(configParams, "configuration parameters must not be null");
Preconditions.notBlank(configFileName, "configFileName must not be null or blank");
this.explicitConfigParams = configParams;
this.configParamsFromFile = fromClasspathResource(configFileName.trim());
}

private static Properties fromClasspathResource(String configFileName) {
Properties props = new Properties();

try {
ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader();
List<URL> resources = Collections.list(classLoader.getResources(configFileName));

if (!resources.isEmpty()) {
if (resources.size() > 1) {
LOG.warning(() -> String.format(
"Discovered %d '%s' configuration files in the classpath; only the first will be used.",
resources.size(), configFileName));
}

URL configFileUrl = resources.get(0);
LOG.info(() -> String.format(
"Loading JUnit Platform configuration parameters from classpath resource [%s].", configFileUrl));
try (InputStream inputStream = configFileUrl.openStream()) {
props.load(inputStream);
}
}
}
catch (Exception ex) {
LOG.log(Level.WARNING, ex,
() -> String.format(
"Failed to load JUnit Platform configuration parameters from classpath resource [%s].",
configFileName));
}

return props;
}

@Override
Expand All @@ -45,27 +94,45 @@ public Optional<Boolean> getBoolean(String key) {

@Override
public int size() {
return this.configurationParameters.size();
return this.explicitConfigParams.size();
}

private String getProperty(String key) {
Preconditions.notBlank(key, "key must not be null or blank");
String value = this.configurationParameters.get(key);

// 1) Check explicit config param.
String value = this.explicitConfigParams.get(key);

// 2) Check system property.
if (value == null) {
try {
value = System.getProperty(key);
}
catch (Exception ex) {
/* ignore */
}

// 3) Check config file.
if (value == null) {
value = this.configParamsFromFile.getProperty(key);
}
}

return value;
}

@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
this.configurationParameters.forEach(builder::append);

this.explicitConfigParams.forEach(builder::append);

// @formatter:off
this.configParamsFromFile.stringPropertyNames().stream()
.filter(key -> !this.explicitConfigParams.containsKey(key))
.forEach(key -> builder.append(key, this.configParamsFromFile.getProperty(key)));
// @formatter:on

return builder.toString();
}

Expand Down
Loading

0 comments on commit f4027b5

Please sign in to comment.