Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow different sources for configuration #1476

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<groupId>io.cucumber</groupId>
<artifactId>datatable</artifactId>
</dependency>
<dependency>
<groupId>io.leangen.geantyref</groupId>
<artifactId>geantyref</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/cucumber/api/CucumberOptionsProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cucumber.api;

import cucumber.runtime.AbstractCucumberOptionsProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Define a class extending {@link AbstractCucumberOptionsProvider} which will provide additional
* Cucumber options during runtime.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CucumberOptionsProvider {
/** @return the class used to contribute options */
Class<? extends AbstractCucumberOptionsProvider> value() default
AbstractCucumberOptionsProvider.class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cucumber.runtime;

import cucumber.api.CucumberOptions;
import io.leangen.geantyref.AnnotationFormatException;
import io.leangen.geantyref.TypeFactory;
import java.util.Map;

/** This class allows dynamically contributing Cucumber options at runtime. */
public abstract class AbstractCucumberOptionsProvider {

/**
* This default provider returns an "empty" {@link CucumberOptions} object, i.e. all values are
* set to their default. Override this method to retrieve Cucumber options from any source
* (properties file, database etc.)
*
* @return the {@link CucumberOptions} to use
*/
public abstract CucumberOptions getOptions();

/**
* @param options map to hold the cucumber options
* @return an instance of {@link CucumberOptions} based on the values in the options mpa
*/
protected CucumberOptions getCucumberOptions(Map<String, Object> options) {
try {
return TypeFactory.annotation(CucumberOptions.class, options);
} catch (AnnotationFormatException e) {
throw new CucumberException(
String.format(
"Error creating %s from options map %s",
CucumberOptions.class.getName(), options.toString()),
e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cucumber.runtime;

import cucumber.api.CucumberOptions;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* Extension of {@link AbstractCucumberOptionsProvider} which will read Cucumber options from a
* properties file.
*
* <p>All properties with a key matching any of the methods in {@link CucumberOptions} are used to
* create an instance of {@link CucumberOptions}. All other properties are ignored.
*
* <p>Values which are of type String[] in {@link CucumberOptions} need to be defined as a single
* string separated by {@link cucumber.runtime.PropertiesFileCucumberOptionsProvider.DELIMITER} in
* the properties file.
*
* <p>The fully qualified path to the properties file can be passed via the `cucumberProperties`
* System property. The default is cucumber.properties in the user.home directory.
*/
public class PropertiesFileCucumberOptionsProvider extends AbstractCucumberOptionsProvider {

public static final String PROPERTIES_FILE_PATH_KEY = "cucumberProperties";
public static final String DELIMITER = ",";
private String propertiesPath;

public PropertiesFileCucumberOptionsProvider() {
this.propertiesPath =
System.getProperty(
PROPERTIES_FILE_PATH_KEY,
System.getProperty("user.home") + File.pathSeparator + "cucumber.properties");
}

public CucumberOptions getOptions() {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propertiesPath));
} catch (IOException exception) {
throw new CucumberException("Error loading properties from file", exception);
}

// create cucumber options from properties
Map<String, Object> options = new HashMap<String, Object>();
for (Method method : CucumberOptions.class.getDeclaredMethods()) {
addOptionIfPresent(properties, options, method.getName(), method.getReturnType());
}
return getCucumberOptions(options);
}

private void addOptionIfPresent(
Properties props, Map<String, Object> options, String key, Class<?> type) {
String value = props.getProperty(key);
// map property value to the appropriate type
if (value != null && !value.isEmpty()) {
if (type.equals(boolean.class)) {
options.put(key, Boolean.parseBoolean(value));
} else if (type.equals(String[].class)) {
options.put(key, value.split(DELIMITER));
} else if (type instanceof Class && ((Class<?>) type).isEnum()) {
// TODO: this is not ideal as we make the assumption that the value of the enum matches its name (except case).
options.put(key, Enum.valueOf((Class<? extends Enum>) type, value.toUpperCase()));
} else {
throw new UnsupportedOperationException(
String.format(
"%s doesn't support mapping to type %f. Only boolean and String[] are supported.",
this.getClass().getName(), type.getClass().getName()));
}
}
}
}
43 changes: 40 additions & 3 deletions core/src/main/java/cucumber/runtime/RuntimeOptionsFactory.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package cucumber.runtime;

import static java.util.Arrays.asList;
import cucumber.api.CucumberOptions;
import cucumber.api.CucumberOptionsProvider;
import cucumber.runtime.io.MultiLoader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static java.util.Arrays.asList;

public class RuntimeOptionsFactory {
private final Class clazz;
private boolean featuresSpecified = false;
Expand All @@ -28,6 +29,11 @@ private List<String> buildArgsFromOptions() {

for (Class classWithOptions = clazz; hasSuperClass(classWithOptions); classWithOptions = classWithOptions.getSuperclass()) {
CucumberOptions options = getOptions(classWithOptions);
AbstractCucumberOptionsProvider optionsProvider = getOptionsProvider(classWithOptions);
if (optionsProvider != null)
{
contributeOptions(optionsProvider.getOptions(), args);
}
if (options != null) {
addDryRun(options, args);
addMonochrome(options, args);
Expand All @@ -46,6 +52,37 @@ private List<String> buildArgsFromOptions() {
return args;
}

private AbstractCucumberOptionsProvider getOptionsProvider(Class<?> clazz) {
CucumberOptionsProvider optionsProvider = clazz.getAnnotation(CucumberOptionsProvider.class);
if (optionsProvider == null || Modifier.isAbstract(optionsProvider.value().getModifiers())) {
return null;
}
try {
return optionsProvider.value().getDeclaredConstructor().newInstance();
} catch (InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
throw new CucumberException(
String.format("Error instantiating options provider %s", optionsProvider.value()), e);
}
}

private void contributeOptions(CucumberOptions options, List<String> args) {
addName(options, args);
addSnippets(options, args);
addDryRun(options, args);
addMonochrome(options, args);
addTags(options, args);
addPlugins(options, args);
addFeatures(options, args);
addGlue(options, args);
addStrict(options, args);
addJunitOptions(options, args);
}

private void addName(CucumberOptions options, List<String> args) {
for (String name : options.name()) {
args.add("--name");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package cucumber.runtime;

import static org.assertj.core.api.Assertions.assertThat;
import cucumber.api.CucumberOptions;
import cucumber.api.SnippetType;
import org.junit.Test;

public class PropertiesFileCucumberOptionsProviderTest {

private PropertiesFileCucumberOptionsProvider target;

@Test
public void testExtraGlue() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberExtraGlue.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.extraGlue())
.as("verify glue")
.isEqualTo(
new String[] {"io.github.martinschneider.steps3", "io.github.martinschneider.steps4"});
}

@Test
public void testMultipleFeaturePaths() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberMultipleFeaturePaths.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.features())
.as("verify feature paths")
.isEqualTo(new String[] {"src/test/resources/features1", "src/test/resources/features2"});
}

@Test
public void testMultipleGlue() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberMultipleGlue.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.glue())
.as("verify glue")
.isEqualTo(
new String[] {"io.github.martinschneider.steps1", "io.github.martinschneider.steps2"});
}

@Test
public void testSingleFeaturesPath() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberSingleFeaturesPath.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.features())
.as("verify feature path")
.isEqualTo(new String[] {"src/test/resources/features1"});
}

@Test
public void testSingleGlue() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberSingleGlue.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.glue())
.as("verify feature path")
.isEqualTo(new String[] {"io.github.martinschneider.steps1"});
}

@Test
public void testStrict() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberStrict.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.strict()).as("verify strict").isTrue();
}

@Test
public void testNonStrict() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberNonStrict.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.strict()).as("verify strict").isFalse();
}

@Test
public void testSnippetsUnderscore() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberSnippetsUnderscore.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.snippets()).as("verify snippet").isEqualTo(SnippetType.UNDERSCORE);
}

@Test
public void testSnippetsCamelCase() {
System.setProperty(
PropertiesFileCucumberOptionsProvider.PROPERTIES_FILE_PATH_KEY,
PropertiesFileCucumberOptionsProviderTest.class
.getResource("cucumberSnippetsCamelCase.properties")
.getPath());
target = new PropertiesFileCucumberOptionsProvider();
CucumberOptions options = target.getOptions();
assertThat(options.snippets()).as("verify snippet").isEqualTo(SnippetType.CAMELCASE);
}
}
Loading