blob: 1c0ee47833b3994d721b12cbc5526e2b8f9ad269 [file] [log] [blame]
/**
* 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);
}
}