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.exception.TransitionException;
import org.squirrelframework.foundation.fsm.annotation.ContextEvent;
import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.squirrelframework.foundation.fsm.TestEvent.*;
import static org.squirrelframework.foundation.fsm.TestState.*;
public class ConventionalStateMachineTest extends AbstractStateMachineTest {
interface CallSequenceMonitor {
void beforeEntryAny(TestState from, TestState to, TestEvent event, Integer context);
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 afterEntryAny(TestState from, TestState to, TestEvent event, Integer context);
void transitFromAToAOnInternalA(TestState from, TestState to, TestEvent event, Integer context);
void transitFromAToBOnToB(TestState from, TestState to, TestEvent event, Integer context);
void fromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToDWhenMvelExp(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToDWhenBetween60To80(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToDOnToD(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToD(TestState from, TestState to, TestEvent event, Integer context);
void transitFromCToAnyOnToD(TestState from, TestState to, TestEvent event, Integer context);
void onToD(TestState from, TestState to, TestEvent event, Integer context);
void transitFromDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context);
void beforeExitAny(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 exitC(TestState from, TestState to, TestEvent event, Integer context);
void exitD(TestState from, TestState to, TestEvent event, Integer context);
void afterExitAny(TestState from, TestState to, TestEvent event, Integer context);
void beforeTransitionBegin(TestState from, TestEvent event, Integer context);
void afterTransitionCompleted(TestState from, TestState to, TestEvent event, Integer context);
void afterTransitionDeclined(TestState from, TestEvent event, Integer context);
void afterTransitionCausedException(Exception e, int transitionStatus, TestState fromState,
TestState toState, TestEvent event, Integer context);
void mvelAction(TestState from, TestState to, TestEvent event, Integer context);
void terminate();
}
@SuppressWarnings("serial")
static class ConventionalStateMachineException extends RuntimeException {
}
@ContextEvent(startEvent="Started", finishEvent="Finished", terminateEvent="Terminated")
public static class ConventionalStateMachine extends AbstractStateMachine<ConventionalStateMachine, TestState, TestEvent, Integer> {
private final CallSequenceMonitor monitor;
protected ConventionalStateMachine(CallSequenceMonitor monitor) {
this.monitor = monitor;
}
protected void beforeEntryAny(TestState from, TestState to, TestEvent event, Integer context) {
monitor.beforeEntryAny(from, to, event, context);
}
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 afterEntryAny(TestState from, TestState to, TestEvent event, Integer context) {
monitor.afterEntryAny(from, to, event, context);
}
protected void transitFromAToAOnInternalA(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromAToAOnInternalA(from, to, event, context);
}
protected void transitFromAToBOnToB(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromAToBOnToB(from, to, event, context);
}
protected void fromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context) {
monitor.fromBToCOnToC(from, to, event, context);
}
protected void transitFromCToDOnToDWhenMvelExp(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToDWhenMvelExp(from, to, event, context);
}
protected void transitFromCToDOnToDWhenBetween60To80(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToDWhenBetween60To80(from, to, event, context);
throw new ConventionalStateMachineException();
}
protected void transitFromCToDOnToD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToDOnToD(from, to, event, context);
}
protected void transitFromCToD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToD(from, to, event, context);
}
protected void transitFromCToAnyOnToD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromCToAnyOnToD(from, to, event, context);
}
protected void onToD(TestState from, TestState to, TestEvent event, Integer context) {
monitor.onToD(from, to, event, context);
}
protected void transitFromDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context) {
monitor.transitFromDToFinalOnToEnd(from, to, event, context);
}
protected void beforeExitAny(TestState from, TestState to, TestEvent event, Integer context) {
monitor.beforeExitAny(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);
}
protected void afterExitAny(TestState from, TestState to, TestEvent event, Integer context) {
monitor.afterExitAny(from, to, event, context);
}
public void mvelAction(TestState from, TestState to, TestEvent event, Integer context) {
monitor.mvelAction(from, to, event, context);
}
@Override
public void beforeTransitionBegin(TestState from, TestEvent event, Integer context) {
super.beforeTransitionBegin(from, event, context);
monitor.beforeTransitionBegin(from, event, context);
}
@Override
public void afterTransitionCompleted(TestState from, TestState to, TestEvent event, Integer context) {
super.afterTransitionCompleted(from, to, event, context);
monitor.afterTransitionCompleted(from, to, event, context);
}
@Override
public void afterTransitionDeclined(TestState from, TestEvent event, Integer context) {
super.afterTransitionDeclined(from, event, context);
monitor.afterTransitionDeclined(from, event, context);
}
@Override
public void terminate(Integer context) {
super.terminate(context);
monitor.terminate();
}
}
@Mock
private CallSequenceMonitor monitor;
private ConventionalStateMachine stateMachine;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
StateMachineBuilder<ConventionalStateMachine, TestState, TestEvent, Integer> builder =
StateMachineBuilderFactory.create(ConventionalStateMachine.class, TestState.class,
TestEvent.class, Integer.class, CallSequenceMonitor.class);
builder.externalTransition().from(A).to(B).on(ToB);
builder.internalTransition().within(A).on(InternalA);
builder.externalTransition().from(B).to(C).on(ToC).callMethod("fromBToCOnToC");
builder.externalTransition().from(C).to(D).on(ToD).whenMvel("MvelExp:::(context!=null && context>80)");
builder.externalTransition().from(C).to(D).on(ToD).when(new Condition<Integer>() {
@Override
public boolean isSatisfied(Integer context) {
return context!=null && context>=60 && context<=80;
}
@Override
public String name() {
return "Between60To80";
}
});
builder.externalTransition().from(D).toFinal(Final).on(ToEnd).evalMvel(
"MvelAction:::(" +
" stateMachine.mvelAction(from, to, event, context);" +
")"
);
stateMachine = builder.newStateMachine(A, monitor);
}
@Test
public void testCanAcceptEvent() {
assertTrue(stateMachine.canAccept(ToB));
assertFalse(stateMachine.canAccept(ToC));
}
@Test
public void testLastState() {
assertThat(stateMachine.getLastRawState(), equalTo(null));
stateMachine.fire(ToB, null);
assertThat(stateMachine.getLastState(), equalTo(A));
stateMachine.fire(ToC, null);
assertThat(stateMachine.getLastState(), equalTo(B));
stateMachine.fire(ToD, 81);
assertThat(stateMachine.getLastState(), equalTo(C));
}
@Test
public void testInternalTransition() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).beforeExitAny(A, null, InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).exitA(A, null, InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).afterExitAny(A, null, InternalA, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromAToAOnInternalA(A, A, InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).beforeEntryAny(null, A, InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).entryA(null, A, InternalA, null);
callSequence.verify(monitor, Mockito.times(0)).afterEntryAny(null, A, InternalA, null);
assertThat(stateMachine.getCurrentState(), equalTo(A));
}
@Test
public void testExternalTransition() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeExitAny(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).exitA(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterExitAny(A, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromAToBOnToB(A, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeEntryAny(null, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).entryB(null, B, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterEntryAny(null, B, ToB, null);
assertThat(stateMachine.getCurrentState(), equalTo(B));
}
@Test
public void testDeclinedTransition() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(ToB, null);
stateMachine.fire(ToB, null);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin(B, ToB, null);
callSequence.verify(monitor, Mockito.times(0)).exitB(B, null, ToB, null);
callSequence.verify(monitor, Mockito.times(1)).afterTransitionDeclined(B, ToB, null);
assertThat(stateMachine.getCurrentState(), equalTo(B));
}
@Test
public void testConditionalTransition() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(ToB, null);
stateMachine.fire(ToC, null);
stateMachine.fire(ToD, 50);
callSequence.verify(monitor, Mockito.times(1)).fromBToCOnToC(B, C, ToC, null);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin(C, ToD, 50);
callSequence.verify(monitor, Mockito.times(1)).afterTransitionDeclined(C, ToD, 50);
assertThat(stateMachine.getCurrentState(), equalTo(TestState.C));
stateMachine.fire(ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin(C, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).beforeExitAny(C, null, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).exitC(C, null, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).afterExitAny(C, null, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToDWhenMvelExp(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(0)).transitFromCToDOnToDWhenBetween60To80(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToD(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToAnyOnToD(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToD(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).onToD(C, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).beforeEntryAny(null, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).entryD(null, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).afterEntryAny(null, D, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted(C, D, ToD, 81);
assertThat(stateMachine.getCurrentState(), equalTo(TestState.D));
}
@Test(expected=TransitionException.class)
public void testTransitionWithException() {
stateMachine.fire(ToB, null);
stateMachine.fire(ToC, null);
stateMachine.fire(ToD, 60);
}
@Test
public void testTransitToFinalState() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.fire(ToB, null);
stateMachine.fire(ToC, null);
stateMachine.fire(ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin(C, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).exitC(C, null, ToD, 81);
callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToD(C, D, ToD, 81);
stateMachine.fire(ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin(D, ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).exitD(D, null, ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).mvelAction(D, Final, ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).transitFromDToFinalOnToEnd(D, Final, ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted(D, Final, ToEnd, null);
callSequence.verify(monitor, Mockito.times(1)).terminate();
assertThat(stateMachine.getStatus(), equalTo(StateMachineStatus.TERMINATED));
}
@Test
public void testDeclaredEventType() {
InOrder callSequence = Mockito.inOrder(monitor);
stateMachine.start();
callSequence.verify(monitor, Mockito.times(1)).entryA(null, A, Started, null);
stateMachine.terminate();
callSequence.verify(monitor, Mockito.times(1)).exitA(A, null, Terminated, null);
}
@Test(expected=RuntimeException.class)
public void testFireEventToTerminatedFSM() {
stateMachine.fire(ToB, null);
assertThat(stateMachine.getCurrentState(), equalTo(B));
stateMachine.terminate(0);
assertThat(stateMachine.getStatus(), equalTo(StateMachineStatus.TERMINATED));
stateMachine.fire(ToC, null);
}
}