Skip to content

Commit

Permalink
Pass in ClassLoader from the outside. This is necessary in order to c…
Browse files Browse the repository at this point in the history
…onstruct a working Runtime from a different environment, like Jython. See #151
  • Loading branch information
aslakhellesoy committed Jan 21, 2012
1 parent abc1934 commit a7aea9d
Show file tree
Hide file tree
Showing 31 changed files with 152 additions and 66 deletions.
13 changes: 13 additions & 0 deletions core/src/main/java/cucumber/cli/DefaultRuntimeFactory.java
@@ -0,0 +1,13 @@
package cucumber.cli;

import cucumber.io.ResourceLoader;
import cucumber.runtime.Runtime;

import java.util.List;

public class DefaultRuntimeFactory implements RuntimeFactory {
@Override
public cucumber.runtime.Runtime createRuntime(ResourceLoader resourceLoader, List<String> gluePaths, ClassLoader classLoader, boolean dryRun) {
return new Runtime(resourceLoader, gluePaths, classLoader, dryRun);
}
}
8 changes: 6 additions & 2 deletions core/src/main/java/cucumber/cli/Main.java
Expand Up @@ -21,6 +21,10 @@ public class Main {
static final String VERSION = ResourceBundle.getBundle("cucumber.version").getString("cucumber-jvm.version");

public static void main(String[] argv) throws Throwable {
run(argv, Thread.currentThread().getContextClassLoader(), new DefaultRuntimeFactory());
}

public static void run(String[] argv, ClassLoader classLoader, RuntimeFactory runtimeFactory) throws IOException {
List<String> featurePaths = new ArrayList<String>();
List<String> gluePaths = new ArrayList<String>();
List<Object> filters = new ArrayList<Object>();
Expand All @@ -30,7 +34,7 @@ public static void main(String[] argv) throws Throwable {
boolean isDryRun = false;

FormatterFactory formatterFactory = new FormatterFactory();
MultiFormatter multiFormatter = new MultiFormatter();
MultiFormatter multiFormatter = new MultiFormatter(classLoader);

while (!args.isEmpty()) {
String arg = args.remove(0);
Expand Down Expand Up @@ -71,7 +75,7 @@ public static void main(String[] argv) throws Throwable {
System.exit(1);
}

Runtime runtime = new Runtime(gluePaths, new FileResourceLoader(), isDryRun);
Runtime runtime = runtimeFactory.createRuntime(new FileResourceLoader(), gluePaths, classLoader, isDryRun);

if (dotCucumber != null) {
writeDotCucumber(featurePaths, dotCucumber, runtime);
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/cucumber/cli/RuntimeFactory.java
@@ -0,0 +1,10 @@
package cucumber.cli;

import cucumber.io.ResourceLoader;
import cucumber.runtime.Runtime;

import java.util.List;

public interface RuntimeFactory {
Runtime createRuntime(ResourceLoader fileResourceLoader, List<String> gluePaths, ClassLoader classLoader, boolean dryRun);
}
11 changes: 8 additions & 3 deletions core/src/main/java/cucumber/formatter/MultiFormatter.java
Expand Up @@ -10,7 +10,12 @@
import java.util.List;

public class MultiFormatter {
private List<Formatter> formatters = new ArrayList<Formatter>();
private final List<Formatter> formatters = new ArrayList<Formatter>();
private final ClassLoader classLoader;

public MultiFormatter(ClassLoader classLoader) {
this.classLoader = classLoader;
}

public void add(Formatter formatter) {
formatters.add(formatter);
Expand All @@ -21,7 +26,7 @@ public boolean isEmpty() {
}

public Formatter formatterProxy() {
return (Formatter) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{Formatter.class}, new InvocationHandler() {
return (Formatter) Proxy.newProxyInstance(classLoader, new Class<?>[]{Formatter.class}, new InvocationHandler() {
@Override
public Object invoke(Object target, Method method, Object[] args) throws Throwable {
for (Formatter formatter : formatters) {
Expand All @@ -33,7 +38,7 @@ public Object invoke(Object target, Method method, Object[] args) throws Throwab
}

public Reporter reporterProxy() {
return (Reporter) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{Reporter.class}, new InvocationHandler() {
return (Reporter) Proxy.newProxyInstance(classLoader, new Class<?>[]{Reporter.class}, new InvocationHandler() {
@Override
public Object invoke(Object target, Method method, Object[] args) throws Throwable {
for (Formatter formatter : formatters) {
Expand Down
18 changes: 10 additions & 8 deletions core/src/main/java/cucumber/io/ClasspathResourceLoader.java
Expand Up @@ -9,9 +9,15 @@
import java.util.HashSet;

public class ClasspathResourceLoader implements ResourceLoader {
private final ClassLoader classLoader;

public ClasspathResourceLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
public Iterable<Resource> resources(String path, String suffix) {
return new ClasspathIterable(cl(), path, suffix);
return new ClasspathIterable(classLoader, path, suffix);
}

public Collection<Class<? extends Annotation>> getAnnotations(String packagePath) {
Expand All @@ -22,7 +28,7 @@ public <T> Collection<Class<? extends T>> getDescendants(Class<T> parentType, St
Collection<Class<? extends T>> result = new HashSet<Class<? extends T>>();
for (Resource classResource : resources(packagePath, ".class")) {
String className = classResource.getClassName();
Class<?> clazz = loadClass(className);
Class<?> clazz = loadClass(className, classLoader);
if (clazz != null && !parentType.equals(clazz) && parentType.isAssignableFrom(clazz)) {
result.add(clazz.asSubclass(parentType));
}
Expand Down Expand Up @@ -51,9 +57,9 @@ public <T> Collection<? extends T> instantiateSubclasses(Class<T> parentType, St
return result;
}

private Class<?> loadClass(String className) {
private Class<?> loadClass(String className, ClassLoader classLoader) {
try {
return cl().loadClass(className);
return classLoader.loadClass(className);
} catch (ClassNotFoundException ignore) {
return null;
} catch (NoClassDefFoundError ignore) {
Expand All @@ -74,8 +80,4 @@ private <T> T newInstance(Class[] constructorParams, Object[] constructorArgs, C
throw new CucumberException(e);
}
}

private ClassLoader cl() {
return Thread.currentThread().getContextClassLoader();
}
}
21 changes: 11 additions & 10 deletions core/src/main/java/cucumber/runtime/Runtime.java
Expand Up @@ -2,6 +2,7 @@

import cucumber.io.ClasspathResourceLoader;
import cucumber.io.ResourceLoader;
import cucumber.runtime.converters.LocalizedXStreams;
import cucumber.runtime.model.CucumberFeature;
import cucumber.runtime.model.CucumberTagStatement;
import gherkin.formatter.Formatter;
Expand All @@ -28,7 +29,7 @@ public class Runtime implements UnreportedStepExecutor {
private static final byte ERRORS = 0x1;

private final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
private final Glue glue = new RuntimeGlue(undefinedStepsTracker);
private final Glue glue;

private final List<Throwable> errors = new ArrayList<Throwable>();
private final Collection<? extends Backend> backends;
Expand All @@ -40,31 +41,31 @@ public class Runtime implements UnreportedStepExecutor {
private boolean skipNextStep = false;
private ScenarioResultImpl scenarioResult = null;


public Runtime(List<String> gluePaths, ResourceLoader resourceLoader) {
this(gluePaths, resourceLoader, false);
public Runtime(ResourceLoader resourceLoader, List<String> gluePaths, ClassLoader classLoader) {
this(resourceLoader, gluePaths, classLoader, false);
}

public Runtime(List<String> gluePaths, ResourceLoader resourceLoader, boolean isDryRun) {
this(gluePaths, resourceLoader, loadBackends(resourceLoader), isDryRun);
public Runtime(ResourceLoader resourceLoader, List<String> gluePaths, ClassLoader classLoader, boolean isDryRun) {
this(resourceLoader, gluePaths, classLoader, loadBackends(resourceLoader, classLoader), isDryRun);
}

public Runtime(List<String> gluePaths, ResourceLoader resourceLoader, Collection<? extends Backend> backends, boolean isDryRun) {
public Runtime(ResourceLoader resourceLoader, List<String> gluePaths, ClassLoader classLoader, Collection<? extends Backend> backends, boolean isDryRun) {
if(backends.isEmpty()) {
throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH.");
}
this.backends = backends;
this.resourceLoader = resourceLoader;
this.isDryRun = isDryRun;
glue = new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader));

for (Backend backend : backends) {
backend.loadGlue(glue, gluePaths);
backend.setUnreportedStepExecutor(this);
}
}

private static Collection<? extends Backend> loadBackends(ResourceLoader resourceLoader) {
return new ClasspathResourceLoader().instantiateSubclasses(Backend.class, "cucumber/runtime", new Class[]{ResourceLoader.class}, new Object[]{resourceLoader});
private static Collection<? extends Backend> loadBackends(ResourceLoader resourceLoader, ClassLoader classLoader) {
return new ClasspathResourceLoader(classLoader).instantiateSubclasses(Backend.class, "cucumber/runtime", new Class[]{ResourceLoader.class}, new Object[]{resourceLoader});
}

public void addError(Throwable error) {
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/cucumber/runtime/RuntimeGlue.java
Expand Up @@ -23,19 +23,19 @@

public class RuntimeGlue implements Glue {
private static final List<Object> NO_FILTERS = emptyList();
private final LocalizedXStreams localizedXStreams = new LocalizedXStreams();

private final List<StepDefinition> stepDefinitions = new ArrayList<StepDefinition>();
private final List<HookDefinition> beforeHooks = new ArrayList<HookDefinition>();
private final List<HookDefinition> afterHooks = new ArrayList<HookDefinition>();

private final UndefinedStepsTracker tracker;
private final LocalizedXStreams localizedXStreams;

public RuntimeGlue(UndefinedStepsTracker tracker) {
public RuntimeGlue(UndefinedStepsTracker tracker, LocalizedXStreams localizedXStreams) {
this.tracker = tracker;
this.localizedXStreams = localizedXStreams;
}


@Override
public void addStepDefinition(StepDefinition stepDefinition) {
stepDefinitions.add(stepDefinition);
Expand Down
Expand Up @@ -10,6 +10,11 @@

public class LocalizedXStreams {
private final Map<Locale, XStream> xStreams = new HashMap<Locale, XStream>();
private final ClassLoader classLoader;

public LocalizedXStreams(ClassLoader classLoader) {
this.classLoader = classLoader;
}

public XStream get(Locale locale) {
XStream xStream = xStreams.get(locale);
Expand All @@ -24,7 +29,7 @@ private XStream newXStream(Locale locale) {
DefaultConverterLookup lookup = new DefaultConverterLookup();

// XStream's registers all the default converters.
XStream xStream = new XStream(null, null, Thread.currentThread().getContextClassLoader(), null, lookup, lookup);
XStream xStream = new XStream(null, null, classLoader, null, lookup, lookup);
xStream.autodetectAnnotations(true);

// Override with our own Locale-aware converters.
Expand Down
Expand Up @@ -23,7 +23,6 @@ public class CucumberFeature {
private Locale locale;
private CucumberScenarioOutline currentScenarioOutline;

// TODO: Use an iterable here, wrapping around
public static List<CucumberFeature> load(ResourceLoader resourceLoader, List<String> featurePaths, final List<Object> filters) {
final List<CucumberFeature> cucumberFeatures = new ArrayList<CucumberFeature>();
final FeatureBuilder builder = new FeatureBuilder(cucumberFeatures);
Expand Down
5 changes: 3 additions & 2 deletions core/src/test/java/cucumber/formatter/HTMLFormatterTest.java
Expand Up @@ -25,10 +25,11 @@ public class HTMLFormatterTest {
public void writes_proper_html() throws IOException {
File dir = createTempDirectory();
HTMLFormatter f = new HTMLFormatter(dir);
ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
List<CucumberFeature> features = CucumberFeature.load(resourceLoader, asList("cucumber/formatter/HTMLFormatterTest.feature"), emptyList());
List<String> gluePaths = emptyList();
Runtime runtime = new Runtime(gluePaths, resourceLoader, asList(mock(Backend.class)), false);
Runtime runtime = new Runtime(resourceLoader, gluePaths, classLoader, asList(mock(Backend.class)), false);
runtime.run(features.get(0), f, f);
f.done();

Expand Down
2 changes: 1 addition & 1 deletion core/src/test/java/cucumber/io/ResourceLoaderTest.java
Expand Up @@ -27,7 +27,7 @@ public void loads_resource_from_filesystem_file() {

@Test
public void loads_resources_from_jar_on_classpath() throws IOException {
Iterable<Resource> files = new ClasspathResourceLoader().resources("cucumber", ".properties");
Iterable<Resource> files = new ClasspathResourceLoader(Thread.currentThread().getContextClassLoader()).resources("cucumber", ".properties");
assertEquals(4, toList(files).size());
}

Expand Down
3 changes: 2 additions & 1 deletion core/src/test/java/cucumber/runtime/BackgroundTest.java
Expand Up @@ -16,7 +16,8 @@
public class BackgroundTest {
@Test
public void should_run_background() throws IOException {
Runtime runtime = new Runtime(new ArrayList<String>(), new ClasspathResourceLoader(), asList(mock(Backend.class)), false);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), new ArrayList<String>(), classLoader, asList(mock(Backend.class)), false);
CucumberFeature feature = feature("test.feature", "" +
"Feature:\n" +
" Background:\n" +
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/java/cucumber/runtime/HookOrderTest.java
Expand Up @@ -25,7 +25,8 @@ public class HookOrderTest {

@Before
public void buildMockWorld() {
runtime = new Runtime(new ArrayList<String>(), mock(ResourceLoader.class), asList(mock(Backend.class)), false);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
runtime = new Runtime(mock(ResourceLoader.class), new ArrayList<String>(), classLoader, asList(mock(Backend.class)), false);
glue = runtime.getGlue();
}

Expand Down
3 changes: 2 additions & 1 deletion core/src/test/java/cucumber/runtime/HookTest.java
Expand Up @@ -46,7 +46,8 @@ public void after_hooks_execute_before_objects_are_disposed() throws Throwable {

CucumberScenario scenario = new CucumberScenario(feature, null, gherkinScenario);

Runtime runtime = new Runtime(CODE_PATHS, new ClasspathResourceLoader(), asList(backend), false);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), CODE_PATHS, classLoader, asList(backend), false);
runtime.getGlue().addAfterHook(hook);

scenario.run(mock(Formatter.class), mock(Reporter.class), runtime);
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/java/cucumber/runtime/RuntimeTest.java
Expand Up @@ -25,7 +25,8 @@ public void runs_feature_with_json_formatter() throws Exception {
StringBuilder out = new StringBuilder();
JSONFormatter jsonFormatter = new JSONFormatter(out);
List<Backend> backends = asList(mock(Backend.class));
new Runtime(Collections.<String>emptyList(), new ClasspathResourceLoader(), backends, true).run(feature, jsonFormatter, jsonFormatter);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
new Runtime(new ClasspathResourceLoader(classLoader), Collections.<String>emptyList(), classLoader, backends, true).run(feature, jsonFormatter, jsonFormatter);
jsonFormatter.done();
String expected = "[{\"id\":\"feature-name\",\"description\":\"\",\"name\":\"feature name\",\"keyword\":\"Feature\",\"line\":1,\"elements\":[{\"description\":\"\",\"name\":\"background name\",\"keyword\":\"Background\",\"line\":2,\"steps\":[{\"result\":{\"status\":\"undefined\"},\"name\":\"b\",\"keyword\":\"Given \",\"line\":3,\"match\":{}}],\"type\":\"background\"},{\"id\":\"feature-name;scenario-name\",\"description\":\"\",\"name\":\"scenario name\",\"keyword\":\"Scenario\",\"line\":4,\"steps\":[{\"result\":{\"status\":\"undefined\"},\"name\":\"s\",\"keyword\":\"When \",\"line\":5,\"match\":{}}],\"type\":\"scenario\"}],\"uri\":\"test.feature\"}]";
assertEquals(expected, out.toString());
Expand Down
Expand Up @@ -18,6 +18,8 @@
import static org.mockito.Mockito.when;

public class StepDefinitionMatchTest {
private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

@Test
public void converts_numbers() throws Throwable {
StepDefinition stepDefinition = mock(StepDefinition.class);
Expand All @@ -29,7 +31,7 @@ public void converts_numbers() throws Throwable {
when(stepWithoutDocStringOrTable.getDocString()).thenReturn(null);
when(stepWithoutDocStringOrTable.getRows()).thenReturn(null);

StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(Arrays.asList(new Argument(0, "5")), stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams());
StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(Arrays.asList(new Argument(0, "5")), stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams(classLoader));
stepDefinitionMatch.runStep(Locale.ENGLISH);
verify(stepDefinition).execute(Locale.ENGLISH, new Object[]{5});
}
Expand All @@ -46,7 +48,7 @@ public void can_have_doc_string_as_only_argument() throws Throwable {
when(stepWithDocString.getDocString()).thenReturn(docString);
when(stepWithDocString.getRows()).thenReturn(null);

StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(new ArrayList<Argument>(), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams());
StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(new ArrayList<Argument>(), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams(classLoader));
stepDefinitionMatch.runStep(Locale.ENGLISH);
verify(stepDefinition).execute(Locale.ENGLISH, new Object[]{"HELLO"});
}
Expand All @@ -63,7 +65,7 @@ public void can_have_doc_string_as_last_argument_among_many() throws Throwable {
when(stepWithDocString.getDocString()).thenReturn(docString);
when(stepWithDocString.getRows()).thenReturn(null);

StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(Arrays.asList(new Argument(0, "5")), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams());
StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(Arrays.asList(new Argument(0, "5")), stepDefinition, "some.feature", stepWithDocString, new LocalizedXStreams(classLoader));
stepDefinitionMatch.runStep(Locale.ENGLISH);
verify(stepDefinition).execute(Locale.ENGLISH, new Object[]{5, "HELLO"});
}
Expand Down
Expand Up @@ -13,7 +13,8 @@
public class ConvertersTest {
@Test
public void shouldTransformToTheRightType() {
LocalizedXStreams transformers = new LocalizedXStreams();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
LocalizedXStreams transformers = new LocalizedXStreams(classLoader);

ConverterLookup en = transformers.get(Locale.ENGLISH).getConverterLookup();
assertTrue((Boolean) ((SingleValueConverter) en.lookupConverterForType(Boolean.class)).fromString("true"));
Expand Down
3 changes: 2 additions & 1 deletion core/src/test/java/cucumber/table/DataTableTest.java
Expand Up @@ -23,7 +23,8 @@ public void initSimpleTable() {
List<DataTableRow> simpleRows = new ArrayList<DataTableRow>();
simpleRows.add(new DataTableRow(new ArrayList<Comment>(), asList("one", "four", "seven"), 1));
simpleRows.add(new DataTableRow(new ArrayList<Comment>(), asList("4444", "55555", "666666"), 2));
XStream xStream = new LocalizedXStreams().get(Locale.UK);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
XStream xStream = new LocalizedXStreams(classLoader).get(Locale.UK);
simpleTable = new DataTable(simpleRows, new TableConverter(xStream));
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/test/java/cucumber/table/TableConverterTest.java
Expand Up @@ -30,7 +30,7 @@ public class TableConverterTest {

@Before
public void createTableConverterWithDateFormat() {
XStream xStream = new LocalizedXStreams().get(Locale.UK);
XStream xStream = new LocalizedXStreams(Thread.currentThread().getContextClassLoader()).get(Locale.UK);
tc = new TableConverter(xStream);
SingleValueConverterWrapperExt converterWrapper = (SingleValueConverterWrapperExt) xStream.getConverterLookup().lookupConverterForType(Date.class);
DateConverter dateConverter = (DateConverter) converterWrapper.getConverter();
Expand Down

0 comments on commit a7aea9d

Please sign in to comment.