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

Before and After hooks support added to Rhino #587

Merged
merged 3 commits into from
Sep 12, 2013
Merged
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
Next Next commit
Before and After hooks support added to Rhino
  • Loading branch information
ruifigueira committed Sep 12, 2013
commit 1b0a63eee322bad4b8e8a817ec8d7b12d8a51c8c
5 changes: 5 additions & 0 deletions rhino/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.cobertura</groupId>
<artifactId>cobertura</artifactId>
Expand Down
42 changes: 28 additions & 14 deletions rhino/src/main/java/cucumber/runtime/rhino/RhinoBackend.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
package cucumber.runtime.rhino;

import static cucumber.runtime.io.MultiLoader.packageName;
import gherkin.formatter.model.Step;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.regexp.NativeRegExp;
import org.mozilla.javascript.tools.shell.Global;

import cucumber.runtime.Backend;
import cucumber.runtime.CucumberException;
import cucumber.runtime.Glue;
Expand All @@ -8,18 +22,6 @@
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.snippets.FunctionNameSanitizer;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.formatter.model.Step;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.regexp.NativeRegExp;
import org.mozilla.javascript.tools.shell.Global;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import static cucumber.runtime.io.MultiLoader.packageName;

public class RhinoBackend implements Backend {
private static final String JS_DSL = "/cucumber/runtime/rhino/dsl.js";
Expand Down Expand Up @@ -72,7 +74,7 @@ public String getSnippet(Step step, FunctionNameSanitizer functionNameSanitizer)
return snippetGenerator.getSnippet(step, functionNameSanitizer);
}

