Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

the slim service can be configured to have a statement timeout #392

Merged
merged 1 commit into from

3 participants

@robobario

The slim service can be configured to have a statement timeout.

@robobario

The use case driving this is that I work on a large suite that runs for many hours with poor fixtures that hang and cause a connection timeout. With this change you can change your slim flags to put a timeout of x seconds on statement calls. The timeout should then be reported in the test.

@amolenaar
Collaborator

This makes for a nice safety net.

@amolenaar amolenaar merged commit 043c1bc into unclebob:master
@denisko

This is nice. I'm going to try to use this, thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 18, 2014
This page is out of date. Refresh to see the latest.
View
23 src/fitnesse/slim/JavaSlimFactory.java
@@ -3,17 +3,34 @@
public class JavaSlimFactory extends SlimFactory {
private NameTranslator identityTranslator = new NameTranslatorIdentity();
-
+ private Integer timeout;
+
+ private JavaSlimFactory(Integer timeout) {
+ this.timeout = timeout;
+ }
+
public StatementExecutorInterface getStatementExecutor() {
- return new StatementExecutor();
+ StatementExecutorInterface statementExecutor = new StatementExecutor();
+ if (timeout != null) {
+ statementExecutor = StatementTimeoutExecutor.decorate(statementExecutor, timeout);
+ }
+ return statementExecutor;
}
@Override
public NameTranslator getMethodNameTranslator() {
return getIdentityTranslator();
}
-
+
private NameTranslator getIdentityTranslator() {
return identityTranslator;
}
+
+ public static SlimFactory createJavaSlimFactory(SlimService.Options options) {
+ return new JavaSlimFactory(options.statementTimeout);
+ }
+
+ public static SlimFactory createJavaSlimFactory() {
+ return new JavaSlimFactory(null);
+ }
}
View
21 src/fitnesse/slim/SlimService.java
@@ -8,31 +8,34 @@
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadPoolExecutor;
import fitnesse.socketservice.SocketFactory;
+import static fitnesse.slim.JavaSlimFactory.createJavaSlimFactory;
+
public class SlimService {
- public static final String OPTION_DESCRIPTOR = "[-v] [-i interactionClass] [-d] port";
+ public static final String OPTION_DESCRIPTOR = "[-v] [-i interactionClass] [-s statementTimeout] [-d] port";
static Class<? extends DefaultInteraction> interactionClass;
public static class Options {
final boolean verbose;
final int port;
final Class<? extends DefaultInteraction> interactionClass;
- /** daemon mode: keep accepting new connections indefinitely. */
+ /**
+ * daemon mode: keep accepting new connections indefinitely.
+ */
final boolean daemon;
+ final Integer statementTimeout;
- public Options(boolean verbose, int port, Class<? extends DefaultInteraction> interactionClass, boolean daemon) {
+ public Options(boolean verbose, int port, Class<? extends DefaultInteraction> interactionClass, boolean daemon, Integer statementTimeout) {
this.verbose = verbose;
this.port = port;
this.interactionClass = interactionClass;
this.daemon = daemon;
+ this.statementTimeout = statementTimeout;
}
}
@@ -45,7 +48,7 @@ public Options(boolean verbose, int port, Class<? extends DefaultInteraction> in
public static void main(String[] args) throws IOException {
Options options = parseCommandLine(args);
if (options != null) {
- startWithFactory(new JavaSlimFactory(), options);
+ startWithFactory(createJavaSlimFactory(options), options);
} else {
parseCommandLineFailed(args);
}
@@ -99,8 +102,10 @@ public static Options parseCommandLine(String[] args) {
String interactionClassName = commandLine.getOptionArgument("i", "interactionClass");
String portString = commandLine.getArgument("port");
int port = (portString == null) ? 8099 : Integer.parseInt(portString);
+ String statementTimeoutString = commandLine.getOptionArgument("s", "statementTimeout");
+ Integer statementTimeout = (statementTimeoutString == null) ? null : Integer.parseInt(statementTimeoutString);
boolean daemon = commandLine.hasOption("d");
- return new Options(verbose, port, getInteractionClass(interactionClassName), daemon);
+ return new Options(verbose, port, getInteractionClass(interactionClassName), daemon, statementTimeout);
}
return null;
}
View
117 src/fitnesse/slim/StatementTimeoutExecutor.java
@@ -0,0 +1,117 @@
+package fitnesse.slim;
+
+import java.util.concurrent.*;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+public class StatementTimeoutExecutor implements StatementExecutorInterface {
+ private final StatementExecutorInterface inner;
+ private final Integer timeout;
+ private final ExecutorService service;
+
+ private StatementTimeoutExecutor(StatementExecutorInterface inner, Integer timeout, ExecutorService service) {
+ this.inner = inner;
+ this.timeout = timeout;
+ this.service = service;
+ }
+
+ public static StatementExecutorInterface decorate(StatementExecutorInterface inner, Integer timeout) {
+ return decorate(inner, timeout, newSingleThreadExecutor());
+ }
+
+ public static StatementExecutorInterface decorate(StatementExecutorInterface inner, Integer timeout, ExecutorService service) {
+ return new StatementTimeoutExecutor(inner, timeout, service);
+ }
+
+ @Override
+ public void setVariable(final String name, final Object value) {
+ inner.setVariable(name, value);
+ }
+
+ @Override
+ public Object getInstance(String instanceName) {
+ return inner.getInstance(instanceName);
+ }
+
+ @Override
+ public boolean stopHasBeenRequested() {
+ return inner.stopHasBeenRequested();
+ }
+
+ @Override
+ public void reset() {
+ inner.reset();
+ }
+
+ @Override
+ public void setInstance(String actorInstanceName, Object actor) {
+ inner.setInstance(actorInstanceName, actor);
+ }
+
+ @Override
+ public void addPath(String path) throws SlimException {
+ inner.addPath(path);
+ }
+
+ @Override
+ public void create(final String instanceName, final String className, final Object... constructorArgs) throws SlimException {
+ Future<?> submit = service.submit(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ inner.create(instanceName, className, constructorArgs);
+ return true;
+ }
+ });
+ try {
+ getWithTimeout(submit);
+ } catch (TimeoutException e) {
+ throw new SlimException("timed out creating instance, instanceName : " + instanceName + ", classname : " + className + ", statementTimeout : " + timeout + " seconds");
+ }
+ }
+
+ @Override
+ public Object callAndAssign(final String symbolName, final String instanceName, final String methodsName, final Object... arguments) throws SlimException {
+ Future<Object> submit = service.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ return inner.callAndAssign(symbolName, instanceName, methodsName, arguments);
+ }
+ });
+ try {
+ return getWithTimeout(submit);
+ } catch (TimeoutException e) {
+ throw new SlimException("timed out in callAndAssign, symbolName : " + symbolName + ", instanceName : " + instanceName + ", methodsName : " + methodsName + ", statementTimeout : " + timeout + " seconds");
+ }
+ }
+
+ @Override
+ public Object call(final String instanceName, final String methodName, final Object... arguments) throws SlimException {
+ Future<Object> submit = service.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ return inner.call(instanceName, methodName, arguments);
+ }
+ });
+ try {
+ return getWithTimeout(submit);
+ } catch (TimeoutException e) {
+ throw new SlimException("timed out in call, instanceName : " + instanceName + ", methodName : " + methodName + ", statementTimeout : " + timeout + " seconds");
+ }
+ }
+
+ private <T> T getWithTimeout(Future<T> submit) throws SlimException, TimeoutException {
+ try {
+ return submit.get(timeout, SECONDS);
+ } catch (InterruptedException e) {
+ throw new SlimException(e);
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof SlimException) {
+ throw (SlimException) cause;
+ } else {
+ throw new SlimException(e.getCause());
+ }
+ }
+ }
+}
View
2  src/fitnesse/testsystems/slim/InProcessSlimClientBuilder.java
@@ -38,7 +38,7 @@ void createSlimService(String args) throws IOException {
private boolean tryCreateSlimService(String args) throws IOException {
try {
SlimService.Options options = SlimService.parseCommandLine(args.trim().split(" "));
- SlimService.startWithFactoryAsync(new JavaSlimFactory(), options);
+ SlimService.startWithFactoryAsync(JavaSlimFactory.createJavaSlimFactory(options), options);
return true;
} catch (IOException e) {
throw e;
View
29 test/fitnesse/slim/JavaSlimFactoryTest.java
@@ -0,0 +1,29 @@
+package fitnesse.slim;
+
+import fitnesse.slim.fixtureInteraction.DefaultInteraction;
+import org.junit.Test;
+
+import static fitnesse.slim.JavaSlimFactory.createJavaSlimFactory;
+import static org.junit.Assert.assertTrue;
+
+public class JavaSlimFactoryTest {
+
+ @Test
+ public void slimFactoryWithNoStatementTimeout() {
+ SlimFactory factory = createJavaSlimFactory();
+ StatementExecutorInterface executor = factory.getStatementExecutor();
+ assertTrue(executor instanceof StatementExecutor);
+ }
+
+ @Test
+ public void slimFactoryWithStatementTimeout() {
+ SlimFactory factory = createJavaSlimFactory(optionsWithStatementTimeout());
+ StatementExecutorInterface executor = factory.getStatementExecutor();
+ assertTrue(executor instanceof StatementTimeoutExecutor);
+ }
+
+ private SlimService.Options optionsWithStatementTimeout() {
+ return new SlimService.Options(false, 8099, DefaultInteraction.class, false, 1000);
+ }
+
+}
View
2  test/fitnesse/slim/ListExecutorTest.java
@@ -6,7 +6,7 @@
@Override
protected ListExecutor getListExecutor() throws Exception {
- SlimFactory slimFactory = new JavaSlimFactory();
+ SlimFactory slimFactory = JavaSlimFactory.createJavaSlimFactory();
return slimFactory.getListExecutor(false);
}
View
23 test/fitnesse/slim/SlimServiceTest.java
@@ -6,6 +6,7 @@
import java.io.IOException;
+import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -16,8 +17,8 @@ protected String getImport() {
}
protected void startSlimService() throws IOException {
- SlimService.Options options = SlimService.parseCommandLine(new String[] { "8099" });
- SlimService.startWithFactoryAsync(new JavaSlimFactory(), options);
+ SlimService.Options options = SlimService.parseCommandLine(new String[]{"8099"});
+ SlimService.startWithFactoryAsync(JavaSlimFactory.createJavaSlimFactory(options), options);
}
protected void closeSlimService() throws InterruptedException {
@@ -33,17 +34,27 @@ protected String expectedStopTestExceptionMessage() {
return "ABORT_SLIM_TEST:fitnesse.slim.test.TestSlim$StopTestException: This is a stop test exception";
}
-
@Test
- public void nullInteractionService_returnsDefaultClass(){
- SlimService.Options options = SlimService.parseCommandLine(new String[] { "8099" });
+ public void nullInteractionService_returnsDefaultClass() {
+ SlimService.Options options = SlimService.parseCommandLine(new String[]{"8099"});
assertEquals("fitnesse.slim.fixtureInteraction.DefaultInteraction", options.interactionClass.getName());
}
@Test
public void definedInteractionService_returnsCorrectClass() {
- SlimService.Options options = SlimService.parseCommandLine(new String[] { "-i", "fitnesse.slim.fixtureInteraction.InteractionDemo", "8099" });
+ SlimService.Options options = SlimService.parseCommandLine(new String[]{"-i", "fitnesse.slim.fixtureInteraction.InteractionDemo", "8099"});
assertEquals("fitnesse.slim.fixtureInteraction.InteractionDemo", options.interactionClass.getName());
}
+ @Test
+ public void undefinedStatementTimeout() {
+ SlimService.Options options = SlimService.parseCommandLine(new String[]{"8099"});
+ assertNull(options.statementTimeout);
+ }
+
+ @Test
+ public void definedStatementTimeout_returnsTimeout() {
+ SlimService.Options options = SlimService.parseCommandLine(new String[]{"-s", "1000", "8099"});
+ assertEquals(1000, (int) options.statementTimeout);
+ }
}
View
190 test/fitnesse/slim/StatementTimeoutExecutorTest.java
@@ -0,0 +1,190 @@
+package fitnesse.slim;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.concurrent.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+public class StatementTimeoutExecutorTest {
+
+ private static final String INSTANCE_NAME = "instanceName";
+ private static final String CLASS_NAME = "className";
+ private static final String ARG = "arg";
+ private static final int TIMEOUT = 4000;
+ private static final String CREATE_TIMEOUT_MESSAGE = "timed out creating instance, instanceName : "
+ + INSTANCE_NAME + ", classname : " + CLASS_NAME + ", statementTimeout : " + TIMEOUT + " seconds";
+ private static final String SYMBOL_NAME = "symbol";
+ private static final String METHOD_NAME = "method name";
+ private static final String CALL_AND_ASSIGN_TIMEOUT_MESSAGE = "timed out in callAndAssign, symbolName : "
+ + SYMBOL_NAME + ", instanceName : " + INSTANCE_NAME + ", methodsName : " + METHOD_NAME + ", statementTimeout : "
+ + TIMEOUT + " seconds";
+ private static final String CALL_TIMEOUT_MESSAGE = "timed out in call, instanceName : " + INSTANCE_NAME
+ + ", methodName : " + METHOD_NAME + ", statementTimeout : " + TIMEOUT + " seconds";
+ @Mock
+ private StatementExecutorInterface inner;
+ @Mock
+ private ExecutorService executorService;
+ private StatementExecutorInterface executor;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ executor = StatementTimeoutExecutor.decorate(inner, TIMEOUT);
+ }
+
+ @Test
+ public void testCreateDelegatesToInner() throws SlimException {
+ executor.create(INSTANCE_NAME, CLASS_NAME, ARG);
+ verify(inner).create(INSTANCE_NAME, CLASS_NAME, ARG);
+ }
+
+ @Test
+ public void testCallAndAssignDelegatesToInner() throws SlimException {
+ when(inner.callAndAssign(SYMBOL_NAME, INSTANCE_NAME, METHOD_NAME, ARG)).thenReturn("answer");
+ Object answer = executor.callAndAssign(SYMBOL_NAME, INSTANCE_NAME, METHOD_NAME, ARG);
+ assertEquals("answer", answer);
+ }
+
+ @Test
+ public void testCallDelegatesToInner() throws SlimException {
+ when(inner.call(INSTANCE_NAME, METHOD_NAME, ARG)).thenReturn("answer");
+ Object answer = executor.call(INSTANCE_NAME, METHOD_NAME, ARG);
+ assertEquals("answer", answer);
+ }
+
+ @Test
+ public void testCreateRethrowsSlimException() throws Exception {
+ Exception error = givenSlimExceptionThrownOnFutureGet();
+ Exception actual = createExpectingException();
+ assertEquals(actual, error);
+ }
+
+ @Test
+ public void testCallAndAssignRethrowsSlimException() throws Exception {
+ Exception error = givenSlimExceptionThrownOnFutureGet();
+ Exception actual = callAndAssignExpectingException();
+ assertEquals(actual, error);
+ }
+
+ @Test
+ public void testCallRethrowsSlimException() throws Exception {
+ Exception error = givenSlimExceptionThrownOnFutureGet();
+ Exception actual = callExpectingException();
+ assertEquals(actual, error);
+ }
+
+ @Test
+ public void testCreateWrapsOtherErrorsThrownInFuture() throws Exception {
+ Exception error = givenRuntimeExceptionThrownOnFutureGet();
+ Exception actual = createExpectingException();
+ assertNotNull(actual);
+ assertEquals(error, actual.getCause());
+ }
+
+ @Test
+ public void testCallAndAssignWrapsOtherErrorsThrownInFuture() throws Exception {
+ Exception error = givenRuntimeExceptionThrownOnFutureGet();
+ Exception actual = callAndAssignExpectingException();
+ assertNotNull(actual);
+ assertEquals(error, actual.getCause());
+ }
+
+ @Test
+ public void testCallWrapsOtherErrorsThrownInFuture() throws Exception {
+ Exception error = givenRuntimeExceptionThrownOnFutureGet();
+ Exception actual = callExpectingException();
+ assertNotNull(actual);
+ assertEquals(error, actual.getCause());
+ }
+
+ @Test
+ public void testCreateThrowsWhenTimeoutExceptionThrown() throws Exception {
+ givenTimeoutExceptionThrownOnFutureGet();
+ Exception actual = createExpectingException();
+ assertNotNull(actual);
+ assertEquals(CREATE_TIMEOUT_MESSAGE, actual.getMessage());
+ }
+
+ @Test
+ public void testCallAndAssignThrowsWhenTimeoutExceptionThrown() throws Exception {
+ givenTimeoutExceptionThrownOnFutureGet();
+ Exception actual = callAndAssignExpectingException();
+ assertNotNull(actual);
+ assertEquals(CALL_AND_ASSIGN_TIMEOUT_MESSAGE, actual.getMessage());
+ }
+
+ @Test
+ public void testCallThrowsWhenTimeoutExceptionThrown() throws Exception {
+ givenTimeoutExceptionThrownOnFutureGet();
+ Exception actual = callExpectingException();
+ assertNotNull(actual);
+ assertEquals(CALL_TIMEOUT_MESSAGE, actual.getMessage());
+ }
+
+ private Exception createExpectingException() {
+ Exception actual = null;
+ try {
+ executor.create(INSTANCE_NAME, CLASS_NAME, ARG);
+ } catch (SlimException e) {
+ actual = e;
+ }
+ return actual;
+ }
+
+ private Exception callAndAssignExpectingException() {
+ Exception actual = null;
+ try {
+ executor.callAndAssign(SYMBOL_NAME, INSTANCE_NAME, METHOD_NAME, ARG);
+ } catch (SlimException e) {
+ actual = e;
+ }
+ return actual;
+ }
+
+ private Exception callExpectingException() {
+ Exception actual = null;
+ try {
+ executor.call(INSTANCE_NAME, METHOD_NAME, ARG);
+ } catch (SlimException e) {
+ actual = e;
+ }
+ return actual;
+ }
+
+ private Exception givenRuntimeExceptionThrownOnFutureGet() throws InterruptedException, ExecutionException, TimeoutException {
+ RuntimeException error = new RuntimeException("error");
+ ExecutionException executionException = new ExecutionException(error);
+ givenExceptionThrownOnFutureGet(executionException);
+ return error;
+ }
+
+ private Exception givenTimeoutExceptionThrownOnFutureGet() throws InterruptedException, ExecutionException, TimeoutException {
+ TimeoutException error = new TimeoutException();
+ givenExceptionThrownOnFutureGet(error);
+ return error;
+ }
+
+ private Exception givenSlimExceptionThrownOnFutureGet() throws InterruptedException, ExecutionException, TimeoutException {
+ SlimException error = new SlimException("error");
+ ExecutionException executionException = new ExecutionException(error);
+ givenExceptionThrownOnFutureGet(executionException);
+ return error;
+ }
+
+ private void givenExceptionThrownOnFutureGet(Exception executionException) throws InterruptedException, ExecutionException, TimeoutException {
+ executor = StatementTimeoutExecutor.decorate(inner, TIMEOUT, executorService);
+ Future future = Mockito.mock(Future.class);
+ when(future.get(TIMEOUT, TimeUnit.SECONDS)).thenThrow(executionException);
+ when(executorService.submit(any(Callable.class))).thenReturn(future);
+ }
+
+}
Something went wrong with that request. Please try again.