/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.aurora.common.util; import java.util.function.Consumer; import org.apache.aurora.common.base.Consumers; import org.apache.aurora.common.testing.easymock.EasyMockTest; import org.apache.aurora.common.util.StateMachine.Rule; import org.apache.aurora.common.util.StateMachine.Transition; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Tests the functionality of StateMachine. * * @author William Farner */ public class StateMachineTest extends EasyMockTest { private static final String NAME = "State machine."; private static final String A = "A"; private static final String B = "B"; private static final String C = "C"; private static final String D = "D"; @Test public void testEmptySM() { control.replay(); try { StateMachine.builder(NAME).build(); fail(); } catch (IllegalStateException e) { // Expected. } } @Test public void testMachineNoInit() { control.replay(); try { StateMachine.<String>builder(NAME) .addState(Rule.from(A).to(B)) .build(); fail(); } catch (IllegalStateException e) { // Expected. } } @Test public void testBasicFSM() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .addState(Rule.from(B).to(C)) .addState(Rule.from(C).to(D)) .build(); assertThat(fsm.getState(), is(A)); changeState(fsm, B); changeState(fsm, C); changeState(fsm, D); } @Test public void testLoopingFSM() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .addState(Rule.from(B).to(C)) .addState(Rule.from(C).to(B, D)) .build(); assertThat(fsm.getState(), is(A)); changeState(fsm, B); changeState(fsm, C); changeState(fsm, B); changeState(fsm, C); changeState(fsm, B); changeState(fsm, C); changeState(fsm, D); } @Test public void testMachineUnknownState() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .addState(Rule.from(B).to(C)) .build(); assertThat(fsm.getState(), is(A)); changeState(fsm, B); changeState(fsm, C); changeStateFail(fsm, D); } @Test public void testMachineBadTransition() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .addState(Rule.from(B).to(C)) .build(); assertThat(fsm.getState(), is(A)); changeState(fsm, B); changeState(fsm, C); changeStateFail(fsm, B); } @Test public void testMachineSelfTransitionAllowed() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(A)) .build(); assertThat(fsm.getState(), is(A)); changeState(fsm, A); changeState(fsm, A); } @Test public void testMachineSelfTransitionDisallowed() { control.replay(); StateMachine<String> fsm = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .build(); assertThat(fsm.getState(), is(A)); changeStateFail(fsm, A); changeStateFail(fsm, A); } @Test public void testCheckStateMatches() { control.replay(); StateMachine<String> stateMachine = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .build(); stateMachine.checkState(A); stateMachine.transition(B); stateMachine.checkState(B); } @Test(expected = IllegalStateException.class) public void testCheckStateFails() { control.replay(); StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B)) .build() .checkState(B); } @Test public void testNoThrowOnInvalidTransition() { control.replay(); StateMachine<String> machine = StateMachine.<String>builder(NAME) .initialState(A) .addState(A, B) .throwOnBadTransition(false) .build(); machine.transition(C); assertThat(machine.getState(), is(A)); } private static final Clazz<Consumer<Transition<String>>> TRANSITION_CLOSURE_CLZ = new Clazz<Consumer<Transition<String>>>() {}; @Test public void testTransitionCallbacks() { Consumer<Transition<String>> anyTransition = createMock(TRANSITION_CLOSURE_CLZ); Consumer<Transition<String>> fromA = createMock(TRANSITION_CLOSURE_CLZ); Consumer<Transition<String>> fromB = createMock(TRANSITION_CLOSURE_CLZ); Transition<String> aToB = new Transition<>(A, B, true); anyTransition.accept(aToB); fromA.accept(aToB); Transition<String> bToB = new Transition<>(B, B, false); anyTransition.accept(bToB); fromB.accept(bToB); Transition<String> bToC = new Transition<>(B, C, true); anyTransition.accept(bToC); fromB.accept(bToC); anyTransition.accept(new Transition<>(C, B, true)); Transition<String> bToD = new Transition<>(B, D, true); anyTransition.accept(bToD); fromB.accept(bToD); control.replay(); StateMachine<String> machine = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule.from(A).to(B).withCallback(fromA)) .addState(Rule.from(B).to(C, D).withCallback(fromB)) .addState(Rule.from(C).to(B)) .addState(Rule.from(D).noTransitions()) .onAnyTransition(anyTransition) .throwOnBadTransition(false) .build(); machine.transition(B); machine.transition(B); machine.transition(C); machine.transition(B); machine.transition(D); } @Test public void testFilteredTransitionCallbacks() { Consumer<Transition<String>> aToBHandler = createMock(TRANSITION_CLOSURE_CLZ); Consumer<Transition<String>> impossibleHandler = createMock(TRANSITION_CLOSURE_CLZ); aToBHandler.accept(new Transition<>(A, B, true)); control.replay(); StateMachine<String> machine = StateMachine.<String>builder(NAME) .initialState(A) .addState(Rule .from(A).to(B, C) .withCallback(Consumers.filter(Transition.to(B), aToBHandler))) .addState(Rule.from(B).to(A) .withCallback(Consumers.filter(Transition.to(B), impossibleHandler))) .addState(Rule.from(C).noTransitions()) .build(); machine.transition(B); machine.transition(A); machine.transition(C); } private static void changeState(StateMachine<String> machine, String to, boolean expectAllowed) { boolean allowed = true; try { machine.transition(to); assertThat(machine.getState(), is(to)); } catch (StateMachine.IllegalStateTransitionException e) { allowed = false; } assertThat(allowed, is(expectAllowed)); } private static void changeState(StateMachine<String> machine, String to) { changeState(machine, to, true); } private static void changeStateFail(StateMachine<String> machine, String to) { changeState(machine, to, false); } }