private StackTraceElement stepDefLocation() {
private StackTraceElement jsLocation() {
Throwable t = new Throwable();
StackTraceElement[] stackTraceElements = t.getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
Expand All @@ -89,8 +91,20 @@ private StackTraceElement stepDefLocation() {
}

public void addStepDefinition(Global jsStepDefinition, NativeRegExp regexp, NativeFunction bodyFunc, NativeFunction argumentsFromFunc) throws Throwable {
StackTraceElement stepDefLocation = stepDefLocation();
StackTraceElement stepDefLocation = jsLocation();
RhinoStepDefinition stepDefinition = new RhinoStepDefinition(cx, scope, jsStepDefinition, regexp, bodyFunc, stepDefLocation, argumentsFromFunc);
glue.addStepDefinition(stepDefinition);
}

public void addBeforeHook(Function fn, String[] tags, int order, int timeout) {
StackTraceElement stepDefLocation = jsLocation();
RhinoHookDefinition hookDefinition = new RhinoHookDefinition(cx, scope, fn, tags, order, timeout, stepDefLocation);
glue.addBeforeHook(hookDefinition);
}

public void addAfterHook(Function fn, String[] tags, int order, int timeout) {
StackTraceElement stepDefLocation = jsLocation();
RhinoHookDefinition hookDefinition = new RhinoHookDefinition(cx, scope, fn, tags, order, timeout, stepDefLocation);
glue.addAfterHook(hookDefinition);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cucumber.runtime.rhino;

import static java.util.Arrays.asList;
import gherkin.TagExpression;
import gherkin.formatter.model.Tag;

import java.util.Collection;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;

import cucumber.api.Scenario;
import cucumber.runtime.HookDefinition;
import cucumber.runtime.Timeout;

public class RhinoHookDefinition implements HookDefinition {

private Context cx;
private Scriptable scope;
private Function fn;
private final TagExpression tagExpression;
private final int order;
private final int timeoutMillis;
private StackTraceElement location;

public RhinoHookDefinition(Context cx, Scriptable scope, Function fn, String[] tagExpressions, int order, int timeout, StackTraceElement location) {
this.cx = cx;
this.scope = scope;
this.fn = fn;
tagExpression = new TagExpression(asList(tagExpressions));
this.order = order;
this.timeoutMillis = timeout;
this.location = location;
}

Function getFunction() {
return fn;
}

@Override
public String getLocation(boolean detail) {
return location.getFileName() + ":" + location.getLineNumber();
}

@Override
public void execute(Scenario scenario) throws Throwable {
final Object[] args = new Object[] { scenario };
Timeout.timeout(new Timeout.Callback<Object>() {
@Override
public Object call() throws Throwable {
return fn.call(cx, scope, scope, args);
}
}, timeoutMillis);
}

@Override
public boolean matches(Collection<Tag> tags) {
return tagExpression.evaluate(tags);
}

@Override
public int getOrder() {
return order;
}

TagExpression getTagExpression() {
return tagExpression;
}

int getTimeout() {
return timeoutMillis;
}

}
26 changes: 26 additions & 0 deletions rhino/src/main/resources/cucumber/runtime/rhino/dsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,32 @@ var registerStepDefinition = function (regexp, bodyFunc) {
jsBackend.addStepDefinition(this, regexp, bodyFunc, argumentsFromFunc);
};

var registerHookDefinition = function(addHookFn, fn, tags, opts) {
if (tags) {
// if tags is a string, convert it into an array
if (typeof tags === "string") {
tags = [ tags ];
}
} else {
tags = [];
}

tags = tags instanceof Array ? tags : [];
opts = opts || {};

var order = opts.order || 1000;
var timeout = opts.timeout || 0;
addHookFn.call(jsBackend, fn, tags, order, timeout);
}

Before = function (fn, tags, opts) {
registerHookDefinition(jsBackend.addBeforeHook, fn, tags, opts);
};

After = function (fn, tags, opts) {
registerHookDefinition(jsBackend.addAfterHook, fn, tags, opts);
};

var Given = registerStepDefinition;
var When = registerStepDefinition;
var Then = registerStepDefinition;
Expand Down
111 changes: 111 additions & 0 deletions rhino/src/test/java/cucumber/runtime/rhino/RhinoHooksTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package cucumber.runtime.rhino;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doNothing;
import gherkin.formatter.model.Tag;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import cucumber.runtime.HookDefinition;
import cucumber.runtime.RuntimeGlue;
import cucumber.runtime.io.ClasspathResourceLoader;
import cucumber.runtime.io.ResourceLoader;

@RunWith(MockitoJUnitRunner.class)
public class RhinoHooksTest {

private static final String[] NO_TAG = { };
private static final String[] TAG = { "@bellies" };
private static final String[] TAGS = { "@tag1", "@tag2" };

private static final int DEFAULT_ORDER = 1000;
private static final int DEFAULT_TIMEOUT = 0;

private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
private final ResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);

@Mock private RuntimeGlue glue;

private ArgumentCaptor<HookDefinition> beforeHookCaptor;
private ArgumentCaptor<HookDefinition> afterHookCaptor;

@Before
public void startCapturingHooksArguments() {
// given
beforeHookCaptor = ArgumentCaptor.forClass(HookDefinition.class);
afterHookCaptor = ArgumentCaptor.forClass(HookDefinition.class);
doNothing().when(glue).addBeforeHook(beforeHookCaptor.capture());
doNothing().when(glue).addAfterHook(afterHookCaptor.capture());
}

@Test
public void shouldCallAddBeforeAndAfterHook() throws IOException {
// when
RhinoBackend jsBackend = new RhinoBackend(resourceLoader);
jsBackend.loadGlue(glue, Collections.singletonList("cucumber/runtime/rhino_hooks"));
List<HookDefinition> beforeHooks = beforeHookCaptor.getAllValues();
List<HookDefinition> afterHooks = afterHookCaptor.getAllValues();

// then
assertHooks(beforeHooks.get(0), afterHooks.get(0), NO_TAG, DEFAULT_ORDER, DEFAULT_TIMEOUT);
assertHooks(beforeHooks.get(1), afterHooks.get(1), TAG, DEFAULT_ORDER, DEFAULT_TIMEOUT);
assertHooks(beforeHooks.get(2), afterHooks.get(2), TAGS, DEFAULT_ORDER, DEFAULT_TIMEOUT);
assertHooks(beforeHooks.get(3), afterHooks.get(3), TAGS, DEFAULT_ORDER, 300);
assertHooks(beforeHooks.get(4), afterHooks.get(4), TAGS, 10, DEFAULT_TIMEOUT);
assertHooks(beforeHooks.get(5), afterHooks.get(5), TAGS, 20, 600);
}

@Test
public void shouldFailWithTimeout() throws Throwable {
// when
RhinoBackend jsBackend = new RhinoBackend(resourceLoader);
jsBackend.loadGlue(glue, Collections.singletonList("cucumber/runtime/rhino_hooks_timeout"));
List<HookDefinition> beforeHooks = beforeHookCaptor.getAllValues();

try {
beforeHooks.get(0).execute(null);
} catch (Exception e) {
// then
assertThat(e.getCause(), instanceOf(InterruptedException.class));
return;
}

fail(InterruptedException.class.getSimpleName() + " expected");
}

private void assertHooks(HookDefinition beforeHook, HookDefinition afterHook, String[] tags, int order, int timeout) {
assertHook(beforeHook, tags, order, timeout);
assertHook(afterHook, tags, order, timeout);
}

private void assertHook(HookDefinition hookDefinition, String[] tagExprs, int order, int timeout) {
assertThat(hookDefinition, instanceOf(RhinoHookDefinition.class));

RhinoHookDefinition rhinoHook = (RhinoHookDefinition) hookDefinition;

List<Tag> tags = new ArrayList<Tag>();

for (String tagExpr : tagExprs) {
tags.add(new Tag(tagExpr, null));
}

assertTrue(rhinoHook.getTagExpression().evaluate(tags));
assertThat(rhinoHook.getOrder(), equalTo(order));
assertThat(rhinoHook.getTimeout(), equalTo(timeout));
}

}
3 changes: 2 additions & 1 deletion rhino/src/test/java/cucumber/runtime/rhino/RunCukesTest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cucumber.runtime.rhino;

import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
public class RunCukesTest {
}
9 changes: 9 additions & 0 deletions rhino/src/test/resources/cucumber/runtime/rhino/cukes.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ Feature: Cukes
Scenario: in the belly
Given I have 4 "cukes" in my belly
Then there are 4 "cukes" in my belly

@bellies
Scenario: in the belly of Doggie, testing hooks with tags
Given Doggie has 4 "cukes" in his belly
Then there are 4 "cukes" in the belly of Doggie

Scenario: in the belly of Doggie, testing hooks with no tags
Given Doggie has 4 "cukes" in his belly
Then I wake up and there is no Doggie
43 changes: 43 additions & 0 deletions rhino/src/test/resources/cucumber/runtime/rhino/stepdefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,35 @@ function assertEquals(expected, actual) {
}
}

function assertContains(expectedVal, array) {
for (var i = 0; i < array.length; i++) {
if (array[i] == expectedVal) return;
}
throw "Expected array containing " + expectedVal + ", but got " + array;
}

World(function () {

});

// Hooks
Before(function() {
this.belliesMissing = [];
});

After(function() {
delete this.belliesMissing;
});

Before(function() {
this.bellies = {};
}, "@bellies");

After(function() {
delete this.bellies;
}, "@bellies");

// Steps
Given(/^I have (\d+) "([^"]*)" in my belly$/, function (n, what) {
this.n = n;
this.what = what;
Expand All @@ -17,3 +42,21 @@ Then(/^there are (\d+) "([^"]*)" in my belly$/, function (n, what) {
assertEquals(n, this.n);
assertEquals(what, this.what);
});

