Fix internal transition for dist machine
- Change previous DistributedStateMachine fixes which didn't really work. Now explicitely getting state from machine to be set into ensemble for post internal transition. - Now can send Message<E> via test plan instead of plain E. - More testing for tweaked features.
This commit is contained in:
@@ -219,11 +219,14 @@ public class DistributedStateMachine<S, E> extends LifecycleObjectSupport implem
|
||||
stateContext.getMessageHeader(StateMachineSystemConstants.STATEMACHINE_IDENTIFIER))) {
|
||||
StateMachineContext<S, E> current = ensemble.getState();
|
||||
if (current != null) {
|
||||
// TODO: it feels a bit wrong that this can be null so
|
||||
// adding note here. feel this will come back to haunt us
|
||||
ensemble.setState(new DefaultStateMachineContext<S, E>(
|
||||
current.getState(), stateContext.getEvent(), stateContext
|
||||
.getMessageHeaders(), stateContext.getStateMachine().getExtendedState()));
|
||||
} else if (stateContext.getStateMachine().getState() != null) {
|
||||
// if current ensemble state is null, get it from sm itself
|
||||
ensemble.setState(new DefaultStateMachineContext<S, E>(stateContext.getStateMachine().getState()
|
||||
.getId(), stateContext.getEvent(), stateContext.getMessageHeaders(), stateContext
|
||||
.getStateMachine().getExtendedState()));
|
||||
}
|
||||
}
|
||||
return stateContext;
|
||||
|
||||
@@ -127,6 +127,20 @@ public class StateMachineTestPlan<S, E> {
|
||||
log.info("Sending test event " + step.sendEvent + " via machine " + machine);
|
||||
machine.sendEvent(step.sendEvent);
|
||||
}
|
||||
} else if (step.sendMessage != null) {
|
||||
ArrayList<StateMachine<S, E>> sendVia = new ArrayList<StateMachine<S, E>>();
|
||||
if (step.sendEventMachineId != null) {
|
||||
sendVia.add(stateMachines.get(step.sendEventMachineId));
|
||||
} else if (step.sendEventToAll) {
|
||||
sendVia.addAll(stateMachines.values());
|
||||
} else {
|
||||
sendVia.add(stateMachines.values().iterator().next());
|
||||
}
|
||||
assertThat("Error finding machine to send via", sendVia, not(empty()));
|
||||
for (StateMachine<S, E> machine : sendVia) {
|
||||
log.info("Sending test event " + step.sendEvent + " via machine " + machine);
|
||||
machine.sendEvent(step.sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (step.expectStateChanged != null) {
|
||||
@@ -212,7 +226,7 @@ public class StateMachineTestPlan<S, E> {
|
||||
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",
|
||||
assertThat("Key " + entry.getKey() + " doesn't 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()));
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
|
||||
/**
|
||||
@@ -110,6 +111,7 @@ public class StateMachineTestPlanBuilder<S, E> {
|
||||
public class StateMachineTestPlanStepBuilder {
|
||||
|
||||
E sendEvent;
|
||||
Message<E> sendMessage;
|
||||
Object sendEventMachineId;
|
||||
boolean sendEventToAll = false;
|
||||
final Collection<S> expectStates = new ArrayList<S>();
|
||||
@@ -189,6 +191,46 @@ public class StateMachineTestPlanBuilder<S, E> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message {@code Message<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(Message<E> event) {
|
||||
return sendEvent(event, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message {@code Message<E>}. If {@code sendToAll} is set to {@code TRUE} event
|
||||
* will be send to all existing machines.
|
||||
*
|
||||
* @param event the event
|
||||
* @param sendToAll send to all machines
|
||||
* @return the state machine test plan step builder
|
||||
*/
|
||||
public StateMachineTestPlanStepBuilder sendEvent(Message<E> event, boolean sendToAll) {
|
||||
this.sendMessage = event;
|
||||
this.sendEventMachineId = null;
|
||||
this.sendEventToAll = sendToAll;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message {@code Message<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(Message<E> event, Object machineId) {
|
||||
this.sendMessage = event;
|
||||
this.sendEventMachineId = machineId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect variable to exist in extended state variables.
|
||||
*
|
||||
@@ -346,8 +388,8 @@ 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, sendEventMachineId, sendEventToAll, expectStates,
|
||||
expectStateChanged, expectStateEntered, expectStateExited, expectEventNotAccepted,
|
||||
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, sendMessage, sendEventMachineId, sendEventToAll,
|
||||
expectStates, expectStateChanged, expectStateEntered, expectStateExited, expectEventNotAccepted,
|
||||
expectTransition, expectTransitionStarted, expectTransitionEnded, expectStateMachineStarted,
|
||||
expectStateMachineStopped, expectVariableKeys, expectVariables));
|
||||
return StateMachineTestPlanBuilder.this;
|
||||
@@ -357,6 +399,7 @@ public class StateMachineTestPlanBuilder<S, E> {
|
||||
|
||||
static class StateMachineTestPlanStep<S, E> {
|
||||
E sendEvent;
|
||||
Message<E> sendMessage;
|
||||
Object sendEventMachineId;
|
||||
boolean sendEventToAll = false;
|
||||
final Collection<S> expectStates;
|
||||
@@ -372,13 +415,14 @@ public class StateMachineTestPlanBuilder<S, E> {
|
||||
final Collection<Object> expectVariableKeys;
|
||||
final Map<Object, Object> expectVariables;
|
||||
|
||||
public StateMachineTestPlanStep(E sendEvent, Object sendEventMachineId, boolean sendEventToAll,
|
||||
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) {
|
||||
public StateMachineTestPlanStep(E sendEvent, Message<E> sendMessage, Object sendEventMachineId,
|
||||
boolean sendEventToAll, 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.sendMessage = sendMessage;
|
||||
this.sendEventMachineId = sendEventMachineId;
|
||||
this.sendEventToAll = sendEventToAll;
|
||||
this.expectStates = expectStates;
|
||||
|
||||
@@ -90,6 +90,36 @@ public class ZookeeperStateMachineEnsembleTests extends AbstractZookeeperTests {
|
||||
ensemble.setState(new DefaultStateMachineContext<String, String>("S2","E1", new HashMap<String, Object>(), new DefaultExtendedState()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStateFromOther() throws Exception {
|
||||
context.register(ZkServerConfig.class, BaseConfig.class);
|
||||
context.refresh();
|
||||
|
||||
CuratorFramework curatorClient =
|
||||
context.getBean("curatorClient", CuratorFramework.class);
|
||||
|
||||
ZookeeperStateMachineEnsemble<String, String> ensemble1 =
|
||||
new ZookeeperStateMachineEnsemble<String, String>(curatorClient, "/foo");
|
||||
ZookeeperStateMachineEnsemble<String, String> ensemble2 =
|
||||
new ZookeeperStateMachineEnsemble<String, String>(curatorClient, "/foo");
|
||||
|
||||
ensemble1.afterPropertiesSet();
|
||||
ensemble2.afterPropertiesSet();
|
||||
ensemble1.start();
|
||||
ensemble2.start();
|
||||
|
||||
assertThat(curatorClient.checkExists().forPath("/foo/data/current"), notNullValue());
|
||||
assertThat(curatorClient.getData().forPath("/foo/data/current").length, is(0));
|
||||
|
||||
ensemble1.setState(new DefaultStateMachineContext<String, String>("S1","E1", new HashMap<String, Object>(), new DefaultExtendedState()));
|
||||
assertThat(curatorClient.getData().forPath("/foo/data/current").length, greaterThan(0));
|
||||
|
||||
StateMachineContext<String, String> context = ensemble2.getState();
|
||||
assertThat(context, notNullValue());
|
||||
assertThat(context.getState(), is("S1"));
|
||||
assertThat(context.getEvent(), is("E1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveEvents() throws Exception {
|
||||
context.register(ZkServerConfig.class, BaseConfig.class);
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
@@ -302,6 +304,83 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
plan.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedStateVariables1() throws Exception {
|
||||
context.register(ZkServerConfig.class, BaseConfig.class);
|
||||
context.refresh();
|
||||
|
||||
CuratorFramework curatorClient =
|
||||
context.getBean("curatorClient", CuratorFramework.class);
|
||||
|
||||
StateMachine<String, String> machine1 =
|
||||
buildTestStateMachine2(curatorClient);
|
||||
StateMachine<String, String> machine2 =
|
||||
buildTestStateMachine2(curatorClient);
|
||||
|
||||
Message<String> message = MessageBuilder
|
||||
.withPayload("EV")
|
||||
.setHeader("testVariable", "x1")
|
||||
.build();
|
||||
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.defaultAwaitTime(2)
|
||||
.stateMachine(machine1)
|
||||
.stateMachine(machine2)
|
||||
.step()
|
||||
.expectStates("SI")
|
||||
.and()
|
||||
.step()
|
||||
.sendEvent(message, machine1)
|
||||
.expectTransition(1)
|
||||
.expectVariable("testVariable", "x1")
|
||||
.and()
|
||||
.build();
|
||||
|
||||
plan.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedStateVariables2() throws Exception {
|
||||
context.register(ZkServerConfig.class, BaseConfig.class);
|
||||
context.refresh();
|
||||
|
||||
CuratorFramework curatorClient =
|
||||
context.getBean("curatorClient", CuratorFramework.class);
|
||||
|
||||
StateMachine<String, String> machine1 =
|
||||
buildTestStateMachine2(curatorClient);
|
||||
StateMachine<String, String> machine2 =
|
||||
buildTestStateMachine2(curatorClient);
|
||||
|
||||
Message<String> message = MessageBuilder
|
||||
.withPayload("EV")
|
||||
.setHeader("testVariable", "x1")
|
||||
.build();
|
||||
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.defaultAwaitTime(2)
|
||||
.stateMachine(machine1)
|
||||
.stateMachine(machine2)
|
||||
.step()
|
||||
.expectStates("SI")
|
||||
.and()
|
||||
.step()
|
||||
.sendEvent("E1", machine1)
|
||||
.expectStateChanged(1)
|
||||
.expectStates("S1")
|
||||
.and()
|
||||
.step()
|
||||
.sendEvent(message, machine1)
|
||||
.expectTransition(1)
|
||||
.expectVariable("testVariable", "x1")
|
||||
.and()
|
||||
.build();
|
||||
|
||||
plan.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testJoinLaterShouldSyncState() throws Exception {
|
||||
@@ -618,7 +697,15 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
.source("SI").target("S1").event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E2");
|
||||
.source("S1").target("S2").event("E2")
|
||||
.and()
|
||||
.withInternal()
|
||||
.source("SI").event("EV")
|
||||
.action(setVariableAction())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source("S1").event("EV")
|
||||
.action(setVariableAction());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
@@ -635,6 +722,10 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
return new FooAction();
|
||||
}
|
||||
|
||||
private static SetVariableAction setVariableAction() {
|
||||
return new SetVariableAction();
|
||||
}
|
||||
|
||||
private static class FooGuard implements Guard<String, String> {
|
||||
|
||||
private final int match;
|
||||
@@ -666,4 +757,16 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetVariableAction implements Action<String, String> {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<String, String> context) {
|
||||
String testVariable = context.getMessageHeaders().get("testVariable", String.class);
|
||||
if (testVariable != null) {
|
||||
context.getExtendedState().getVariables().put("testVariable", testVariable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user