mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
162 lines
5.0 KiB
Java
162 lines
5.0 KiB
Java
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
||
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
||
|
|
// the WPILib BSD license file in the root directory of this project.
|
||
|
|
|
||
|
|
package org.wpilib.commands3;
|
||
|
|
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||
|
|
import static org.junit.jupiter.api.Assertions.fail;
|
||
|
|
|
||
|
|
import java.util.List;
|
||
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||
|
|
import org.junit.jupiter.api.Test;
|
||
|
|
|
||
|
|
class SchedulerTest extends CommandTestBase {
|
||
|
|
@Test
|
||
|
|
void basic() {
|
||
|
|
var enabled = new AtomicBoolean(false);
|
||
|
|
var ran = new AtomicBoolean(false);
|
||
|
|
var command =
|
||
|
|
Command.noRequirements()
|
||
|
|
.executing(
|
||
|
|
coroutine -> {
|
||
|
|
do {
|
||
|
|
coroutine.yield();
|
||
|
|
} while (!enabled.get());
|
||
|
|
ran.set(true);
|
||
|
|
})
|
||
|
|
.named("Basic Command");
|
||
|
|
|
||
|
|
m_scheduler.schedule(command);
|
||
|
|
m_scheduler.run();
|
||
|
|
assertTrue(m_scheduler.isRunning(command), "Command should be running after being scheduled");
|
||
|
|
|
||
|
|
enabled.set(true);
|
||
|
|
m_scheduler.run();
|
||
|
|
if (m_scheduler.isRunning(command)) {
|
||
|
|
fail("Command should no longer be running after awaiting its completion");
|
||
|
|
}
|
||
|
|
|
||
|
|
assertTrue(ran.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
@SuppressWarnings("PMD.ImmutableField") // PMD bugs
|
||
|
|
void atomicity() {
|
||
|
|
var mechanism =
|
||
|
|
new Mechanism("X", m_scheduler) {
|
||
|
|
int m_x = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Launch 100 commands that each call `x++` 500 times.
|
||
|
|
// If commands run on different threads, the lack of atomic
|
||
|
|
// operations or locks will mean the final number will be
|
||
|
|
// less than the expected 50,000
|
||
|
|
int numCommands = 100;
|
||
|
|
int iterations = 500;
|
||
|
|
|
||
|
|
for (int cmdCount = 0; cmdCount < numCommands; cmdCount++) {
|
||
|
|
var command =
|
||
|
|
Command.noRequirements()
|
||
|
|
.executing(
|
||
|
|
coroutine -> {
|
||
|
|
for (int i = 0; i < iterations; i++) {
|
||
|
|
mechanism.m_x++;
|
||
|
|
coroutine.yield();
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.named("CountCommand[" + cmdCount + "]");
|
||
|
|
|
||
|
|
m_scheduler.schedule(command);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int i = 0; i < iterations; i++) {
|
||
|
|
m_scheduler.run();
|
||
|
|
}
|
||
|
|
|
||
|
|
assertEquals(numCommands * iterations, mechanism.m_x);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
@SuppressWarnings("PMD.ImmutableField") // PMD bugs
|
||
|
|
void runMechanism() {
|
||
|
|
var example =
|
||
|
|
new Mechanism("Counting", m_scheduler) {
|
||
|
|
int m_x = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
Command countToTen =
|
||
|
|
example
|
||
|
|
.run(
|
||
|
|
coroutine -> {
|
||
|
|
example.m_x = 0;
|
||
|
|
for (int i = 0; i < 10; i++) {
|
||
|
|
coroutine.yield();
|
||
|
|
example.m_x++;
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.named("Count To Ten");
|
||
|
|
|
||
|
|
m_scheduler.schedule(countToTen);
|
||
|
|
for (int i = 0; i < 10; i++) {
|
||
|
|
m_scheduler.run();
|
||
|
|
}
|
||
|
|
m_scheduler.run();
|
||
|
|
|
||
|
|
assertEquals(10, example.m_x);
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void compositionsDoNotNeedRequirements() {
|
||
|
|
var m1 = new Mechanism("M1", m_scheduler);
|
||
|
|
var m2 = new Mechanism("m2", m_scheduler);
|
||
|
|
|
||
|
|
// the group has no requirements, but can schedule child commands that do
|
||
|
|
var group =
|
||
|
|
Command.noRequirements()
|
||
|
|
.executing(
|
||
|
|
co -> {
|
||
|
|
co.awaitAll(
|
||
|
|
m1.run(Coroutine::park).named("M1 Command"),
|
||
|
|
m2.run(Coroutine::park).named("M2 Command"));
|
||
|
|
})
|
||
|
|
.named("Composition");
|
||
|
|
|
||
|
|
m_scheduler.schedule(group);
|
||
|
|
m_scheduler.run(); // start m1 and m2 commands
|
||
|
|
assertEquals(3, m_scheduler.getRunningCommands().size());
|
||
|
|
}
|
||
|
|
|
||
|
|
@Test
|
||
|
|
void nestedMechanisms() {
|
||
|
|
var superstructure =
|
||
|
|
new Mechanism("Superstructure", m_scheduler) {
|
||
|
|
private final Mechanism m_elevator = new Mechanism("Elevator", m_scheduler);
|
||
|
|
private final Mechanism m_arm = new Mechanism("Arm", m_scheduler);
|
||
|
|
|
||
|
|
public Command superCommand() {
|
||
|
|
return run(co -> {
|
||
|
|
co.await(m_elevator.run(Coroutine::park).named("Elevator Subcommand"));
|
||
|
|
co.await(m_arm.run(Coroutine::park).named("Arm Subcommand"));
|
||
|
|
})
|
||
|
|
.named("Super Command");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
m_scheduler.schedule(superstructure.superCommand());
|
||
|
|
m_scheduler.run();
|
||
|
|
assertEquals(
|
||
|
|
List.of(superstructure.m_arm.getDefaultCommand()),
|
||
|
|
superstructure.m_arm.getRunningCommands(),
|
||
|
|
"Arm should only be running its default command");
|
||
|
|
|
||
|
|
// Scheduling something that requires an in-use inner mechanism cancels the outer command
|
||
|
|
m_scheduler.schedule(superstructure.m_elevator.run(Coroutine::park).named("Conflict"));
|
||
|
|
|
||
|
|
m_scheduler.run(); // schedules the default superstructure command
|
||
|
|
m_scheduler.run(); // starts running the default superstructure command
|
||
|
|
assertEquals(List.of(superstructure.getDefaultCommand()), superstructure.getRunningCommands());
|
||
|
|
}
|
||
|
|
}
|