Given(/^(\w+) has (\d+) "([^"]*)" in his belly$/, function(bellyOwner, n, what) {
if (this.bellies) {
this.bellies[bellyOwner] = { n : n, what : what };
}
else {
this.belliesMissing.push(bellyOwner);
}
});

Then(/^there are (\d+) "([^"]*)" in the belly of (\w+)$/, function(n, what, bellyOwner) {
assertEquals(n, this.bellies[bellyOwner].n);
assertEquals(what, this.bellies[bellyOwner].what);
});

Then(/^I wake up and there is no (\w+)$/, function(bellyOwner) {
assertContains(bellyOwner, this.belliesMissing);
});
12 changes: 12 additions & 0 deletions rhino/src/test/resources/cucumber/runtime/rhino_hooks/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Hooks
var defineHooks = function(fn, tags, opts) {
Before(fn, tags, opts);
After(fn, tags, opts);
};

defineHooks(function() { });
defineHooks(function() { }, "@bellies");
defineHooks(function() { }, [ "@tag1", "@tag2" ]);
defineHooks(function() { }, [ "@tag1", "@tag2" ], { timeout : 300 });
defineHooks(function() { }, [ "@tag1", "@tag2" ], { order : 10 });
defineHooks(function() { }, [ "@tag1", "@tag2" ], { timeout : 600, order : 20 });
Loading