package org.squirrelframework.foundation.fsm;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.squirrelframework.foundation.fsm.annotation.*;
import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.squirrelframework.foundation.fsm.TestEvent.*;
import static org.squirrelframework.foundation.fsm.TestState.*;
public class StateMachineExtensionTest extends AbstractStateMachineTest {
interface CallSequenceMonitor {
void entryA(TestState from, TestState to, TestEvent event, Integer context);
void entryB(TestState from, TestState to, TestEvent event, Integer context);
void entryC(TestState from, TestState to, TestEvent event, Integer context);
void entryD(TestState from, TestState to, TestEvent event, Integer context);
void transitFromAToBOnToB(TestState from, TestState to, TestEvent event, Integer context);
void testAToBOnToB(TestState from, TestState to, TestEvent event, Integer context);
void conditionalTransitToB(TestState from, TestState to, TestEvent event, Integer context);
void transitFromBToCOnToCBase(TestState from, TestState to, TestEvent event, Integer context);
void transitFromBToCOnToCOverride(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToD(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToDWithNormalPriority(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToDWithHighPriority(TestState from, TestState to, TestEvent event, Integer context);
void transitFromDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context);
void exitA(TestState from, TestState to, TestEvent event, Integer context);
void exitB(TestState from, TestState to, TestEvent event, Integer context);
void afterExitB(TestState from, TestState to, TestEvent event, Integer context);
void exitC(TestState from, TestState to, TestEvent event, Integer context);
void exitD(TestState from, TestState to, TestEvent event, Integer context);
void beforeEntryB(TestState from, TestState to, TestEvent event, Integer context);
void beforeExitA(TestState from, TestState to, TestEvent event, Integer context);
void afterEntryB(TestState from, TestState to, TestEvent event, Integer context);
void afterExitA(TestState from, TestState to, TestEvent event, Integer context);
void beforeTransitToB(TestState from, TestState to, TestEvent event, Integer context);
void afterTransitToB(TestState from, TestState to, TestEvent event, Integer context);
}
@State(name="B", entryCallMethod="afterEntryB")
@Transitions({
@Transit(from="A", to="B", on="ToB", callMethod="testAToBOnToB"),
@Transit(from="C", to="D", on="ToD", callMethod="transitFromCToDOnToDWithNormalPriority", priority=TransitionPriority.NORMAL),
@Transit(from="D", to="B", on="ToA")
})
interface DeclarativeStateMachine extends StateMachine<DeclarativeStateMachine, TestState, TestEvent, Integer> {
}
@States({
@State(name="A", exitCallMethod="beforeExitA"),
@State(name="A", exitCallMethod="afterExitA"),
@State(name="B", entryCallMethod="beforeEntryB")
})
@Transitions({
@Transit(from="B", to="C", on="ToC"),
@Transit(from="A", to="B", on="ToB", callMethod="beforeTransitToB"),
@Transit(from="A", to="B", on="ToB", callMethod="conditionalTransitToB"),
@Transit(from="A", to="B", on="ToB", callMethod="afterTransitToB"),
@Transit(from="C", to="D", on="ToD", callMethod="transitFromCToDOnToDWithHighPriority", priority=TransitionPriority.HIGH),
@Transit(from="D", to="A", on="ToA", priority=TransitionPriority.HIGH)
})
static class StateMachineImpl extends AbstractDeclarativeStateMachine {
public StateMachineImpl(CallSequenceMonitor monitor) {
super(monitor);
}
protected void beforeEntryB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.beforeEntryB(from, to, event, context);
}
protected void beforeExitA(TestState from, TestState to, TestEvent event, Integer context) {
monitor.beforeExitA(from, to, event, context);
}
protected void afterEntryB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.afterEntryB(from, to, event, context);
}
protected void afterExitA(TestState from, TestState to, TestEvent event, Integer context) {
monitor.afterExitA(from, to, event, context);
}
protected void beforeTransitToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.beforeTransitToB(from, to, event, context);
}
protected void afterTransitToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.afterTransitToB(from, to, event, context);
}
protected void transitFromCToDOnToDWithHighPriority(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToDWithHighPriority(from, to, event, context);
}
protected void transitFromCToDOnToDWithNormalPriority(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToDWithHighPriority(from, to, event, context);
}
@ExecuteWhen("context!=null && context>80")
protected void conditionalTransitToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.conditionalTransitToB(from, to, event, context);
}
@Override
protected void transitFromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromBToCOnToCOverride(from, to, event, context);
}
}
abstract static class AbstractDeclarativeStateMachine extends
AbstractStateMachine<DeclarativeStateMachine, TestState, TestEvent, Integer> implements DeclarativeStateMachine {
protected CallSequenceMonitor monitor;
public AbstractDeclarativeStateMachine(CallSequenceMonitor monitor) {
this.monitor = monitor;
}
protected void entryA(TestState from, TestState to, TestEvent event, Integer context) {
monitor.entryA(from, to, event, context);
}
protected void entryB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.entryB(from, to, event, context);
}
protected void entryC(TestState from, TestState to, TestEvent event, Integer context) {
monitor.entryC(from, to, event, context);
}
protected void entryD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.entryD(from, to, event, context);
}
protected void transitFromAToBOnToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromAToBOnToB(from, to, event, context);
}
protected void testAToBOnToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.testAToBOnToB(from, to, event, context);
}
protected void transitFromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromBToCOnToCBase(from, to, event, context);
}
protected void transitFromCToDOnToD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToD(from, to, event, context);
}
protected void transitFromDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromDToFinalOnToEnd(from, to, event, context);
}
protected void exitA(TestState from, TestState to, TestEvent event, Integer context) {
monitor.exitA(from, to, event, context);
}
protected void exitB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.exitB(from, to, event, context);
}
protected void exitC(TestState from, TestState to, TestEvent event, Integer context) {
monitor.exitC(from, to, event, context);
}
protected void exitD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.exitD(from, to, event, context);
}
public synchronized void start(Integer context) {
super.start(context);
}
public synchronized void terminate(Integer context) {
super.terminate(context);
}
}
@Mock
private CallSequenceMonitor monitor;
private DeclarativeStateMachine stateMachine;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
StateMachineBuilder<DeclarativeStateMachine, TestState, TestEvent, Integer> builder =
StateMachineBuilderFactory.<DeclarativeStateMachine, TestState, TestEvent, Integer>
create(StateMachineImpl.class, TestState.class,
TestEvent.class, Integer.class, CallSequenceMonitor.class);
builder.externalTransition().from(D).toFinal(Final).on(ToEnd);
stateMachine = builder.newStateMachine(A, monitor);
}
@Test
public void testTransitions() {
stateMachine.fire(ToB, null);
assertThat(stateMachine.getCurrentState(), equalTo(B));
stateMachine.fire(ToC, null);
assertThat(stateMachine.getCurrentState(), equalTo(C));
stateMachine.fire(ToD, null);
assertThat(stateMachine.getCurrentState(), equalTo(D));
stateMachine.fire(ToEnd, null);
assertThat(stateMachine.getStatus(), equalTo(StateMachineStatus.TERMINATED));
}
@Test
public void testMethodInvokeSequence() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(TestEvent.ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeExitA(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).exitA(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterExitA(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitToB(A, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).testAToBOnToB(A, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromAToBOnToB(A, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterTransitToB(A, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeEntryB(null, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).entryB(null, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterEntryB(null, B, ToB, null);
}
@Test
public void testExecuteWhenNotSatisfied() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(TestEvent.ToB, 10);
callSequence.verify(monitor, Mockito.times(0)).conditionalTransitToB(A, B, ToB, 10);
}
@Test
public void testExecuteWhenSatisfied() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(TestEvent.ToB, 90);
callSequence.verify(monitor, Mockito.times(1)).conditionalTransitToB(A, B, ToB, 90);
}
@Test
public void testOverrideMethodInvokeSequence() throws InterruptedException {
stateMachine.fire(TestEvent.ToB, null);
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(TestEvent.ToC, null);
Thread.sleep(100);
callSequence.verify(monitor, Mockito.times(1)).exitB(B, null, ToC, null);
callSequence.verify(monitor, Mockito.times(0)).afterExitB(B, null, ToC, null);
callSequence.verify(monitor, Mockito.times(0)).transitFromBToCOnToCBase(B, C, ToC, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromBToCOnToCOverride(B, C, ToC, null);
}
@Test
public void testTransitionPriority() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(ToB, null);
stateMachine.fire(ToC, null);
stateMachine.fire(ToD, null);
callSequence.verify(monitor, Mockito.times(1)).exitC(C, null, ToD, null);
callSequence.verify(monitor, Mockito.times(0)).transitFromCToDOnToDWithNormalPriority(C, D, ToD, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToDWithHighPriority(C, D, ToD, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToD(C, D, ToD, null);
callSequence.verify(monitor, Mockito.times(1)).entryD(null, D, ToD, null);
}
@Test
// original transition D-[ToA, Always, 1]->B was override to D-[ToA, Always, 1000]->A
public void testTransitionPriority2() {
stateMachine.fire(ToB, null);
stateMachine.fire(ToC, null);
stateMachine.fire(ToD, null);
stateMachine.fire(ToA, null);
assertThat(stateMachine.getCurrentState(), equalTo(A));
}
}