package org.squirrelframework.foundation.fsm.threadsafe; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.squirrelframework.foundation.component.SquirrelConfiguration; import org.squirrelframework.foundation.exception.TransitionException; import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory; import org.squirrelframework.foundation.fsm.UntypedAnonymousAction; import org.squirrelframework.foundation.fsm.UntypedStateMachine; import org.squirrelframework.foundation.fsm.UntypedStateMachineBuilder; import org.squirrelframework.foundation.fsm.annotation.OnTransitionDecline; public class AsyncExectionTest { private UntypedStateMachineBuilder builder = null; @Before public void setUp() throws Exception { builder = StateMachineBuilderFactory.create(ConcurrentSimpleStateMachine.class); SquirrelConfiguration.registerNewExecutorService(4, 120, TimeUnit.MILLISECONDS); SquirrelConfiguration.registerNewSchedulerService(4, 120, TimeUnit.MILLISECONDS); } @After public void tearDown() throws Exception { SquirrelConfiguration.registerNewExecutorService(1, 120, TimeUnit.MILLISECONDS); SquirrelConfiguration.registerNewSchedulerService(1, 120, TimeUnit.MILLISECONDS); } @Test @SuppressWarnings("unused") public void testTimedState() { final StringBuilder logger = new StringBuilder(); // timed state must be defined before transition builder.defineTimedState("A", 10, 100, "FIRST", null); builder.internalTransition().within("A").on("FIRST").perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { if (logger.length() > 0) { logger.append('.'); } logger.append("AToBOnFIRST"); } }); builder.transition().from("A").to("B").on("SECOND"); final UntypedStateMachine fsm = builder.newStateMachine("A"); fsm.addDeclarativeListener(new Object() { @OnTransitionDecline public void onTransitionDeclined() { fail(); } }); fsm.start(); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { } fsm.fire("SECOND"); fsm.terminate(); assertEquals("AToBOnFIRST.AToBOnFIRST.AToBOnFIRST.AToBOnFIRST.AToBOnFIRST", logger.toString()); } @Test public void testAsyncMethodCall() { builder.transition().from("A").to("B").on("FIRST").callMethod("fromAToB"); builder.transition().from("B").to("C").on("SECOND").callMethod("fromBToC"); final ConcurrentSimpleStateMachine fsm = builder.newUntypedStateMachine("A"); fsm.fire("FIRST"); assertEquals("C", fsm.getCurrentState()); assertEquals("fromAToB.fromBToC", fsm.logger.toString()); assertTrue(Thread.currentThread()!=fsm.fromAToBCallThread); assertTrue(Thread.currentThread()==fsm.fromBToCCallThread); } @Test(timeout=1000) public void testTimedoutActionCall() { builder.transition().from("A").to("B").on("FIRST") .perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { try { Thread.sleep(100000); } catch (InterruptedException e) { } } @Override public long timeout() { return 10; } @Override public boolean isAsync() { return true; } }); final ConcurrentSimpleStateMachine fsm = builder.newUntypedStateMachine("A"); try { fsm.fire("FIRST"); } catch(TransitionException e) { assertTrue(e.getTargetException().getClass()==TimeoutException.class); return; } fail(); } @Test public void testAsyncActionException() { final String errMsg = "This exception is thrown on purpse."; builder.transition().from("A").to("B").on("FIRST").perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { throw new IllegalArgumentException(errMsg); } @Override public boolean isAsync() { return true; } }); final ConcurrentSimpleStateMachine fsm = builder.newUntypedStateMachine("A"); try { fsm.fire("FIRST"); } catch(TransitionException e) { assertTrue(e.getTargetException().getClass()==IllegalArgumentException.class); assertTrue(e.getTargetException().getMessage()==errMsg); return; } fail(); } @Test public void testAsynActionExecOrder() { builder.onExit("AsyncA").perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { try { Thread.sleep(500); } catch (InterruptedException e) { } } @Override public boolean isAsync() { return true; } @Override public String name() { return "Exit-AsyncA"; } }); builder.externalTransition().from("AsyncA").to("AsyncB") .on("AsyncFirst").perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { try { Thread.sleep(300); } catch (InterruptedException e) { } } @Override public boolean isAsync() { return true; } @Override public String name() { return "AsyncA-AsyncB"; } }); builder.onEntry("AsyncB").perform(new UntypedAnonymousAction() { @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { try { Thread.sleep(100); } catch (InterruptedException e) { } } @Override public boolean isAsync() { return true; } @Override public String name() { return "Entry-AsyncB"; } }); final ConcurrentSimpleStateMachine fsm = builder.newUntypedStateMachine("AsyncA"); fsm.start(); fsm.fire("AsyncFirst"); assertThat(fsm.logger2.toString(), equalTo("AsyncA-null, AsyncA-AsyncB, null-AsyncB")); } }