Fixes for zk ensemble operations

- Fix some concurrent event issues which were
  born with new tests
- Enhance tests for dist machine and ensemble
- Add new features to testing system
- Polish and add more logging
This commit is contained in:
Janne Valkealahti
2015-07-29 20:15:00 +01:00
parent 706f1de232
commit 6684b0deda
8 changed files with 936 additions and 35 deletions

View File

@@ -22,13 +22,19 @@ import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Matcher;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep;
import org.springframework.statemachine.test.support.LatchStateMachineListener;
import org.springframework.util.StringUtils;
/**
* {@code StateMachineTestPlan} is fully constructed plan how
@@ -41,18 +47,25 @@ import org.springframework.statemachine.test.support.LatchStateMachineListener;
*/
public class StateMachineTestPlan<S, E> {
private final List<StateMachine<S, E>> stateMachines;
private final static Log log = LogFactory.getLog(StateMachineTestPlan.class);
private final Map<Object, StateMachine<S, E>> stateMachines;
private final List<StateMachineTestPlanStep<S, E>> steps;
private Integer defaultAwaitTime = 10;
/**
* Instantiates a new state machine test plan.
*
* @param stateMachines the state machines
* @param steps the steps
* @param defaultAwaitTime the default await time in seconds
*/
public StateMachineTestPlan(List<StateMachine<S, E>> stateMachines, List<StateMachineTestPlanStep<S, E>> steps) {
public StateMachineTestPlan(Map<Object, StateMachine<S, E>> stateMachines, List<StateMachineTestPlanStep<S, E>> steps,
Integer defaultAwaitTime) {
this.stateMachines = stateMachines;
this.steps = steps;
if (defaultAwaitTime != null) {
this.defaultAwaitTime = defaultAwaitTime;
}
}
/**
@@ -62,39 +75,144 @@ public class StateMachineTestPlan<S, E> {
*/
public void test() throws Exception {
List<LatchStateMachineListener<S, E>> listeners = new ArrayList<LatchStateMachineListener<S, E>>();
for (StateMachine<S, E> stateMachine : stateMachines) {
Map<StateMachine<S, E>, LatchStateMachineListener<S, E>> listeners =
new HashMap<StateMachine<S,E>, LatchStateMachineListener<S,E>>();
for (StateMachine<S, E> stateMachine : stateMachines.values()) {
LatchStateMachineListener<S, E> listener = new LatchStateMachineListener<S, E>();
listeners.add(listener);
listeners.put(stateMachine, listener);
stateMachine.addStateListener(listener);
stateMachine.start();
}
log.info("Running test plan for machines "
+ StringUtils.collectionToCommaDelimitedString(stateMachines.values()));
int stepCounter = 0;
for (StateMachineTestPlanStep<S, E> step : steps) {
for (LatchStateMachineListener<S, E> listener : listeners) {
listener.reset(step.expectStateChanged != null ? step.expectStateChanged : 0, 0, 0, 0, 0, 0, 0, 0, 0);
log.info("Running test plan step " + stepCounter++);
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
listener.reset(
step.expectStateChanged != null ? step.expectStateChanged : 0,
step.expectStateEntered != null ? step.expectStateEntered : 0,
step.expectStateExited != null ? step.expectStateExited : 0,
step.expectEventNotAccepted != null ? step.expectEventNotAccepted : 0,
step.expectTransition != null ? step.expectTransition : 0,
step.expectTransitionStarted != null ? step.expectTransitionStarted : 0,
step.expectTransitionEnded != null ? step.expectTransitionEnded : 0,
step.expectStateMachineStarted != null ? step.expectStateMachineStarted : 0,
step.expectStateMachineStopped != null ? step.expectStateMachineStopped : 0);
}
if (step.sendEvent != null) {
stateMachines.get(0).sendEvent(step.sendEvent);
}
if (step.expectStateChanged != null) {
for (LatchStateMachineListener<S, E> listener : listeners) {
assertThat(listener.getStateChangedLatch().await(5, TimeUnit.SECONDS), is(true));
assertThat(listener.getStateChanged().size(), is(step.expectStateChanged));
if (step.expectStateMachineStarted != null) {
for (Entry<StateMachine<S, E>, LatchStateMachineListener<S, E>> entry : listeners.entrySet()) {
assertThat("StateMachineStarted Await not matched for machine " + entry.getKey(), entry.getValue()
.getStateMachineStartedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat("StateMachineStarted count not matched for machine " + entry.getKey(), entry.getValue()
.getStateMachineStarted().size(), is(step.expectStateMachineStarted));
}
}
if (step.expectState != null) {
for (StateMachine<S, E> stateMachine : stateMachines) {
if (step.sendEvent != null) {
StateMachine<S, E> sendVia = null;
if (step.sendEventMachineId != null) {
sendVia = stateMachines.get(step.sendEventMachineId);
} else {
sendVia = stateMachines.values().iterator().next();
}
assertThat("Error finding machine to send via", sendVia, notNullValue());
log.info("Sending test event " + step.sendEvent + " via machine " + sendVia);
sendVia.sendEvent(step.sendEvent);
}
if (step.expectStateChanged != null) {
for (Entry<StateMachine<S, E>, LatchStateMachineListener<S, E>> entry : listeners.entrySet()) {
assertThat("StateChanged Await not matched for machine " + entry.getKey(), entry.getValue()
.getStateChangedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat("StateChanged count not matched for machine " + entry.getKey(), entry.getValue()
.getStateChanged().size(), is(step.expectStateChanged));
}
}
if (step.expectStateEntered != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getStateEnteredLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getStateEntered().size(), is(step.expectStateEntered));
}
}
if (step.expectStateExited != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getStateExitedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getStateExited().size(), is(step.expectStateExited));
}
}
if (step.expectEventNotAccepted != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getEventNotAcceptedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getEventNotAccepted().size(), is(step.expectEventNotAccepted));
}
}
if (step.expectTransition != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getTransitionLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getTransition().size(), is(step.expectTransition));
}
}
if (step.expectTransitionStarted != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getTransitionStartedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getTransitionStarted().size(), is(step.expectTransitionStarted));
}
}
if (step.expectTransitionEnded != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getTransitionEndedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getTransitionEnded().size(), is(step.expectTransitionEnded));
}
}
if (step.expectStateMachineStopped != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getStateMachineStoppedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));
assertThat(listener.getStateMachineStopped().size(), is(step.expectStateMachineStopped));
}
}
if (!step.expectStates.isEmpty()) {
for (StateMachine<S, E> stateMachine : stateMachines.values()) {
assertThat(stateMachine.getState(), notNullValue());
Collection<Matcher<? super S>> itemMatchers = new ArrayList<Matcher<? super S>>();
itemMatchers.add(is(step.expectState));
for (S expectState : step.expectStates) {
itemMatchers.add(is(expectState));
}
assertThat(stateMachine.getState().getIds(), containsInAnyOrder(itemMatchers));
}
}
if (!step.expectVariableKeys.isEmpty()) {
for (StateMachine<S, E> stateMachine : stateMachines.values()) {
Map<Object, Object> variables = stateMachine.getExtendedState().getVariables();
for (Object key : step.expectVariableKeys) {
assertThat("Key " + key + " doesn't exist in extended state variables",
variables.containsKey(key), is(true));
}
}
}
if (!step.expectVariables.isEmpty()) {
for (StateMachine<S, E> stateMachine : stateMachines.values()) {
Map<Object, Object> variables = stateMachine.getExtendedState().getVariables();
for (Entry<Object, Object> entry : step.expectVariables.entrySet()) {
assertThat("Key " + entry.getKey() + " doesn exist in extended state variables",
variables.containsKey(entry.getKey()), is(true));
assertThat("Variable " + entry.getKey() + " doesn't match in extended state variables",
variables.get(entry.getKey()), is(entry.getValue()));
}
}
}
}
}

