/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* 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.strongback.command;
import static org.fest.assertions.Assertions.assertThat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.junit.Before;
import org.junit.Test;
import org.strongback.Logger;
public class CommandGroupTest {
private Scheduler scheduler;
private Queue<String> list;
private Command[] c;
private Command[] d;
@Before
public void beforeEach() {
scheduler = new Scheduler(Logger.noOp(),null);
list = new LinkedList<>();
TestCommand.reset();
c = new Command[10];
for (int i = 0; i < c.length; i++)
c[i] = new TestCommand(list);
DelayCommand.reset();
d = new Command[10];
for (int i = 0; i < d.length; i++)
d[i] = new DelayCommand(list, i);
}
@Test
public void shouldCancelTimedOutCommand() {
scheduler.submit(c[0]);
scheduler.submit(c[1]);
scheduler.submit(d[1]);
assertThat(list).isEmpty();
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin", "C1 init", "C1 exe", "C1 fin", "D1 init", "D1 exe"));
list.clear();
scheduler.execute(1);
assertThat(list).isEqualTo(listOf("D1 exe" ));
list.clear();
scheduler.execute(2);
assertThat(list).isEqualTo(listOf( "D1 exe" ));
list.clear();
scheduler.execute(1000);
assertThat(list).isEqualTo(listOf( "D1 fin" ));
list.clear();
}
@Test
public void shoudKillCommandThatUsesRequirementOfAnother() {
Requirable required = new Requirable() {
};
CommandWithRequirement r0 = new CommandWithRequirement(required, 0, list, true);
CommandWithRequirement r1 = new CommandWithRequirement(required, 1, list, false);
CommandWithRequirement r2 = new CommandWithRequirement(required, 2, list, true);
scheduler.submit(r0);
scheduler.submit(r1);
scheduler.submit(r2);
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("R0 inter", "R1 init", "R1 exe", "R1 end" ));
list.clear();
}
@Test
public void shoudKillCommandGroupThatUsesRequirementOfAnother() {
Requirable required = new Requirable() {
@Override
public String toString() {
return "R0";
}
};
CommandWithRequirement r0 = new CommandWithRequirement(required, 0, list, true);
CommandWithRequirement r1 = new CommandWithRequirement(required, 1, list, true);
CommandWithRequirement r2 = new CommandWithRequirement(required, 2, list, true);
RequiredGroup g = new RequiredGroup(r0, r1, r2);
CommandWithRequirement r3 = new CommandWithRequirement(required, 3, list, false);
scheduler.submit(g);
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("R0 init", "R0 exe", "R0 end" ));
list.clear();
scheduler.submit(r3);
scheduler.execute(1);
assertThat(list).isEqualTo(listOf("R1 inter", "R3 init", "R3 exe", "R3 end" ));
list.clear();
scheduler.execute(2);
assertThat(list).isEmpty();
}
private final class RequiredGroup extends CommandGroup {
public RequiredGroup(CommandWithRequirement... commands) {
sequentially(commands);
}
}
@Test
public void shouldTimeoutDiagramFromBoard() {
scheduler.submit(new DiagramFromBoard());
// Nothing has executed yet
assertThat(list).isEmpty();
// First step should execute 0
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin" ));
list.clear();
// Second step is a branch, nothing was executed, fork was primed
scheduler.execute(1);
assertThat(list).isEmpty();
// Third step should execute 1 and 2 (parallelized) and 3 (just forked)
scheduler.execute(2);
assertThat(list).isEqualTo(listOf("C1 init", "C1 exe", "C1 fin", "C2 init", "C2 exe", "C2 fin", "C3 init", "C3 exe",
"C3 fin" ));
list.clear();
// Fourth step should execute 5 (main) and 4 (on fork)
scheduler.execute(3);
assertThat(list)
.isEqualTo(listOf("C5 init", "C5 exe", "C5 fin", "C4 init", "C4 exe", "C4 fin" ));
list.clear();
// Fifth step is a branch, nothing was executed fork is dead, another fork was primed
scheduler.execute(4);
assertThat(list).isEmpty();
// Sixth step should execute 6 (main) and 7 (just forked)
scheduler.execute(5);
assertThat(list)
.isEqualTo(listOf("C6 init", "C6 exe", "C6 fin", "C7 init", "C7 exe", "C7 fin" ));
list.clear();
// Seventh step scheduler is empty, nothing executes
scheduler.execute(6);
assertThat(list).isEmpty();
}
@Test
public void shouldExecuteCommandsInOrder() {
Command c = new SeqCommandGroup();
scheduler.submit(c);
assertThat(list).isEmpty();
// First step, C0 should have been added, run, and finished
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin" ));
// Second step, C1 should have been added, run, and finished
scheduler.execute(0);
assertThat(list)
.isEqualTo(listOf("C0 init", "C0 exe", "C0 fin", "C1 init", "C1 exe", "C1 fin" ));
// Third step, C2 should have been added, run, and finished
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin", "C1 init", "C1 exe", "C1 fin", "C2 init", "C2 exe",
"C2 fin" ));
}
private final class SeqCommandGroup extends CommandGroup {
@SuppressWarnings("synthetic-access")
public SeqCommandGroup() {
sequentially(c[0], c[1], c[2]);
}
}
@Test
public void shouldExecuteCommandsTogether() {
Command c = new SimulCommandGroup();
scheduler.submit(c);
scheduler.execute(0);
// After one step, all three should have run
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin", "C1 init", "C1 exe", "C1 fin", "C2 init", "C2 exe",
"C2 fin" ));
}
private final class SimulCommandGroup extends CommandGroup {
@SuppressWarnings("synthetic-access")
public SimulCommandGroup() {
simultaneously(c[0], c[1], c[2]);
}
}
@Test
public void shouldExecuteTwoCommandsTogetherAndOneAfter() {
Command c = new TwoOneGroup();
scheduler.submit(c);
// After one step, first two should have run
scheduler.execute(0);
assertThat(list)
.isEqualTo(listOf("C0 init", "C0 exe", "C0 fin", "C1 init", "C1 exe", "C1 fin" ));
list.clear();
// After two steps, all three should have run
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C2 init", "C2 exe", "C2 fin" ));
}
private final class TwoOneGroup extends CommandGroup {
@SuppressWarnings("synthetic-access")
public TwoOneGroup() {
sequentially(simultaneously(c[0], c[1]), c[2]);
}
}
@Test
public void shouldModelDiagramFromBoard() {
scheduler.submit(new DiagramFromBoard());
// Nothing has executed yet
assertThat(list).isEmpty();
// First step should execute 0
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin" ));
list.clear();
// Second step is a branch, nothing was executed, fork was primed
scheduler.execute(0);
assertThat(list).isEmpty();
// Third step should execute 1 and 2 (parallelized) and 3 (just forked)
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C1 init", "C1 exe", "C1 fin", "C2 init", "C2 exe", "C2 fin", "C3 init", "C3 exe",
"C3 fin" ));
list.clear();
// Fourth step should execute 5 (main) and 4 (on fork)
scheduler.execute(0);
assertThat(list)
.isEqualTo(listOf("C5 init", "C5 exe", "C5 fin", "C4 init", "C4 exe", "C4 fin" ));
list.clear();
// Fifth step is a branch, nothing was executed fork is dead, another fork was primed
scheduler.execute(0);
assertThat(list).isEmpty();
// Sixth step should execute 6 (main) and 7 (just forked)
scheduler.execute(0);
assertThat(list)
.isEqualTo(listOf("C6 init", "C6 exe", "C6 fin", "C7 init", "C7 exe", "C7 fin" ));
list.clear();
// Seventh step scheduler is empty, nothing executes
scheduler.execute(0);
assertThat(list).isEmpty();
}
private final class DiagramFromBoard extends CommandGroup {
@SuppressWarnings("synthetic-access")
public DiagramFromBoard() {
sequentially(c[0], fork(sequentially(c[3], c[4])), simultaneously(c[1], c[2]), c[5], fork(c[7]), c[6]);
}
}
@Test
public void shouldModelOtherDiagramFromBoard() {
scheduler.submit(new OtherDiagramFromBoard());
// Nothing has executed yet
assertThat(list).isEmpty();
// First step should execute 0
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C0 init", "C0 exe", "C0 fin" ));
list.clear();
// Second step is a branch, nothing executes, fork is primed
scheduler.execute(0);
assertThat(list).isEmpty();
// Third step should execute 3 (main) and 1 and 2 (just forked parallelized)
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C3 init", "C3 exe", "C3 fin", "C1 init", "C1 exe", "C1 fin", "C2 init", "C2 exe",
"C2 fin" ));
list.clear();
// Fourth step should execute 4 and 5 (parallelized) fork is dead
scheduler.execute(0);
assertThat(list)
.isEqualTo(listOf("C4 init", "C4 exe", "C4 fin", "C5 init", "C5 exe", "C5 fin" ));
list.clear();
// Fifth step should execute 6
scheduler.execute(0);
assertThat(list).isEqualTo(listOf("C6 init", "C6 exe", "C6 fin" ));
list.clear();
// Sixth step scheduler is empty, nothing executed
scheduler.execute(0);
assertThat(list).isEmpty();
}
private <T> List<T> listOf(@SuppressWarnings("unchecked") T... values) {
return Arrays.asList(values);
}
private final class OtherDiagramFromBoard extends CommandGroup {
@SuppressWarnings("synthetic-access")
public OtherDiagramFromBoard() {
sequentially(c[0], fork(simultaneously(c[1], c[2])), c[3], simultaneously(c[4], c[5]), c[6]);
}
}
private static final class TestCommand extends Command {
private static int commandID = 0;
public static void reset() {
commandID = 0;
}
private final Queue<String> list;
private final int id;
/**
* @param list the list to log to
*/
public TestCommand(Queue<String> list) {
this.list = list;
id = commandID;
commandID++;
}
@Override
public void initialize() {
list.offer("C" + id + " init");
}
@Override
public boolean execute() {
list.offer("C" + id + " exe");
return true;
}
@Override
public void end() {
list.offer("C" + id + " fin");
}
@Override
public String toString() {
return "C" + id;
}
}
private static final class DelayCommand extends Command {
private static int commandID = 0;
public static void reset() {
commandID = 0;
}
private final Queue<String> list;
private final int id;
/**
* @param list the list to log to
* @param length how long to delay
*/
public DelayCommand(Queue<String> list, double length) {
super(length);
this.list = list;
id = commandID;
commandID++;
}
@Override
public void initialize() {
list.offer("D" + id + " init");
}
@Override
public boolean execute() {
list.offer("D" + id + " exe");
return false;
}
@Override
public void end() {
list.offer("D" + id + " fin");
}
@Override
public String toString() {
return "D" + id;
}
}
private final class CommandWithRequirement extends Command {
private final Queue<String> list;
private final int number;
public CommandWithRequirement(Requirable required, int number, Queue<String> list, boolean interruptible) {
super(required);
this.list = list;
this.number = number;
if (!interruptible) setNotInterruptible();
}
@Override
public void initialize() {
list.offer("R" + number + " init");
}
@Override
public boolean execute() {
list.offer("R" + number + " exe");
return true;
}
@Override
public void interrupted() {
list.offer("R" + number + " inter");
}
@Override
public void end() {
list.offer("R" + number + " end");
}
@Override
public String toString() {
return "R" + number;
}
}
}