Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.net.URI;
import java.util.List;
import java.util.Set;

@API(status = API.Status.STABLE)
public interface Backend {
Expand All @@ -15,7 +16,20 @@ public interface Backend {
* @param glue Glue that provides the steps to be executed.
* @param gluePaths The locations for the glue to be loaded.
*/
void loadGlue(Glue glue, List<URI> gluePaths);
default void loadGlue(Glue glue, List<URI> gluePaths) {

}

/**
* Invoked once before all features. This is where steps and hooks should be
* loaded.
*
* @param glue Glue that provides the steps to be executed.
* @param glueClassNames The classes of glue to be loaded.
*/
default void loadGlueClasses(Glue glue, Set<String> glueClassNames) {
// TODO: Refactor out a request object.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO:

  • Refactor out a request object.

}

/**
* Invoked before a new scenario starts. Implementations should do any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public final class CommandlineOptions {
public static final String GLUE = "--glue";
public static final String GLUE_SHORT = "-g";

public static final String GLUE_CLASS = "--glue-class";

public static final String TAGS = "--tags";
public static final String TAGS_SHORT = "-t";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static io.cucumber.core.cli.CommandlineOptions.DRY_RUN;
import static io.cucumber.core.cli.CommandlineOptions.DRY_RUN_SHORT;
import static io.cucumber.core.cli.CommandlineOptions.GLUE;
import static io.cucumber.core.cli.CommandlineOptions.GLUE_CLASS;
import static io.cucumber.core.cli.CommandlineOptions.GLUE_SHORT;
import static io.cucumber.core.cli.CommandlineOptions.HELP;
import static io.cucumber.core.cli.CommandlineOptions.HELP_SHORT;
Expand Down Expand Up @@ -125,6 +126,9 @@ private RuntimeOptionsBuilder parse(List<String> args) {
String gluePath = removeArgFor(arg, args);
URI parse = GluePath.parse(gluePath);
parsedOptions.addGlue(parse);
} else if (arg.equals(GLUE_CLASS)) {
String glueClassName = removeArgFor(arg, args);
parsedOptions.addGlueClass(glueClassName);
} else if (arg.equals(TAGS) || arg.equals(TAGS_SHORT)) {
parsedOptions.addTagFilter(TagExpressionParser.parse(removeArgFor(arg, args)));
} else if (arg.equals(PUBLISH)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ public final class Constants {
*/
public static final String GLUE_PROPERTY_NAME = "cucumber.glue";

/**
* Property name to set the glue classes: {@value}
* <p>
* A comma separated list fully qualified class names e.g.:
* {@code com.example.app.Stepdefinitions}.
*
* @see io.cucumber.core.feature.GluePath
*/
public static final String GLUE_CLASSES_PROPERTY_NAME = "cucumber.glue-classes";

/**
* Property name used to select a specific object factory implementation:
* {@value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static io.cucumber.core.options.Constants.FEATURES_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.FILTER_NAME_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.FILTER_TAGS_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.GLUE_CLASSES_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.OBJECT_FACTORY_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.OPTIONS_PROPERTY_NAME;
Expand Down Expand Up @@ -88,6 +89,11 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) {
splitAndMap(GluePath::parse),
builder::addGlue);

parseAll(properties,
GLUE_CLASSES_PROPERTY_NAME,
splitAndMap(identity()),
builder::addGlueClass);

parse(properties,
OBJECT_FACTORY_PROPERTY_NAME,
ObjectFactoryParser::parseObjectFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -30,6 +31,7 @@
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;

public final class RuntimeOptions implements
io.cucumber.core.feature.Options,
Expand All @@ -40,6 +42,7 @@ public final class RuntimeOptions implements
io.cucumber.core.eventbus.Options {

private final List<URI> glue = new ArrayList<>();
private final Set<String> glueClasses = new HashSet<>();
private final List<Expression> tagExpressions = new ArrayList<>();
private final List<Pattern> nameFilters = new ArrayList<>();
private final List<FeatureWithLines> featurePaths = new ArrayList<>();
Expand Down Expand Up @@ -76,7 +79,7 @@ void addDefaultSummaryPrinter() {
}

void addDefaultGlueIfAbsent() {
if (glue.isEmpty()) {
if (glue.isEmpty() && glueClasses.isEmpty()) {
glue.add(rootPackageUri());
}
}
Expand Down Expand Up @@ -150,6 +153,11 @@ public List<URI> getGlue() {
return unmodifiableList(glue);
}

@Override
public Set<String> getGlueClasses() {
return unmodifiableSet(glueClasses);
}

@Override
public boolean isDryRun() {
return dryRun;
Expand Down Expand Up @@ -191,6 +199,11 @@ void setGlue(List<URI> parsedGlue) {
glue.addAll(parsedGlue);
}

void setGlueClasses(Set<String> parsedGlue) {
glueClasses.clear();
glueClasses.addAll(parsedGlue);
}

@Override
public List<URI> getFeaturePaths() {
return unmodifiableList(featurePaths.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
Expand All @@ -22,6 +23,7 @@ public final class RuntimeOptionsBuilder {
private final List<Pattern> parsedNameFilters = new ArrayList<>();
private final List<FeatureWithLines> parsedFeaturePaths = new ArrayList<>();
private final List<URI> parsedGlue = new ArrayList<>();
private final Set<String> parsedGlueClasses = new HashSet<>();
private final List<Options.Plugin> plugins = new ArrayList<>();
private List<FeatureWithLines> parsedRerunPaths = null;
private Integer parsedThreads = null;
Expand Down Expand Up @@ -59,6 +61,11 @@ public RuntimeOptionsBuilder addGlue(URI glue) {
return this;
}

public RuntimeOptionsBuilder addGlueClass(String glueClassName) {
parsedGlueClasses.add(glueClassName);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO:

  • Also support classes.

How to deal with class loading?

return this;
}

public RuntimeOptionsBuilder addNameFilter(Pattern pattern) {
this.parsedNameFilters.add(pattern);
return this;
Expand Down Expand Up @@ -128,6 +135,10 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) {
runtimeOptions.setGlue(this.parsedGlue);
}

if (!this.parsedGlueClasses.isEmpty()) {
runtimeOptions.setGlueClasses(this.parsedGlueClasses);
}

runtimeOptions.addPlugins(this.plugins);

if (parsedObjectFactoryClass != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private Function<Path, Consumer<Path>> processClassFiles(
};
}

private Optional<Class<?>> safelyLoadClass(String fqn) {
public Optional<Class<?>> safelyLoadClass(String fqn) {
try {
return Optional.ofNullable(getClassLoader().loadClass(fqn));
} catch (ClassNotFoundException | NoClassDefFoundError e) {
Expand All @@ -111,6 +111,14 @@ private Optional<Class<?>> safelyLoadClass(String fqn) {
return Optional.empty();
}

public Class<?> loadClass(String fqn) {
try {
return getClassLoader().loadClass(fqn);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
throw new IllegalArgumentException("Could not to load class '" + fqn + "'", e);
}
}

public List<Class<?>> scanForClassesInPackage(String packageName) {
return scanForClassesInPackage(packageName, NULL_FILTER);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import io.cucumber.core.snippets.SnippetType;

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public interface Options {

Expand All @@ -19,4 +21,7 @@ public interface Options {

Class<? extends UuidGenerator> getUuidGeneratorClass();

default Set<String> getGlueClasses() {
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.cucumber.plugin.event.SnippetsSuggestedEvent;
import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -50,11 +49,11 @@ public Runner(
this.backends = backends;
this.glue = new CachingGlue(bus);
this.objectFactory = objectFactory;
List<URI> gluePaths = runnerOptions.getGlue();
log.debug(() -> "Loading glue from " + gluePaths);
log.debug(() -> "Loading glue from " + runnerOptions.getGlue());
for (Backend backend : backends) {
log.debug(() -> "Loading glue for backend " + backend.getClass().getName());
backend.loadGlue(this.glue, gluePaths);
backend.loadGlue(this.glue, runnerOptions.getGlue());
backend.loadGlueClasses(this.glue, runnerOptions.getGlueClasses());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ Options:

-g, --glue PATH Package to load glue code (step
definitions, hooks and plugins) from
e.g: com.example.app. When not
provided Cucumber will search the
classpath.
e.g: com.example.app.
When neither glue, nor glue classes
are provided Cucumber will search
the classpath.

--glue-class FQCN A fully qualified glue class name
e.g: com.example.app.StepDefinitions.
When neither glue, nor glue classes
are provided Cucumber will search
the classpath.

-p, --plugin PLUGIN[:[PATH|[URI [OPTIONS]]]
Register a plugin.
Expand Down Expand Up @@ -147,6 +154,9 @@ cucumber.filter.tags= # a cucumber tag expression.
cucumber.glue= # comma separated package names.
# example: com.example.glue

cucumber.glue-classes= # comma separated class names.
# example: com.example.glue.StepDefinitions

cucumber.plugin= # comma separated plugin strings.
# example: pretty, json:path/to/report.json

Expand Down
33 changes: 27 additions & 6 deletions cucumber-java/src/main/java/io/cucumber/java/JavaBackend.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static io.cucumber.core.resource.ClasspathSupport.CLASSPATH_SCHEME;
import static io.cucumber.java.MethodScanner.scan;
Expand All @@ -30,18 +32,37 @@ final class JavaBackend implements Backend {

@Override
public void loadGlue(Glue glue, List<URI> gluePaths) {
loadGlueClassesImpl(glue, scanForClasses(gluePaths));
}

@Override
public void loadGlueClasses(Glue glue, Set<String> glueClassNames) {
Set<Class<?>> glueClasses = glueClassNames.stream()
.map(classFinder::loadClass)
.collect(Collectors.toSet());

loadGlueClassesImpl(glue, glueClasses);
}

private void loadGlueClassesImpl(Glue glue, Set<Class<?>> glueClasses) {
GlueAdaptor glueAdaptor = new GlueAdaptor(lookup, glue);
glueClasses.forEach(aGlueClass -> processClass(aGlueClass, glueAdaptor));
}

gluePaths.stream()
private Set<Class<?>> scanForClasses(List<URI> gluePaths) {
return gluePaths.stream()
.filter(gluePath -> CLASSPATH_SCHEME.equals(gluePath.getScheme()))
.map(ClasspathSupport::packageName)
.map(classFinder::scanForClassesInPackage)
.flatMap(Collection::stream)
.distinct()
.forEach(aGlueClass -> scan(aGlueClass, (method, annotation) -> {
container.addClass(method.getDeclaringClass());
glueAdaptor.addDefinition(method, annotation);
}));
.collect(Collectors.toSet());
}

private void processClass(Class<?> aGlueClass, GlueAdaptor glueAdaptor) {
scan(aGlueClass, (method, annotation) -> {
container.addClass(method.getDeclaringClass());
glueAdaptor.addDefinition(method, annotation);
});
}

@Override
Expand Down
Loading
Loading