content: /**
   * 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);
    }
  }
  