Skip to content

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
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.