/* * Copyright 2015 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.ctrl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.After; import org.junit.Before; import org.junit.Test; import ccre.channel.BooleanCell; import ccre.channel.BooleanInput; import ccre.channel.EventCell; import ccre.channel.EventInput; import ccre.channel.EventOutput; import ccre.channel.FloatCell; import ccre.testing.CountingEventOutput; @SuppressWarnings("javadoc") public class StateMachineTest { private StateMachine machine; private static final String NOT_A_NAME = "BadState"; private static final String[] names = new String[] { "Open", "Closed", "Exploded", "Locked" }; @Before public void setUp() throws Exception { machine = new StateMachine(0, names); } @After public void tearDown() throws Exception { machine = null; } @Test public void testStateMachineConstructor() { for (int i = 0; i < names.length; i++) { assertEquals(new StateMachine(names[i], names).getState(), i); assertEquals(new StateMachine(i, names).getState(), i); assertEquals(new StateMachine(names[i], names).getStateName(), names[i]); assertEquals(new StateMachine(i, names).getStateName(), names[i]); } } // A bunch of tests that just check constructor edge cases @Test(expected = NullPointerException.class) public void testStateMachineStringNullStringArray() { new StateMachine("Test", (String[]) null); } @Test(expected = NullPointerException.class) public void testStateMachineIntNullStringArray() { new StateMachine(0, (String[]) null); } @Test(expected = RuntimeException.class) public void testStateMachineStringEmptyStringArray() { new StateMachine("Test", new String[0]); } @Test(expected = RuntimeException.class) public void testStateMachineIntEmptyStringArray() { new StateMachine(0, new String[0]); } @Test(expected = NullPointerException.class) public void testStateMachineStringNull() { new StateMachine("Test", new String[] { "Test", null }); } @Test(expected = NullPointerException.class) public void testStateMachineIntNull() { new StateMachine(0, new String[] { "Real", null }); } @Test(expected = RuntimeException.class) public void testStateMachineNotIncluded() { new StateMachine("Test", new String[] { "Not This", "Nope", "Not This Either" }); } @Test(expected = RuntimeException.class) public void testStateMachineNullName() { new StateMachine(null, new String[] { "Not This", "Nope", "Not This Either" }); } @Test(expected = RuntimeException.class) public void testStateMachineOutOfRangePositive() { new StateMachine(3, new String[] { "Exists", "Also exists", "Next one won't!" }); } @Test(expected = RuntimeException.class) public void testStateMachineOutOfRangeNegative() { new StateMachine(-1, new String[] { "Exists", "Also exists", "Next one won't!" }); } @Test(expected = RuntimeException.class) public void testStateMachineDuplicateString() { new StateMachine("Test", new String[] { "Test", "Alpha", "Not a duplicate", "Alpha" }); } @Test(expected = RuntimeException.class) public void testStateMachineDuplicateInt() { new StateMachine(0, new String[] { "Test", "Alpha", "Not a duplicate", "Alpha" }); } // Now back to our regularly scheduled testing @Test public void testGetNumberOfStates() { assertEquals(machine.getNumberOfStates(), names.length); } @Test public void testGetState() { assertEquals(machine.getState(), 0); assertEquals(machine.getStateName(), names[0]); } @Test public void testGetStateNameInt() { for (int i = 0; i < names.length; i++) { assertEquals(machine.getStateName(i), names[i]); } } @Test(expected = IllegalArgumentException.class) public void testGetStateNameNegative() { machine.getStateName(-1); } @Test(expected = IllegalArgumentException.class) public void testGetStateNameHigh() { machine.getStateName(names.length); } @Test public void testSetStateInt() { for (int i = 0; i < names.length * 4; i++) { int state = i % names.length; machine.setState(state); assertEquals(machine.getState(), state); assertEquals(machine.getStateName(), names[state]); } } @Test(expected = IllegalArgumentException.class) public void testSetStateIntNegative() { machine.setState(-1); } @Test(expected = IllegalArgumentException.class) public void testSetStateIntHigh() { machine.setState(names.length); } @Test public void testSetStateString() { for (int i = 0; i < names.length * 4; i++) { int state = i % names.length; machine.setState(names[state]); assertEquals(machine.getState(), state); assertEquals(machine.getStateName(), names[state]); } } @Test(expected = IllegalArgumentException.class) public void testSetStateStringNonexistent() { machine.setState(NOT_A_NAME); } @Test public void testSetStateWhenStringEventInput() { EventCell[] events = new EventCell[names.length]; for (int i = 0; i < names.length; i++) { events[i] = new EventCell(); machine.setStateWhen(names[i], events[i]); } tryStateSetters(events); } @Test(expected = IllegalArgumentException.class) public void testSetStateWhenStringEventInputNonexistent() { machine.setStateWhen(NOT_A_NAME, EventInput.never); } @Test public void testSetStateWhenIntEventInput() { EventCell[] events = new EventCell[names.length]; for (int i = 0; i < names.length; i++) { events[i] = new EventCell(); machine.setStateWhen(i, events[i]); } tryStateSetters(events); } @Test(expected = IllegalArgumentException.class) public void testSetStateWhenIntEventInputNegative() { machine.setStateWhen(-1, EventInput.never); } @Test(expected = IllegalArgumentException.class) public void testSetStateWhenIntEventInputHigh() { machine.setStateWhen(names.length, EventInput.never); } @Test public void testGetStateSetEventString() { EventOutput[] events = new EventOutput[names.length]; for (int i = 0; i < names.length; i++) { events[i] = machine.getStateSetEvent(names[i]); } tryStateSetters(events); } @Test(expected = IllegalArgumentException.class) public void testGetSetStateEventStringNonexistent() { machine.getStateSetEvent(NOT_A_NAME); } @Test public void testGetStateSetEventInt() { EventOutput[] events = new EventOutput[names.length]; for (int i = 0; i < names.length; i++) { events[i] = machine.getStateSetEvent(i); } tryStateSetters(events); } @Test(expected = IllegalArgumentException.class) public void testGetStateSetEventIntNegative() { machine.getStateSetEvent(-1); } @Test(expected = IllegalArgumentException.class) public void testGetStateSetEventIntHigh() { machine.getStateSetEvent(names.length); } private void tryStateSetters(EventOutput[] events) { for (int i = 0; i < names.length * 4; i++) { int state = i % names.length; events[state].event(); assertEquals(machine.getState(), state); assertEquals(machine.getStateName(), names[state]); } } @Test public void testIsState() { for (int i = 0; i < names.length; i++) { machine.setState(i); assertTrue(machine.isState(i)); assertTrue(machine.isState(names[i])); for (int j = 0; j < names.length; j++) { if (i != j) { assertFalse(machine.isState(j)); assertFalse(machine.isState(names[j])); } } } } @Test(expected = IllegalArgumentException.class) public void testIsStateNegative() { machine.isState(-1); } @Test(expected = IllegalArgumentException.class) public void testIsStateHigh() { machine.isState(names.length); } @Test(expected = IllegalArgumentException.class) public void testIsStateNonexistent() { machine.isState(NOT_A_NAME); } @Test public void testGetIsState() { BooleanInput[] inputsA = new BooleanInput[names.length]; BooleanInput[] inputsB = new BooleanInput[names.length]; for (int i = 0; i < names.length; i++) { inputsA[i] = machine.getIsState(i); inputsB[i] = machine.getIsState(names[i]); } for (int i = 0; i < names.length; i++) { machine.setState(i); assertTrue(inputsA[i].get()); assertTrue(inputsB[i].get()); for (int j = 0; j < names.length; j++) { if (i != j) { assertFalse(inputsA[j].get()); assertFalse(inputsB[j].get()); } } } } @Test(expected = IllegalArgumentException.class) public void testGetIsStateNegative() { machine.getIsState(-1); } @Test(expected = IllegalArgumentException.class) public void testGetIsStateHigh() { machine.getIsState(names.length); } @Test(expected = IllegalArgumentException.class) public void testGetIsStateNonexistent() { machine.getIsState(NOT_A_NAME); } @Test public void testGetStateTransitionEventStringString() { EventOutput[] events = new EventOutput[names.length * names.length]; for (int i = 0; i < names.length; i++) { for (int j = 0; j < names.length; j++) { events[i + j * 4] = machine.getStateTransitionEvent(names[i], names[j]); } } tryStateTransitions(events); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventStringStringNonexistentA() { machine.getStateTransitionEvent(NOT_A_NAME, names[0]); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventStringStringNonexistentB() { machine.getStateTransitionEvent(names[0], NOT_A_NAME); } @Test public void testGetStateTransitionEventIntInt() { EventOutput[] events = new EventOutput[names.length * names.length]; for (int i = 0; i < names.length; i++) { for (int j = 0; j < names.length; j++) { events[i + j * 4] = machine.getStateTransitionEvent(i, j); } } tryStateTransitions(events); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventIntIntNegativeA() { machine.getStateTransitionEvent(-1, 0); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventIntIntNegativeB() { machine.getStateTransitionEvent(0, -1); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventIntIntHighA() { machine.getStateTransitionEvent(names.length, 0); } @Test(expected = IllegalArgumentException.class) public void testGetStateTransitionEventIntIntHighB() { machine.getStateTransitionEvent(0, names.length); } private void tryStateTransitions(EventOutput[] events) { for (int state = 0; state < names.length; state++) { for (int i = 0; i < names.length; i++) { for (int j = 0; j < names.length; j++) { machine.setState(state); events[i + j * 4].event(); if (i == state) { assertEquals(machine.getState(), j); } else { assertEquals(machine.getState(), state); } } } } } @Test public void testTransitionStateWhenStringStringEventInput() { EventCell[] events = new EventCell[names.length * names.length]; for (int i = 0; i < names.length; i++) { for (int j = 0; j < names.length; j++) { events[i + j * 4] = new EventCell(); machine.transitionStateWhen(names[i], names[j], events[i + j * 4]); } } tryStateTransitions(events); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenStringStringEventInputNonexistentA() { machine.transitionStateWhen(NOT_A_NAME, names[0], EventInput.never); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenStringStringEventInputNonexistentB() { machine.transitionStateWhen(names[0], NOT_A_NAME, EventInput.never); } @Test public void testTransitionStateWhenIntIntEventInput() { EventCell[] events = new EventCell[names.length * names.length]; for (int i = 0; i < names.length; i++) { for (int j = 0; j < names.length; j++) { events[i + j * 4] = new EventCell(); machine.transitionStateWhen(i, j, events[i + j * 4]); } } tryStateTransitions(events); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenIntIntEventInputNegativeA() { machine.transitionStateWhen(-1, 0, EventInput.never); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenIntIntEventInputNegativeB() { machine.transitionStateWhen(0, -1, EventInput.never); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenIntIntEventInputHighA() { machine.transitionStateWhen(names.length, 0, EventInput.never); } @Test(expected = IllegalArgumentException.class) public void testTransitionStateWhenIntIntEventInputHighB() { machine.transitionStateWhen(0, names.length, EventInput.never); } private int expectedOld = -1, expectedNew = -1; @Test public void testGetStateExitEnterEvent() { EventInput exit = machine.getStateExitEvent(); EventInput enter = machine.getStateEnterEvent(); tryStateEnterExit(exit, enter); } @Test public void testOnStateExitEnter() { EventCell exit = new EventCell(), enter = new EventCell(); machine.onStateExit(exit); machine.onStateEnter(enter); tryStateEnterExit(exit, enter); } private void tryStateEnterExit(EventInput exit, EventInput enter) { CountingEventOutput cexit = new CountingEventOutput(); CountingEventOutput center = new CountingEventOutput(); exit.send(cexit); // expect enter AFTER exit exit.send(() -> center.ifExpected = true); // expect state is still the old state exit.send(() -> assertEquals(expectedOld, machine.getState())); enter.send(center); // expect state is nstate enter.send(() -> assertEquals(expectedNew, machine.getState())); for (int i = 0; i < names.length * 4; i++) { // +1 is to make sure that we don't start with zero int state = (i + 1) % names.length; expectedOld = machine.getState(); expectedNew = state; cexit.ifExpected = true; machine.setState(state); cexit.check(); center.check(); // make sure that it doesn't happen if it's the same state machine.setState(state); } } @Test public void testOnExitEnterStateString() { for (int target = 0; target < names.length; target++) { for (int source = 0; source < names.length; source++) { if (source == target) { continue; } // this is hacky, but it works! ((EventCell) machine.getStateEnterEvent()).__UNSAFE_clearListeners(); ((EventCell) machine.getStateExitEvent()).__UNSAFE_clearListeners(); // the point is that otherwise, the CountingEventOutputs will // start being annoyed next cycle around final int ftarget = target; CountingEventOutput center1 = new CountingEventOutput(); CountingEventOutput center2 = new CountingEventOutput(); machine.onEnterState(names[target]).send(center1); machine.onEnterState(names[target]).send(() -> assertEquals(ftarget, machine.getState())); machine.onEnterState(names[target], center2); machine.onEnterState(names[target], () -> assertEquals(ftarget, machine.getState())); final int fsource = source; CountingEventOutput cexit1 = new CountingEventOutput(); CountingEventOutput cexit2 = new CountingEventOutput(); machine.onExitState(names[source]).send(cexit1); machine.onExitState(names[source]).send(() -> center1.ifExpected = true); machine.onExitState(names[source]).send(() -> assertEquals(fsource, machine.getState())); machine.onExitState(names[source], cexit2); machine.onExitState(names[source], () -> center2.ifExpected = true); machine.onExitState(names[source], () -> assertEquals(fsource, machine.getState())); machine.setState(source); cexit1.ifExpected = cexit2.ifExpected = true; machine.setState(target); cexit1.check(); cexit2.check(); center1.check(); center2.check(); } } } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateStringNonexistent() { machine.onEnterState(NOT_A_NAME); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateStringNonexistent() { machine.onExitState(NOT_A_NAME); } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateStringEventOutputNonexistent() { machine.onEnterState(NOT_A_NAME, EventOutput.ignored); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateStringEventOutputNonexistent() { machine.onExitState(NOT_A_NAME, EventOutput.ignored); } @Test public void testOnExitEnterStateInt() { for (int target = 0; target < names.length; target++) { for (int source = 0; source < names.length; source++) { if (source == target) { continue; } // this is hacky, but it works! ((EventCell) machine.getStateEnterEvent()).__UNSAFE_clearListeners(); ((EventCell) machine.getStateExitEvent()).__UNSAFE_clearListeners(); // the point is that otherwise, the CountingEventOutputs will // start being annoyed next cycle around final int ftarget = target; CountingEventOutput center1 = new CountingEventOutput(); CountingEventOutput center2 = new CountingEventOutput(); machine.onEnterState(target).send(center1); machine.onEnterState(target).send(() -> assertEquals(ftarget, machine.getState())); machine.onEnterState(target, center2); machine.onEnterState(target, () -> assertEquals(ftarget, machine.getState())); final int fsource = source; CountingEventOutput cexit1 = new CountingEventOutput(); CountingEventOutput cexit2 = new CountingEventOutput(); machine.onExitState(source).send(cexit1); machine.onExitState(source).send(() -> center1.ifExpected = true); machine.onExitState(source).send(() -> assertEquals(fsource, machine.getState())); machine.onExitState(source, cexit2); machine.onExitState(source, () -> center2.ifExpected = true); machine.onExitState(source, () -> assertEquals(fsource, machine.getState())); machine.setState(source); cexit1.ifExpected = cexit2.ifExpected = true; machine.setState(target); cexit1.check(); cexit2.check(); center1.check(); center2.check(); } } } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateIntNegative() { machine.onEnterState(-1); } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateIntHigh() { machine.onEnterState(names.length); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateIntNegative() { machine.onExitState(-1); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateIntHigh() { machine.onExitState(names.length); } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateIntEventOutputNegative() { machine.onEnterState(-1, EventOutput.ignored); } @Test(expected = IllegalArgumentException.class) public void testOnEnterStateIntEventOutputHigh() { machine.onEnterState(names.length, EventOutput.ignored); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateIntEventOutputNegative() { machine.onExitState(-1, EventOutput.ignored); } @Test(expected = IllegalArgumentException.class) public void testOnExitStateIntEventOutputHigh() { machine.onExitState(names.length, EventOutput.ignored); } // And now some higher-level tests @Test public void testDoor() { // Setup door StateMachine door = new StateMachine(0, "CLOSED", "OPEN", "EXPLODED"); BooleanInput isOpen = door.getIsState("OPEN"); EventCell doOpen = new EventCell(), doClose = new EventCell(); door.setStateWhen("OPEN", doOpen); door.setStateWhen("CLOSED", doClose); assertFalse(isOpen.get()); doClose.event(); assertFalse(isOpen.get()); doOpen.event(); assertTrue(isOpen.get()); doOpen.event(); assertTrue(isOpen.get()); doClose.event(); assertFalse(isOpen.get()); doOpen.event(); assertTrue(isOpen.get()); doClose.event(); assertFalse(isOpen.get()); doOpen.event(); assertTrue(isOpen.get()); EventOutput txv = door.getStateTransitionEvent("CLOSED", "EXPLODED"); BooleanCell hasExplodedEver = new BooleanCell(); BooleanCell hasNotUnexplodedEver = new BooleanCell(true); FloatCell timeDilationConstant = new FloatCell(1.0f); door.onEnterState("EXPLODED", hasExplodedEver.eventSet(true)); door.onExitState("EXPLODED", hasNotUnexplodedEver.eventSet(false)); door.onEnterState("EXPLODED", timeDilationConstant.eventSet(0.1f)); door.onExitState("EXPLODED", timeDilationConstant.eventSet(1.0f)); assertFalse(door.isState("EXPLODED")); assertFalse(hasExplodedEver.get()); assertTrue(hasNotUnexplodedEver.get()); assertTrue(timeDilationConstant.get() == 1.0f); txv.event(); assertFalse(door.isState("EXPLODED")); assertFalse(hasExplodedEver.get()); assertTrue(hasNotUnexplodedEver.get()); assertTrue(timeDilationConstant.get() == 1.0f); doClose.event(); assertFalse(door.isState("EXPLODED")); assertFalse(hasExplodedEver.get()); assertTrue(hasNotUnexplodedEver.get()); assertTrue(timeDilationConstant.get() == 1.0f); txv.event(); assertTrue(door.isState("EXPLODED")); assertTrue(hasExplodedEver.get()); assertTrue(hasNotUnexplodedEver.get()); assertTrue(timeDilationConstant.get() == 0.1f); doOpen.event(); assertFalse(door.isState("EXPLODED")); assertTrue(hasExplodedEver.get()); assertFalse(hasNotUnexplodedEver.get()); assertTrue(timeDilationConstant.get() == 1.0f); } @Test public void testTurnstile() { // Setup turnstile StateMachine turnstile = new StateMachine("LOCKED", "LOCKED", "UNLOCKED"); EventCell insertCoin = new EventCell(); EventCell pushThrough = new EventCell(); BooleanCell isLocked = new BooleanCell(true); BooleanCell gotThrough = new BooleanCell(); gotThrough.setTrueWhen(pushThrough.andNot(isLocked)); turnstile.transitionStateWhen("LOCKED", "UNLOCKED", insertCoin); turnstile.transitionStateWhen("UNLOCKED", "LOCKED", pushThrough); turnstile.onEnterState("UNLOCKED", isLocked.eventSet(false)); turnstile.onExitState("UNLOCKED", isLocked.eventSet(true)); final int[] totalEntriesExits = new int[4]; turnstile.onEnterState("LOCKED").send(new EventOutput() { @Override public void event() { totalEntriesExits[0]++; } }); turnstile.onEnterState("UNLOCKED").send(new EventOutput() { @Override public void event() { totalEntriesExits[1]++; } }); turnstile.onExitState("LOCKED").send(new EventOutput() { @Override public void event() { totalEntriesExits[2]++; } }); turnstile.onExitState("UNLOCKED").send(new EventOutput() { @Override public void event() { totalEntriesExits[3]++; } }); // Test turnstile assertTrue(isLocked.get()); assertFalse(gotThrough.get()); pushThrough.event(); assertTrue(isLocked.get()); assertFalse(gotThrough.get()); pushThrough.event(); assertTrue(isLocked.get()); assertFalse(gotThrough.get()); insertCoin.event(); assertFalse(isLocked.get()); assertFalse(gotThrough.get()); insertCoin.event(); assertFalse(isLocked.get()); assertFalse(gotThrough.get()); pushThrough.event(); assertTrue(isLocked.get()); assertTrue(gotThrough.get()); gotThrough.set(false); pushThrough.event(); assertTrue(isLocked.get()); assertFalse(gotThrough.get()); insertCoin.event(); insertCoin.event(); insertCoin.event(); assertFalse(isLocked.get()); assertFalse(gotThrough.get()); pushThrough.event(); assertTrue(isLocked.get()); assertTrue(gotThrough.get()); turnstile.setState("UNLOCKED"); assertFalse(isLocked.get()); turnstile.setState(1); assertFalse(isLocked.get()); turnstile.setState("LOCKED"); assertTrue(isLocked.get()); turnstile.setState("UNLOCKED"); assertFalse(isLocked.get()); turnstile.setState(0); assertTrue(isLocked.get()); turnstile.setState("LOCKED"); assertTrue(isLocked.get()); turnstile.setState("UNLOCKED"); assertFalse(isLocked.get()); assertEquals("Wrong total number of LOCKED entrances!", totalEntriesExits[0], 4); assertEquals("Wrong total number of UNLOCKED entrances!", totalEntriesExits[1], 5); assertEquals("Wrong total number of LOCKED exits!", totalEntriesExits[2], 5); assertEquals("Wrong total number of UNLOCKED exits!", totalEntriesExits[3], 4); } @Test public void testGandalf() { StateMachine gandalf = new StateMachine(0, "ALIVE", "MIA", "DEAD"); BooleanCell died = new BooleanCell(); BooleanCell missing = new BooleanCell(); BooleanCell alive = new BooleanCell(); final int[] changes = new int[4]; gandalf.onStateEnter(new EventOutput() { @Override public void event() { changes[0]++; } }); gandalf.onStateExit(new EventOutput() { @Override public void event() { changes[1]++; } }); gandalf.getStateEnterEvent().send(new EventOutput() { @Override public void event() { changes[2]++; } }); gandalf.getStateExitEvent().send(new EventOutput() { @Override public void event() { changes[3]++; } }); gandalf.getIsState("DEAD").send(died); gandalf.onEnterState("MIA", missing.eventSet(true)); gandalf.onExitState("MIA", missing.eventSet(false)); gandalf.onEnterState("ALIVE", alive.eventSet(true)); gandalf.onExitState("ALIVE", alive.eventSet(false)); assertEquals(gandalf.getStateName(0), "ALIVE"); assertEquals(gandalf.getStateName(1), "MIA"); assertEquals(gandalf.getStateName(2), "DEAD"); assertFalse(died.get()); assertFalse(missing.get()); assertFalse(alive.get()); // try to retrigger events... but already in that state. gandalf.setState(0); assertFalse(died.get()); assertFalse(missing.get()); assertFalse(alive.get()); // actually retrigger events gandalf.setState(1); gandalf.setState(0); assertFalse(died.get()); assertFalse(missing.get()); assertTrue(alive.get()); gandalf.setState(gandalf.getState() + 1); assertFalse(died.get()); assertTrue(missing.get()); assertFalse(alive.get()); gandalf.setState(gandalf.getState() + 1); assertTrue(died.get()); assertFalse(missing.get()); assertFalse(alive.get()); assertEquals("Expected five enterances", changes[0], 4); assertEquals("Expected five exits", changes[1], 4); assertEquals("Expected five enterances", changes[2], 4); assertEquals("Expected five exits", changes[3], 4); } }