Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Before and After hooks support added to Rhino #587

Merged
merged 3 commits into from

3 participants

@ruifigueira

Hi,

I just added Before and After hooks support to the rhino backend.
Rhino scripts can now call two new functions, Before and After, whitch receive:

  • a function
  • a optional string or array of strings with tag expressions for that hook
  • a optional object with timeout and/or order properties

Example:

Before(function() {
  // do something
}, [ "@mytag" ], { timeout : 100, order : 1 });

After(function() {
  // do something
}, [ "@mytag" ]);
@aslakhellesoy

Awesome! Can you fix the typo that caused Travis to fail the build?

@ruifigueira

Oops, sorry about that. I just fixed that. Also, I replaced tab indentation with space indentation

@aslakhellesoy

Thanks!

My impression (from mailing list and issue tracker activity) is that not many people are using Cucumber-Rhino. It's very cool to see that there is at least one person using it! :-)

I'd love to know more...

@aslakhellesoy aslakhellesoy merged commit 3841f93 into cucumber:master
@ruifigueira

Well, I just start using cucumber-jvm two days ago. Cucumber-rhino caught my atention because I'm using Rhino as a scripting language in another project of mine, and I was surprised to see that it was soooo easy to extend it! Cucumber-JVM is a great piece of software :)
I needed those Before and After hooks so that I could start and stop a Selenium WebDriver, and it is working like a charm now.
Hopefully, you'll see more contributions from me to cucumber-rhino, because I see great value there!

Thanks!

@jorgeazevedo

@ruifigueira using a Selenium WebDriver with cucumber-rhino is exactly what I'd like to do! Since I'm just getting started this is proving really complicated. Is it possible you share your code? Maybe we could setup a hello world type project for rhino/selenium

Sorry to hijack this pull request discussion, by the way, but I can't find a way to contact you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
5 rhino/pom.xml
@@ -44,6 +44,11 @@
<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>
<scope>test</scope>
View
44 rhino/src/main/java/cucumber/runtime/rhino/RhinoBackend.java
@@ -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;
@@ -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";
@@ -56,7 +58,7 @@ public void loadGlue(Glue glue, List<String> gluePaths) {
@Override
public void setUnreportedStepExecutor(UnreportedStepExecutor executor) {
- //Not used yet
+ // Not used yet
}
@Override
@@ -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) {
@@ -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);
+ }
}
View
75 rhino/src/main/java/cucumber/runtime/rhino/RhinoHookDefinition.java
@@ -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;
+ }
+
+}
View
32 rhino/src/main/resources/cucumber/runtime/rhino/dsl.js
@@ -1,5 +1,5 @@
-var registerStepDefinition = function (regexp, bodyFunc) {
- var argumentsFromFunc = function (stepName) {
+var registerStepDefinition = function(regexp, bodyFunc) {
+ var argumentsFromFunc = function(stepName) {
var match = regexp.exec(stepName);
if (match) {
var arguments = new Packages.java.util.ArrayList();
@@ -17,11 +17,37 @@ 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;
-var World = function (func) {
+var World = function(func) {
// TODO: do this properly
func();
};
View
112 rhino/src/test/java/cucumber/runtime/rhino/RhinoHooksTest.java
@@ -0,0 +1,112 @@
+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));
+ }
+
+}
View
3  rhino/src/test/java/cucumber/runtime/rhino/RunCukesTest.java
@@ -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 {
}
View
9 rhino/src/test/resources/cucumber/runtime/rhino/cukes.feature
@@ -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
View
51 rhino/src/test/resources/cucumber/runtime/rhino/stepdefs.js
@@ -4,16 +4,61 @@ function assertEquals(expected, actual) {
}
}
-World(function () {
+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;
});
-Given(/^I have (\d+) "([^"]*)" in my belly$/, function (n, what) {
+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;
});
-Then(/^there are (\d+) "([^"]*)" in my belly$/, function (n, what) {
+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);
+});
View
12 rhino/src/test/resources/cucumber/runtime/rhino_hooks/hooks.js
@@ -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 });
View
5 rhino/src/test/resources/cucumber/runtime/rhino_hooks_timeout/hooks_timeout.js
@@ -0,0 +1,5 @@
+// Hooks
+Before(function() {
+ // this must fail with timeout
+ Packages.java.lang.Thread.sleep(200);
+}, [], { timeout : 100 });
Something went wrong with that request. Please try again.