/**
* 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.scheduler.state;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.aurora.gen.ScheduleStatus;
import org.apache.aurora.gen.ScheduledTask;
import org.apache.aurora.scheduler.base.TaskTestUtil;
import org.apache.aurora.scheduler.base.Tasks;
import org.apache.aurora.scheduler.state.SideEffect.Action;
import org.apache.aurora.scheduler.state.TaskStateMachine.TaskState;
import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
import org.junit.Before;
import org.junit.Test;
import static org.apache.aurora.scheduler.state.StateChangeResult.ILLEGAL;
import static org.apache.aurora.scheduler.state.StateChangeResult.ILLEGAL_WITH_SIDE_EFFECTS;
import static org.apache.aurora.scheduler.state.StateChangeResult.NOOP;
import static org.apache.aurora.scheduler.state.StateChangeResult.SUCCESS;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.ASSIGNED;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.DELETED;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.DRAINING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.FAILED;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.FINISHED;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.INIT;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.KILLED;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.KILLING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.LOST;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.PENDING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.PREEMPTING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.RESTARTING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.RUNNING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.STARTING;
import static org.apache.aurora.scheduler.state.TaskStateMachine.TaskState.THROTTLED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
// TODO(wfarner): At this rate, it's probably best to exhaustively cover this class with a matrix
// from every state to every state.
public class TaskStateMachineTest {
private TaskStateMachine stateMachine;
@Before
public void setUp() {
stateMachine = makeStateMachine(makeTask(false));
}
private TaskStateMachine makeStateMachine(ScheduledTask builder) {
return new TaskStateMachine(IScheduledTask.build(builder));
}
@Test
public void testSimpleTransition() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING, FINISHED);
legalTransition(DELETED, Action.DELETE);
}
@Test
public void testServiceRescheduled() {
stateMachine = makeStateMachine(makeTask(true));
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(FINISHED, Action.SAVE_STATE, Action.RESCHEDULE);
}
@Test
public void testPostTerminalTransitionDenied() {
for (ScheduleStatus endState : Tasks.TERMINAL_STATES) {
stateMachine = makeStateMachine(makeTask(false));
Set<SideEffect.Action> finalActions = Sets.newHashSet(Action.SAVE_STATE);
switch (endState) {
case FAILED:
finalActions.add(Action.INCREMENT_FAILURES);
break;
case FINISHED:
break;
case KILLED:
case LOST:
finalActions.add(Action.RESCHEDULE);
break;
case KILLING:
finalActions.add(Action.KILL);
break;
default:
fail("Unknown state " + endState);
break;
}
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(TaskState.valueOf(endState.name()), finalActions);
for (ScheduleStatus badTransition : Tasks.TERMINAL_STATES) {
if (endState.equals(badTransition)) {
assertEquals(NOOP, stateMachine.updateState(Optional.of(badTransition)).getResult());
} else {
illegalTransition(TaskState.valueOf(badTransition.name()));
}
}
}
}
@Test
public void testTerminalToDeleted() {
for (ScheduleStatus endState : Tasks.TERMINAL_STATES) {
stateMachine = makeStateMachine(makeTask(false));
Set<SideEffect.Action> finalActions = Sets.newHashSet(Action.SAVE_STATE);
switch (endState) {
case FAILED:
finalActions.add(Action.INCREMENT_FAILURES);
break;
case FINISHED:
break;
case KILLED:
case LOST:
finalActions.add(Action.RESCHEDULE);
break;
case KILLING:
finalActions.add(Action.KILL);
break;
default:
fail("Unknown state " + endState);
break;
}
expectUpdateStateOnTransitionTo(
PENDING,
ASSIGNED,
STARTING,
RUNNING);
legalTransition(TaskState.valueOf(endState.name()), finalActions);
legalTransition(DELETED, Action.DELETE);
}
}
@Test
public void testUnknownTask() {
stateMachine = new TaskStateMachine("id");
illegalTransition(RUNNING, Action.KILL);
}
@Test
public void testLostTask() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(LOST, Action.SAVE_STATE, Action.RESCHEDULE);
}
@Test
public void testKilledPending() {
expectUpdateStateOnTransitionTo(PENDING);
legalTransition(KILLING, Action.DELETE);
}
@Test
public void testRestartedTask() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(RESTARTING, Action.SAVE_STATE, Action.KILL);
legalTransition(FINISHED, Action.SAVE_STATE, Action.RESCHEDULE);
}
@Test
public void testRogueRestartedTask() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(RESTARTING, Action.SAVE_STATE, Action.KILL);
illegalTransition(RUNNING, Action.KILL);
}
@Test
public void testPendingRestartedTask() {
expectUpdateStateOnTransitionTo(PENDING);
// PENDING -> RESTARTING should not be allowed.
illegalTransition(RESTARTING);
}
@Test
public void testDrainedTask() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(DRAINING, Action.SAVE_STATE, Action.KILL);
legalTransition(FINISHED, Action.SAVE_STATE, Action.RESCHEDULE);
}
@Test
public void testRogueDrainedTask() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(DRAINING, Action.SAVE_STATE, Action.KILL);
illegalTransition(RUNNING, Action.KILL);
}
@Test
public void testPendingDrainedTask() {
expectUpdateStateOnTransitionTo(PENDING);
// PENDING -> DRAINING should not be allowed.
illegalTransition(DRAINING);
}
@Test
public void testAllowsSkipStartingAndRunning() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, FINISHED);
}
@Test
public void testAllowsSkipRunning() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, FINISHED);
}
@Test
public void testHonorsMaxFailures() {
ScheduledTask task = makeTask(false);
task.getAssignedTask().getTask().setMaxTaskFailures(10);
task.setFailureCount(8);
stateMachine = makeStateMachine(task);
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(FAILED, Action.SAVE_STATE, Action.RESCHEDULE, Action.INCREMENT_FAILURES);
ScheduledTask rescheduled = task.deepCopy();
rescheduled.setFailureCount(9);
stateMachine = makeStateMachine(rescheduled);
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(FAILED, Action.SAVE_STATE, Action.INCREMENT_FAILURES);
}
@Test
public void testHonorsUnlimitedFailures() {
ScheduledTask task = makeTask(false);
task.getAssignedTask().getTask().setMaxTaskFailures(-1);
task.setFailureCount(1000);
stateMachine = makeStateMachine(task);
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(FAILED, Action.SAVE_STATE, Action.RESCHEDULE, Action.INCREMENT_FAILURES);
}
@Test
public void testKillingRequest() {
expectUpdateStateOnTransitionTo(PENDING, ASSIGNED, STARTING, RUNNING);
legalTransition(KILLING, Action.KILL, Action.SAVE_STATE);
expectUpdateStateOnTransitionTo(KILLED);
}
@Test
public void testThrottledTask() {
expectUpdateStateOnTransitionTo(THROTTLED, PENDING);
}
private static final Function<Action, SideEffect> TO_SIDE_EFFECT =
action -> new SideEffect(action, Optional.absent());
private void legalTransition(TaskState state, SideEffect.Action... expectedActions) {
legalTransition(state, ImmutableSet.copyOf(expectedActions));
}
private void legalTransition(TaskState state, Set<SideEffect.Action> expectedActions) {
ScheduleStatus previousState = stateMachine.getPreviousState();
TransitionResult result = stateMachine.updateState(state.getStatus());
assertEquals("Transition to " + state + " was not successful", SUCCESS, result.getResult());
assertNotEquals(previousState, stateMachine.getPreviousState());
assertEquals(
FluentIterable.from(expectedActions).transform(TO_SIDE_EFFECT).toSet(),
result.getSideEffects());
}
private void expectUpdateStateOnTransitionTo(TaskState... states) {
for (TaskState status : states) {
legalTransition(status, Action.SAVE_STATE);
}
}
private void illegalTransition(TaskState state, SideEffect.Action... expectedActions) {
illegalTransition(
state,
FluentIterable.from(
ImmutableSet.copyOf(expectedActions)).transform(TO_SIDE_EFFECT).toSet());
}
private void illegalTransition(TaskState state, Set<SideEffect> sideEffects) {
TransitionResult expected = new TransitionResult(
sideEffects.isEmpty() ? ILLEGAL : ILLEGAL_WITH_SIDE_EFFECTS,
ImmutableSet.copyOf(sideEffects));
assertEquals(expected, stateMachine.updateState(state.getStatus()));
}
private static ScheduledTask makeTask(boolean service) {
ScheduledTask builder = TaskTestUtil.makeTask("test", TaskTestUtil.JOB).newBuilder();
builder.setStatus(INIT.getStatus().get());
builder.getAssignedTask().getTask()
.setMaxTaskFailures(0)
.setIsService(service);
return builder;
}
private static final TransitionResult SAVE = new TransitionResult(
SUCCESS,
ImmutableSet.of(new SideEffect(Action.SAVE_STATE, Optional.absent())));
private static final TransitionResult SAVE_AND_KILL = new TransitionResult(
SUCCESS,
ImmutableSet.of(
new SideEffect(Action.SAVE_STATE, Optional.absent()),
new SideEffect(Action.KILL, Optional.absent())));
private static final TransitionResult SAVE_AND_RESCHEDULE = new TransitionResult(
SUCCESS,
ImmutableSet.of(
new SideEffect(Action.SAVE_STATE, Optional.absent()),
new SideEffect(Action.RESCHEDULE, Optional.absent())));
private static final TransitionResult SAVE_KILL_AND_RESCHEDULE = new TransitionResult(
SUCCESS,
ImmutableSet.of(
new SideEffect(Action.SAVE_STATE, Optional.absent()),
new SideEffect(Action.KILL, Optional.absent()),
new SideEffect(Action.RESCHEDULE, Optional.absent())));
private static final TransitionResult ILLEGAL_KILL = new TransitionResult(
ILLEGAL_WITH_SIDE_EFFECTS,
ImmutableSet.of(new SideEffect(Action.KILL, Optional.absent())));
private static final TransitionResult RECORD_FAILURE = new TransitionResult(
SUCCESS,
ImmutableSet.of(
new SideEffect(Action.SAVE_STATE, Optional.absent()),
new SideEffect(Action.INCREMENT_FAILURES, Optional.absent())));
private static final TransitionResult DELETE_TASK = new TransitionResult(
SUCCESS,
ImmutableSet.of(new SideEffect(Action.DELETE, Optional.absent())));
private static final class TestCase {
private final boolean taskPresent;
private final TaskState from;
private final TaskState to;
private TestCase(boolean taskPresent, TaskState from, TaskState to) {
this.taskPresent = taskPresent;
this.from = from;
this.to = to;
}
@Override
public int hashCode() {
return Objects.hash(taskPresent, from, to);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TestCase)) {
return false;
}
TestCase other = (TestCase) o;
return taskPresent == other.taskPresent
&& from == other.from
&& to == other.to;
}
@Override
public String toString() {
return com.google.common.base.Objects.toStringHelper(this)
.add("taskPresent", taskPresent)
.add("from", from)
.add("to", to)
.toString();
}
}
private static final Map<TestCase, TransitionResult> EXPECTATIONS =
ImmutableMap.<TestCase, TransitionResult>builder()
.put(new TestCase(true, INIT, THROTTLED), SAVE)
.put(new TestCase(true, INIT, PENDING), SAVE)
.put(new TestCase(false, INIT, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, INIT, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, INIT, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, THROTTLED, PENDING), SAVE)
.put(new TestCase(true, THROTTLED, KILLING), DELETE_TASK)
.put(new TestCase(false, THROTTLED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, THROTTLED, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, THROTTLED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, PENDING, ASSIGNED), SAVE)
.put(new TestCase(false, PENDING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, PENDING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, PENDING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, PENDING, KILLING), DELETE_TASK)
.put(new TestCase(false, ASSIGNED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, ASSIGNED, STARTING), SAVE)
.put(new TestCase(false, ASSIGNED, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, ASSIGNED, RUNNING), SAVE)
.put(new TestCase(false, ASSIGNED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, ASSIGNED, FINISHED), SAVE)
.put(new TestCase(true, ASSIGNED, PREEMPTING), SAVE_AND_KILL)
.put(new TestCase(true, ASSIGNED, RESTARTING), SAVE_AND_KILL)
.put(new TestCase(true, ASSIGNED, DRAINING), SAVE_AND_KILL)
.put(new TestCase(true, ASSIGNED, FAILED), RECORD_FAILURE)
.put(new TestCase(true, ASSIGNED, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, ASSIGNED, KILLING), SAVE_AND_KILL)
.put(new TestCase(true, ASSIGNED, LOST), SAVE_KILL_AND_RESCHEDULE)
.put(new TestCase(false, STARTING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, STARTING, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, STARTING, RUNNING), SAVE)
.put(new TestCase(false, STARTING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, STARTING, FINISHED), SAVE)
.put(new TestCase(true, STARTING, PREEMPTING), SAVE_AND_KILL)
.put(new TestCase(true, STARTING, RESTARTING), SAVE_AND_KILL)
.put(new TestCase(true, STARTING, DRAINING), SAVE_AND_KILL)
.put(new TestCase(true, STARTING, FAILED), RECORD_FAILURE)
.put(new TestCase(true, STARTING, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, STARTING, KILLING), SAVE_AND_KILL)
.put(new TestCase(true, STARTING, LOST), SAVE_AND_RESCHEDULE)
.put(new TestCase(false, RUNNING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, RUNNING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, RUNNING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, RUNNING, FINISHED), SAVE)
.put(new TestCase(true, RUNNING, PREEMPTING), SAVE_AND_KILL)
.put(new TestCase(true, RUNNING, RESTARTING), SAVE_AND_KILL)
.put(new TestCase(true, RUNNING, DRAINING), SAVE_AND_KILL)
.put(new TestCase(true, RUNNING, FAILED), RECORD_FAILURE)
.put(new TestCase(true, RUNNING, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, RUNNING, KILLING), SAVE_AND_KILL)
.put(new TestCase(true, RUNNING, LOST), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, FINISHED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, FINISHED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, FINISHED, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, FINISHED, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, FINISHED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, FINISHED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, FINISHED, DELETED), DELETE_TASK)
.put(new TestCase(true, PREEMPTING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, PREEMPTING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, PREEMPTING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, PREEMPTING, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, PREEMPTING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, PREEMPTING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, PREEMPTING, FINISHED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, PREEMPTING, FAILED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, PREEMPTING, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, PREEMPTING, KILLING), SAVE)
.put(new TestCase(true, PREEMPTING, LOST), SAVE_KILL_AND_RESCHEDULE)
.put(new TestCase(true, RESTARTING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, RESTARTING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, RESTARTING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, RESTARTING, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, RESTARTING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, RESTARTING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, RESTARTING, FINISHED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, RESTARTING, FAILED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, RESTARTING, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, RESTARTING, KILLING), SAVE)
.put(new TestCase(true, RESTARTING, LOST), SAVE_KILL_AND_RESCHEDULE)
.put(new TestCase(true, DRAINING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, DRAINING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, DRAINING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, DRAINING, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, DRAINING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, DRAINING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, DRAINING, FINISHED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, DRAINING, FAILED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, DRAINING, KILLED), SAVE_AND_RESCHEDULE)
.put(new TestCase(true, DRAINING, KILLING), SAVE)
.put(new TestCase(true, DRAINING, LOST), SAVE_KILL_AND_RESCHEDULE)
.put(new TestCase(true, FAILED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, FAILED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, FAILED, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, FAILED, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, FAILED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, FAILED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, FAILED, DELETED), DELETE_TASK)
.put(new TestCase(true, KILLED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, KILLED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, KILLED, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, KILLED, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, KILLED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, KILLED, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, KILLED, DELETED), DELETE_TASK)
.put(new TestCase(true, KILLING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, KILLING, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, KILLING, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, KILLING, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, KILLING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, KILLING, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, KILLING, FINISHED), SAVE)
.put(new TestCase(true, KILLING, FAILED), SAVE)
.put(new TestCase(true, KILLING, KILLED), SAVE)
.put(new TestCase(true, KILLING, LOST), SAVE)
.put(new TestCase(true, KILLING, DELETED), DELETE_TASK)
.put(new TestCase(true, LOST, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, LOST, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(true, LOST, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, LOST, STARTING), ILLEGAL_KILL)
.put(new TestCase(true, LOST, RUNNING), ILLEGAL_KILL)
.put(new TestCase(false, LOST, RUNNING), ILLEGAL_KILL)
.put(new TestCase(true, LOST, DELETED), DELETE_TASK)
.put(new TestCase(false, DELETED, ASSIGNED), ILLEGAL_KILL)
.put(new TestCase(false, DELETED, STARTING), ILLEGAL_KILL)
.put(new TestCase(false, DELETED, RUNNING), ILLEGAL_KILL)
.build();
@Test
public void testAllTransitions() {
for (TaskState from : TaskState.values()) {
for (TaskState to : TaskState.values()) {
for (Boolean taskPresent : ImmutableList.of(Boolean.TRUE, Boolean.FALSE)) {
TestCase testCase = new TestCase(taskPresent, from, to);
TransitionResult expectation = EXPECTATIONS.get(testCase);
if (expectation == null) {
if (taskPresent && from.equals(to) || !taskPresent && to.equals(DELETED)) {
expectation = new TransitionResult(NOOP, ImmutableSet.of());
} else {
expectation = new TransitionResult(ILLEGAL, ImmutableSet.of());
}
}
TaskStateMachine machine;
if (taskPresent) {
// Cannot create a state machine for an DELETED task that is in the store.
boolean expectException = from.equals(DELETED);
try {
machine = new TaskStateMachine(
IScheduledTask.build(makeTask(false).setStatus(from.getStatus().get())));
if (expectException) {
fail();
}
} catch (IllegalStateException e) {
if (expectException) {
continue;
} else {
throw e;
}
}
} else {
machine = new TaskStateMachine("name");
}
assertEquals(
"Unexpected behavior for " + testCase,
expectation,
machine.updateState(to.getStatus()));
}
}
}
}
}