Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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 @@ -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,12 @@ public RuntimeOptionsBuilder addGlue(URI glue) {
return this;
}

public RuntimeOptionsBuilder addGlueClass(String glueClassName) {
// TODO: Support Class<?> ?
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 +136,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 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
36 changes: 30 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,10 @@
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
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 +33,39 @@ 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::safelyLoadClass)
.filter(Optional::isPresent)
.map(Optional::get)
.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
46 changes: 36 additions & 10 deletions cucumber-java8/src/main/java/io/cucumber/java8/Java8Backend.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static io.cucumber.java8.LambdaGlueRegistry.CLOSED;

Expand All @@ -33,20 +36,43 @@ final class Java8Backend 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::safelyLoadClass)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());

loadGlueClassesImpl(glue, glueClasses);
}

private void loadGlueClassesImpl(Glue glue, Set<Class<?>> glueClasses) {
this.glue = new ClosureAwareGlueRegistry(glue);
// Scan for Java8 style glue (lambdas)
gluePaths.stream()
glueClasses.stream()
.filter(aClass -> !LambdaGlue.class.equals(aClass) && LambdaGlue.class.isAssignableFrom(aClass))
.map(aClass -> (Class<? extends LambdaGlue>) aClass.asSubclass(LambdaGlue.class))
.filter(glueClass -> !glueClass.isInterface())
.filter(glueClass -> glueClass.getConstructors().length > 0)
.forEach(this::processClass);
}

private Set<Class<?>> scanForClasses(List<URI> gluePaths) {
return gluePaths.stream()
.filter(gluePath -> ClasspathSupport.CLASSPATH_SCHEME.equals(gluePath.getScheme()))
.map(ClasspathSupport::packageName)
.map(basePackageName -> classFinder.scanForSubClassesInPackage(basePackageName, LambdaGlue.class))
// Scan for Java8 style glue (lambdas)
.map(classFinder::scanForClassesInPackage)
.flatMap(Collection::stream)
.filter(glueClass -> !glueClass.isInterface())
.filter(glueClass -> glueClass.getConstructors().length > 0)
.distinct()
.forEach(glueClass -> {
container.addClass(glueClass);
lambdaGlueClasses.add(glueClass);
});
.collect(Collectors.toSet());
}

private void processClass(Class<? extends LambdaGlue> glueClass) {
container.addClass(glueClass);
lambdaGlueClasses.add(glueClass);
}

@Override
Expand Down
Loading