View File

@@ -16,7 +16,11 @@
package org.springframework.statemachine.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.statemachine.StateMachine;
@@ -30,8 +34,9 @@ import org.springframework.statemachine.StateMachine;
*/
public class StateMachineTestPlanBuilder<S, E> {
private List<StateMachine<S, E>> stateMachines = new ArrayList<StateMachine<S, E>>();
private Map<Object, StateMachine<S, E>> stateMachines = new HashMap<Object, StateMachine<S, E>>();
private final List<StateMachineTestPlanStep<S, E>> steps = new ArrayList<StateMachineTestPlanStep<S, E>>();
private Integer defaultAwaitTime;
/**
* Gets a new instance of this builder.
@@ -49,7 +54,33 @@ public class StateMachineTestPlanBuilder<S, E> {
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> stateMachine) {
this.stateMachines.add(stateMachine);
return stateMachine(stateMachine, stateMachine);
}
/**
* Associate a state machine with this builder.
*
* @param stateMachine the state machine
* @param machineId the machine id to use for sending
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> stateMachine, Object machineId) {
this.stateMachines.put(machineId, stateMachine);
return this;
}
/**
* Sets default await time. This is in seconds how long a latch
* will be waited for listening various callbacks.
*
* @param seconds the default await time in seconds
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> defaultAwaitTime(int seconds) {
if (seconds < 0) {
throw new IllegalArgumentException("Default await time cannot be negative, was " + seconds);
}
this.defaultAwaitTime = seconds;
return this;
}
@@ -68,7 +99,7 @@ public class StateMachineTestPlanBuilder<S, E> {
* @return the state machine test plan
*/
public StateMachineTestPlan<S, E> build() {
return new StateMachineTestPlan<S, E>(stateMachines, steps);
return new StateMachineTestPlan<S, E>(stateMachines, steps, defaultAwaitTime);
}
/**
@@ -77,8 +108,20 @@ public class StateMachineTestPlanBuilder<S, E> {
public class StateMachineTestPlanStepBuilder {
E sendEvent;
S expectState;
Object sendEventMachineId;
final Collection<S> expectStates = new ArrayList<S>();
Integer expectStateChanged;
Integer expectStateEntered;
Integer expectStateExited;
Integer expectEventNotAccepted;
Integer expectTransition;
Integer expectTransitionStarted;
Integer expectTransitionEnded;
Integer expectStateMachineStarted;
Integer expectStateMachineStopped;
final Collection<Object> expectVariableKeys = new ArrayList<Object>();
final Map<Object, Object> expectVariables = new HashMap<Object, Object>();
/**
* Expect a state {@code S}.
@@ -87,18 +130,68 @@ public class StateMachineTestPlanBuilder<S, E> {
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectState(S state) {
this.expectState = state;
this.expectStates.add(state);
return this;
}
/**
* Send an event {@code E}.
* Expect a states {@code S}.
*
* @param states the states
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStates(S... states) {
this.expectStates.addAll(Arrays.asList(states));
return this;
}
/**
* Send an event {@code E}. In case multiple state machines
* exists, a random one will be chosen to send this event.
*
* @param event the event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event) {
return sendEvent(event, null);
}
/**
* Send an event {@code E} into a state machine identified
* by {@code machineId}.
*
* @param event the event
* @param machineId the machine identifier for sending event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event, Object machineId) {
this.sendEvent = event;
this.sendEventMachineId = machineId;
return this;
}
/**
* Expect variable to exist in extended state variables.
*
* @param key the key
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectVariable(Object key) {
this.expectVariableKeys.add(key);
return this;
}
/**
* Expect variable to exist in extended state variables and match
* with the value.
*
* @param key the key
* @param value the value
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectVariable(Object key, Object value) {
this.expectVariables.put(key, value);
return this;
}
@@ -116,6 +209,118 @@ public class StateMachineTestPlanBuilder<S, E> {
return this;
}
/**
* Expect state enter happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateEntered(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateEntered = count;
return this;
}
/**
* Expect state exit happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateExited(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateExited = count;
return this;
}
/**
* Expect event not accepter happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectEventNotAccepted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectEventNotAccepted = count;
return this;
}
/**
* Expect transition happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransition(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransition = count;
return this;
}
/**
* Expect transition start happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransitionStarted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransitionStarted = count;
return this;
}
/**
* Expect transition end happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransitionEnded(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransitionEnded = count;
return this;
}
/**
* Expect state machine start happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateMachineStarted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateMachineStarted = count;
return this;
}
/**
* Expect state machine stop happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateMachineStopped(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateMachineStopped = count;
return this;
}
/**
* Add a new step and return {@link StateMachineTestPlanBuilder}
* for chaining.
@@ -123,7 +328,10 @@ public class StateMachineTestPlanBuilder<S, E> {
* @return the state machine test plan builder for chaining
*/
public StateMachineTestPlanBuilder<S, E> and() {
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, expectState, expectStateChanged));
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, sendEventMachineId, expectStates,
expectStateChanged, expectStateEntered, expectStateExited, expectEventNotAccepted,
expectTransition, expectTransitionStarted, expectTransitionEnded, expectStateMachineStarted,
expectStateMachineStopped, expectVariableKeys, expectVariables));
return StateMachineTestPlanBuilder.this;
}
@@ -131,12 +339,39 @@ public class StateMachineTestPlanBuilder<S, E> {
static class StateMachineTestPlanStep<S, E> {
E sendEvent;
S expectState;
Object sendEventMachineId;
final Collection<S> expectStates;
Integer expectStateChanged;
public StateMachineTestPlanStep(E sendEvent, S expectState, Integer expectStateChanged) {
Integer expectStateEntered;
Integer expectStateExited;
Integer expectEventNotAccepted;
Integer expectTransition;
Integer expectTransitionStarted;
Integer expectTransitionEnded;
Integer expectStateMachineStarted;
Integer expectStateMachineStopped;
final Collection<Object> expectVariableKeys;
final Map<Object, Object> expectVariables;
public StateMachineTestPlanStep(E sendEvent, Object sendEventMachineId, Collection<S> expectStates, Integer expectStateChanged,
Integer expectStateEntered, Integer expectStateExited, Integer expectEventNotAccepted,
Integer expectTransition, Integer expectTransitionStarted, Integer expectTransitionEnded,
Integer expectStateMachineStarted, Integer expectStateMachineStopped,
Collection<Object> expectVariableKeys, Map<Object, Object> expectVariables) {
this.sendEvent = sendEvent;
this.expectState = expectState;
this.sendEventMachineId = sendEventMachineId;
this.expectStates = expectStates;
this.expectStateChanged = expectStateChanged;
this.expectStateEntered = expectStateEntered;
this.expectStateExited = expectStateExited;
this.expectEventNotAccepted = expectEventNotAccepted;
this.expectTransition = expectTransition;
this.expectTransitionStarted = expectTransitionStarted;
this.expectTransitionEnded = expectTransitionEnded;
this.expectStateMachineStarted = expectStateMachineStarted;
this.expectStateMachineStopped = expectStateMachineStopped;
this.expectVariableKeys = expectVariableKeys;
this.expectVariables = expectVariables;
}
}