From b1d8b181be49a1e070387207e5055de2016841fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Mon, 18 Jun 2012 16:15:15 +0100 Subject: [PATCH] Step definitions and hooks can now specify a timeout (milliseconds) after which a is thrown if the stepdef/hook has not completed. Closes #343 --- History.md | 2 + .../main/java/cucumber/runtime/Timeout.java | 37 +++++++++++ .../src/main/java/cucumber/runtime/Utils.java | 43 ++++-------- .../java/cucumber/runtime/TimeoutTest.java | 66 +++++++++++++++++++ .../test/java/cucumber/runtime/UtilsTest.java | 26 -------- .../src/main/code_generator/I18n.groovy.txt | 6 +- .../runtime/groovy/GroovyBackend.java | 33 +++++++--- .../runtime/groovy/GroovyHookDefinition.java | 19 ++++-- .../runtime/groovy/GroovyStepDefinition.java | 17 +++-- .../java/cucumber/runtime/groovy/Hooks.java | 34 ++++++---- java/src/main/code_generator/I18n.java.txt | 5 ++ .../main/java/cucumber/annotation/After.java | 5 ++ .../main/java/cucumber/annotation/Before.java | 5 ++ .../cucumber/runtime/java/JavaBackend.java | 26 +++++--- .../runtime/java/JavaHookDefinition.java | 6 +- .../runtime/java/JavaStepDefinition.java | 6 +- 16 files changed, 235 insertions(+), 101 deletions(-) create mode 100644 core/src/main/java/cucumber/runtime/Timeout.java create mode 100644 core/src/test/java/cucumber/runtime/TimeoutTest.java diff --git a/History.md b/History.md index 7173ed3082..8937d1b049 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,7 @@ ## [Git master](https://github.com/cucumber/cucumber-jvm/compare/v1.0.9...master) +* [Java/Groovy] Step definitions and hooks can now specify a timeout (milliseconds) after which a `TimeoutException` is thrown if the stepdef/hook has not completed. + Please note that for Groovy, `sleep(int)` is not interruptible, so in order for sleeps to work your code must use `Thread.sleep(int)` ([#343](https://github.com/cucumber/cucumber-jvm/issues/343) Aslak Hellesøy) * [Java] More explanatary exception if a hook is declared with bad parameter types. (Aslak Hellesøy) * [Core/JUnit] JUnit report has time reported as seconds instead of millis. ([#347](https://github.com/cucumber/cucumber-jvm/issues/347) Aslak Hellesøy) * [Core] List legal enum values if conversion fails ([#344](https://github.com/cucumber/cucumber-jvm/issues/344) Aslak Hellesøy) diff --git a/core/src/main/java/cucumber/runtime/Timeout.java b/core/src/main/java/cucumber/runtime/Timeout.java new file mode 100644 index 0000000000..c1b16d3cbb --- /dev/null +++ b/core/src/main/java/cucumber/runtime/Timeout.java @@ -0,0 +1,37 @@ +package cucumber.runtime; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Timeout { + public static T timeout(Callback callback, int timeoutMillis) throws Throwable { + if (timeoutMillis == 0) { + return callback.call(); + } else { + final Thread executionThread = Thread.currentThread(); + final AtomicBoolean done = new AtomicBoolean(); + ScheduledFuture timer = Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { + @Override + public void run() { + if (!done.get()) { + executionThread.interrupt(); + } + } + }, timeoutMillis, TimeUnit.MILLISECONDS); + try { + T result = callback.call(); + timer.cancel(true); + return result; + } catch (InterruptedException timeout) { + throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); + } + } + } + + public interface Callback { + T call() throws Throwable; + } +} diff --git a/core/src/main/java/cucumber/runtime/Utils.java b/core/src/main/java/cucumber/runtime/Utils.java index cca11d0c21..1b9a2df3c2 100644 --- a/core/src/main/java/cucumber/runtime/Utils.java +++ b/core/src/main/java/cucumber/runtime/Utils.java @@ -5,11 +5,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; public class Utils { public static List listOf(int size, T obj) { @@ -35,35 +30,19 @@ public static boolean hasConstructor(Class clazz, Class[] paramTypes) { } public static Object invoke(final Object target, final Method method, int timeoutMillis, final Object... args) throws Throwable { - try { - if (timeoutMillis == 0) { - return method.invoke(target, args); - } else { - final Thread executionThread = Thread.currentThread(); - final AtomicBoolean done = new AtomicBoolean(); - ScheduledFuture timer = Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { - @Override - public void run() { - System.out.println("done = " + done); - if (!done.get()) { - executionThread.interrupt(); - } - } - }, timeoutMillis, TimeUnit.MILLISECONDS); + return Timeout.timeout(new Timeout.Callback() { + @Override + public Object call() throws Throwable { try { - Object result = invoke(target, method, 0, args); - timer.cancel(true); - return result; - } catch (InterruptedException timeout) { - throw new TimeoutException("Timed out after " + timeoutMillis + "ms."); + return method.invoke(target, args); + } catch (IllegalArgumentException e) { + throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(method), e); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } catch (IllegalAccessException e) { + throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(method), e); } } - } catch (IllegalArgumentException e) { - throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(method), e); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } catch (IllegalAccessException e) { - throw new CucumberException("Failed to invoke " + MethodFormat.FULL.format(method), e); - } + }, timeoutMillis); } } \ No newline at end of file diff --git a/core/src/test/java/cucumber/runtime/TimeoutTest.java b/core/src/test/java/cucumber/runtime/TimeoutTest.java new file mode 100644 index 0000000000..41911975cc --- /dev/null +++ b/core/src/test/java/cucumber/runtime/TimeoutTest.java @@ -0,0 +1,66 @@ +package cucumber.runtime; + +import org.junit.Test; + +import java.util.concurrent.TimeoutException; + +import static java.lang.Thread.sleep; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TimeoutTest { + @Test + public void doesnt_time_out_if_it_takes_too_long() throws Throwable { + final Slow slow = new Slow(); + String what = Timeout.timeout(new Timeout.Callback() { + @Override + public String call() throws Throwable { + return slow.slow(); + } + }, 50); + assertEquals("slow", what); + } + + @Test(expected = TimeoutException.class) + public void times_out_if_it_takes_too_long() throws Throwable { + final Slow slow = new Slow(); + Timeout.timeout(new Timeout.Callback() { + @Override + public String call() throws Throwable { + return slow.slower(); + } + }, 50); + fail(); + } + + @Test(expected = TimeoutException.class) + public void times_out_infinite_loop_if_it_takes_too_long() throws Throwable { + final Slow slow = new Slow(); + Timeout.timeout(new Timeout.Callback() { + @Override + public Void call() throws Throwable { + slow.infinite(); + return null; + } + }, 10); + fail(); + } + + public static class Slow { + public String slow() throws InterruptedException { + sleep(10); + return "slow"; + } + + public String slower() throws InterruptedException { + sleep(100); + return "slower"; + } + + public void infinite() throws InterruptedException { + while (true) { + sleep(1); + } + } + } +} diff --git a/core/src/test/java/cucumber/runtime/UtilsTest.java b/core/src/test/java/cucumber/runtime/UtilsTest.java index 8388b5007a..a28598be16 100644 --- a/core/src/test/java/cucumber/runtime/UtilsTest.java +++ b/core/src/test/java/cucumber/runtime/UtilsTest.java @@ -27,30 +27,4 @@ public class NonStaticInnerClass { public static class StaticInnerClass { } - - @Test - public void doesnt_time_out_if_it_takes_too_long() throws Throwable { - Slow slow = new Slow(); - Object what = Utils.invoke(slow, Slow.class.getMethod("slow"), 50); - assertEquals("slow", what); - } - - @Test(expected = TimeoutException.class) - public void times_out_if_it_takes_too_long() throws Throwable { - Slow slow = new Slow(); - Utils.invoke(slow, Slow.class.getMethod("slower"), 50); - fail(); - } - - public static class Slow { - public String slow() throws InterruptedException { - sleep(10); - return "slow"; - } - - public String slower() throws InterruptedException { - sleep(100); - return "slower"; - } - } } diff --git a/groovy/src/main/code_generator/I18n.groovy.txt b/groovy/src/main/code_generator/I18n.groovy.txt index 5befc71284..6680c8c5ab 100644 --- a/groovy/src/main/code_generator/I18n.groovy.txt +++ b/groovy/src/main/code_generator/I18n.groovy.txt @@ -7,7 +7,11 @@ import java.util.regex.Pattern; public class ${i18n.underscoredIsoCode.toUpperCase()} { <% i18n.codeKeywords.each { kw -> %> public static void ${kw}(Pattern regexp, Closure body) throws Throwable { - GroovyBackend.instance.addStepDefinition(regexp, body); + GroovyBackend.instance.addStepDefinition(regexp, 0, body); + } + + public static void ${kw}(Pattern regexp, int timeoutMillis, Closure body) throws Throwable { + GroovyBackend.instance.addStepDefinition(regexp, timeoutMillis, body); } <% } %> } diff --git a/groovy/src/main/java/cucumber/runtime/groovy/GroovyBackend.java b/groovy/src/main/java/cucumber/runtime/groovy/GroovyBackend.java index a4a3d84d8c..b7e1eda08d 100644 --- a/groovy/src/main/java/cucumber/runtime/groovy/GroovyBackend.java +++ b/groovy/src/main/java/cucumber/runtime/groovy/GroovyBackend.java @@ -14,7 +14,11 @@ import groovy.lang.Closure; import groovy.lang.GroovyShell; import groovy.lang.Script; +import groovy.transform.ThreadInterrupt; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer; import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.InvokerInvocationException; import java.io.IOException; import java.io.InputStreamReader; @@ -38,8 +42,15 @@ public class GroovyBackend implements Backend { private Object groovyWorld; private Glue glue; + private static GroovyShell createShell() { + CompilerConfiguration compilerConfig = new CompilerConfiguration(); + // Probably not needed: + // compilerConfig.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class)); + return new GroovyShell(Thread.currentThread().getContextClassLoader(), new Binding(), compilerConfig); + } + public GroovyBackend(ResourceLoader resourceLoader) { - this(new GroovyShell(), resourceLoader); + this(createShell(), resourceLoader); } public GroovyBackend(GroovyShell shell, ResourceLoader resourceLoader) { @@ -112,25 +123,29 @@ public String getSnippet(Step step) { return snippetGenerator.getSnippet(step); } - public void addStepDefinition(Pattern regexp, Closure body) { - glue.addStepDefinition(new GroovyStepDefinition(regexp, body, currentLocation(), instance)); + public void addStepDefinition(Pattern regexp, int timeoutMillis, Closure body) { + glue.addStepDefinition(new GroovyStepDefinition(regexp, timeoutMillis, body, currentLocation(), instance)); } public void registerWorld(Closure closure) { worldClosure = closure; } - void addBeforeHook(TagExpression tagExpression, Closure body) { - glue.addBeforeHook(new GroovyHookDefinition(body, tagExpression, currentLocation(), instance)); + public void addBeforeHook(TagExpression tagExpression, int timeoutMillis, Closure body) { + glue.addBeforeHook(new GroovyHookDefinition(tagExpression, timeoutMillis, body, currentLocation(), instance)); } - public void addAfterHook(TagExpression tagExpression, Closure body) { - glue.addAfterHook(new GroovyHookDefinition(body, tagExpression, currentLocation(), instance)); + public void addAfterHook(TagExpression tagExpression, int timeoutMillis, Closure body) { + glue.addAfterHook(new GroovyHookDefinition(tagExpression, timeoutMillis, body, currentLocation(), instance)); } - public void invoke(Closure body, Object[] args) { + public void invoke(Closure body, Object[] args) throws Throwable { body.setDelegate(getGroovyWorld()); - body.call(args); + try { + body.call(args); + } catch(InvokerInvocationException e) { + throw e.getCause(); + } } private Object getGroovyWorld() { diff --git a/groovy/src/main/java/cucumber/runtime/groovy/GroovyHookDefinition.java b/groovy/src/main/java/cucumber/runtime/groovy/GroovyHookDefinition.java index 0d6682d23c..47cacd4cc8 100644 --- a/groovy/src/main/java/cucumber/runtime/groovy/GroovyHookDefinition.java +++ b/groovy/src/main/java/cucumber/runtime/groovy/GroovyHookDefinition.java @@ -2,6 +2,7 @@ import cucumber.runtime.HookDefinition; import cucumber.runtime.ScenarioResult; +import cucumber.runtime.Timeout; import gherkin.TagExpression; import gherkin.formatter.model.Tag; import groovy.lang.Closure; @@ -9,14 +10,16 @@ import java.util.Collection; public class GroovyHookDefinition implements HookDefinition { - private final Closure body; private final TagExpression tagExpression; + private final int timeoutMillis; + private final Closure body; private final GroovyBackend backend; private final StackTraceElement location; - public GroovyHookDefinition(Closure body, TagExpression tagExpression, StackTraceElement location, GroovyBackend backend) { - this.body = body; + public GroovyHookDefinition(TagExpression tagExpression, int timeoutMillis, Closure body, StackTraceElement location, GroovyBackend backend) { this.tagExpression = tagExpression; + this.timeoutMillis = timeoutMillis; + this.body = body; this.location = location; this.backend = backend; } @@ -27,8 +30,14 @@ public String getLocation(boolean detail) { } @Override - public void execute(ScenarioResult scenarioResult) throws Throwable { - backend.invoke(body, new Object[]{scenarioResult}); + public void execute(final ScenarioResult scenarioResult) throws Throwable { + Timeout.timeout(new Timeout.Callback() { + @Override + public Object call() throws Throwable { + backend.invoke(body, new Object[]{scenarioResult}); + return null; + } + }, timeoutMillis); } @Override diff --git a/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java b/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java index aed09b8153..2fe5385b10 100644 --- a/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java +++ b/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java @@ -3,6 +3,7 @@ import cucumber.runtime.JdkPatternArgumentMatcher; import cucumber.runtime.ParameterType; import cucumber.runtime.StepDefinition; +import cucumber.runtime.Timeout; import gherkin.I18n; import gherkin.formatter.Argument; import gherkin.formatter.model.Step; @@ -13,14 +14,16 @@ import java.util.regex.Pattern; public class GroovyStepDefinition implements StepDefinition { + private final Pattern pattern; private final JdkPatternArgumentMatcher argumentMatcher; + private final int timeoutMillis; private final Closure body; private final StackTraceElement location; - private final Pattern pattern; private GroovyBackend backend; - public GroovyStepDefinition(Pattern pattern, Closure body, StackTraceElement location, GroovyBackend backend) { + public GroovyStepDefinition(Pattern pattern, int timeoutMillis, Closure body, StackTraceElement location, GroovyBackend backend) { this.pattern = pattern; + this.timeoutMillis = timeoutMillis; this.backend = backend; this.argumentMatcher = new JdkPatternArgumentMatcher(pattern); this.body = body; @@ -44,8 +47,14 @@ public List getParameterTypes() { return result; } - public void execute(I18n i18n, Object[] args) throws Throwable { - backend.invoke(body, args); + public void execute(I18n i18n, final Object[] args) throws Throwable { + Timeout.timeout(new Timeout.Callback() { + @Override + public Object call() throws Throwable { + backend.invoke(body, args); + return null; + } + }, timeoutMillis); } public boolean isDefinedAt(StackTraceElement stackTraceElement) { diff --git a/groovy/src/main/java/cucumber/runtime/groovy/Hooks.java b/groovy/src/main/java/cucumber/runtime/groovy/Hooks.java index da12a43750..90c62eb107 100644 --- a/groovy/src/main/java/cucumber/runtime/groovy/Hooks.java +++ b/groovy/src/main/java/cucumber/runtime/groovy/Hooks.java @@ -3,30 +3,42 @@ import gherkin.TagExpression; import groovy.lang.Closure; -import static java.util.Arrays.asList; +import java.util.ArrayList; +import java.util.List; public class Hooks { public static void World(Closure body) throws Throwable { GroovyBackend.instance.registerWorld(body); } - public static void Before(Object... tagsExpressionsAndBody) throws Throwable { - addHook(tagsExpressionsAndBody, true); + public static void Before(Object... args) throws Throwable { + addHook(args, true); } - public static void After(Object... tagsExpressionsAndBody) throws Throwable { - addHook(tagsExpressionsAndBody, false); + public static void After(Object... args) throws Throwable { + addHook(args, false); } private static void addHook(Object[] tagsExpressionsAndBody, boolean before) { - String[] tagExpressions = new String[tagsExpressionsAndBody.length - 1]; - System.arraycopy(tagsExpressionsAndBody, 0, tagExpressions, 0, tagExpressions.length); - TagExpression tagExpression = new TagExpression(asList(tagExpressions)); - Closure body = (Closure) tagsExpressionsAndBody[tagsExpressionsAndBody.length - 1]; + List tagExpressions = new ArrayList(); + int timeoutMillis = 0; + Closure body = null; + + for (Object o : tagsExpressionsAndBody) { + if (o instanceof String) { + tagExpressions.add((String) o); + } else if (o instanceof Integer) { + timeoutMillis = (Integer) o; + } else if (o instanceof Closure) { + body = (Closure) o; + } + } + + TagExpression tagExpression = new TagExpression(tagExpressions); if (before) { - GroovyBackend.instance.addBeforeHook(tagExpression, body); + GroovyBackend.instance.addBeforeHook(tagExpression, timeoutMillis, body); } else { - GroovyBackend.instance.addAfterHook(tagExpression, body); + GroovyBackend.instance.addAfterHook(tagExpression, timeoutMillis, body); } } } diff --git a/java/src/main/code_generator/I18n.java.txt b/java/src/main/code_generator/I18n.java.txt index d71e9756ca..5994949f3c 100644 --- a/java/src/main/code_generator/I18n.java.txt +++ b/java/src/main/code_generator/I18n.java.txt @@ -15,5 +15,10 @@ public @interface ${kw} { * @return a regular expression */ String value(); + + /** + * @return max amount of time this is allowed to run for. 0 (default) means no restriction. + */ + int timeout() default 0; } diff --git a/java/src/main/java/cucumber/annotation/After.java b/java/src/main/java/cucumber/annotation/After.java index cbe2528b1d..e12e779d62 100644 --- a/java/src/main/java/cucumber/annotation/After.java +++ b/java/src/main/java/cucumber/annotation/After.java @@ -12,4 +12,9 @@ * @return a tag expression */ String[] value() default {}; + + /** + * @return max amount of time this is allowed to run for. 0 (default) means no restriction. + */ + int timeout() default 0; } diff --git a/java/src/main/java/cucumber/annotation/Before.java b/java/src/main/java/cucumber/annotation/Before.java index beae7f0b17..656d54305c 100644 --- a/java/src/main/java/cucumber/annotation/Before.java +++ b/java/src/main/java/cucumber/annotation/Before.java @@ -12,4 +12,9 @@ * @return a tag expression */ String[] value() default {}; + + /** + * @return max amount of time this is allowed to run for. 0 (default) means no restriction. + */ + int timeout() default 0; } diff --git a/java/src/main/java/cucumber/runtime/java/JavaBackend.java b/java/src/main/java/cucumber/runtime/java/JavaBackend.java index 33ba6cc68b..64ea49f82d 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaBackend.java +++ b/java/src/main/java/cucumber/runtime/java/JavaBackend.java @@ -90,13 +90,8 @@ public String getSnippet(Step step) { void addStepDefinition(Annotation annotation, Method method) { try { - Method regexpMethod = annotation.getClass().getMethod("value"); - String regexpString = (String) Utils.invoke(annotation, regexpMethod, 0); - if (regexpString != null) { - Pattern pattern = Pattern.compile(regexpString); - objectFactory.addClass(method.getDeclaringClass()); - glue.addStepDefinition(new JavaStepDefinition(method, pattern, objectFactory)); - } + objectFactory.addClass(method.getDeclaringClass()); + glue.addStepDefinition(new JavaStepDefinition(method, pattern(annotation), timeout(annotation), objectFactory)); } catch (DuplicateStepDefinitionException e) { throw e; } catch (Throwable e) { @@ -104,6 +99,17 @@ void addStepDefinition(Annotation annotation, Method method) { } } + private Pattern pattern(Annotation annotation) throws Throwable { + Method regexpMethod = annotation.getClass().getMethod("value"); + String regexpString = (String) Utils.invoke(annotation, regexpMethod, 0); + return Pattern.compile(regexpString); + } + + private int timeout(Annotation annotation) throws Throwable { + Method regexpMethod = annotation.getClass().getMethod("timeout"); + return (Integer) Utils.invoke(annotation, regexpMethod, 0); + } + void addHook(Annotation annotation, Method method) { objectFactory.addClass(method.getDeclaringClass()); @@ -112,10 +118,12 @@ void addHook(Annotation annotation, Method method) { if (annotation.annotationType().equals(Before.class)) { String[] tagExpressions = ((Before) annotation).value(); - glue.addBeforeHook(new JavaHookDefinition(method, tagExpressions, hookOrder, objectFactory)); + int timeout = ((Before) annotation).timeout(); + glue.addBeforeHook(new JavaHookDefinition(method, tagExpressions, hookOrder, timeout, objectFactory)); } else { String[] tagExpressions = ((After) annotation).value(); - glue.addAfterHook(new JavaHookDefinition(method, tagExpressions, hookOrder, objectFactory)); + int timeout = ((After) annotation).timeout(); + glue.addAfterHook(new JavaHookDefinition(method, tagExpressions, hookOrder, timeout, objectFactory)); } } } diff --git a/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java b/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java index ab4ea75f5f..d996d0ef66 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java +++ b/java/src/main/java/cucumber/runtime/java/JavaHookDefinition.java @@ -16,12 +16,14 @@ public class JavaHookDefinition implements HookDefinition { private final Method method; + private final int timeout; private final TagExpression tagExpression; private final int order; private final ObjectFactory objectFactory; - public JavaHookDefinition(Method method, String[] tagExpressions, int order, ObjectFactory objectFactory) { + public JavaHookDefinition(Method method, String[] tagExpressions, int order, int timeout, ObjectFactory objectFactory) { this.method = method; + this.timeout = timeout; tagExpression = new TagExpression(asList(tagExpressions)); this.order = order; this.objectFactory = objectFactory; @@ -54,7 +56,7 @@ public void execute(ScenarioResult scenarioResult) throws Throwable { throw new CucumberException("Hooks must declare 0 or 1 arguments. " + method.toString()); } - Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, 0, args); + Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, timeout, args); } @Override diff --git a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java index 2f364b7f31..c38fd85cea 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java +++ b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java @@ -16,18 +16,20 @@ public class JavaStepDefinition implements StepDefinition { private final Method method; private final Pattern pattern; + private final int timeout; private final JdkPatternArgumentMatcher argumentMatcher; private final ObjectFactory objectFactory; - public JavaStepDefinition(Method method, Pattern pattern, ObjectFactory objectFactory) { + public JavaStepDefinition(Method method, Pattern pattern, int timeout, ObjectFactory objectFactory) { this.method = method; this.pattern = pattern; + this.timeout = timeout; this.argumentMatcher = new JdkPatternArgumentMatcher(pattern); this.objectFactory = objectFactory; } public void execute(I18n i18n, Object[] args) throws Throwable { - Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, 0, args); + Utils.invoke(objectFactory.getInstance(method.getDeclaringClass()), method, timeout, args); } public List matchedArguments(Step step) {