mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
Since there is no longer a requirement for Subsystems/Mechanisms to be registered to the command scheduler via a register() call, Mechanism can be changed to an interface instead to allow for more flexible inheritance structures. Specifically, this would allow compatibility with CTRE swerve (which previously required implementing Subsystem so that it could extend CTRE's base class).
157 lines
5.8 KiB
Java
157 lines
5.8 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.command3;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Supplier;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
class SchedulerConflictTests extends CommandTestBase {
|
|
@Test
|
|
void compositionsCannotAwaitConflictingCommands() {
|
|
var mech = new DummyMechanism("The Mechanism", m_scheduler);
|
|
|
|
var group =
|
|
Command.noRequirements(
|
|
co -> {
|
|
co.awaitAll(
|
|
mech.run(Coroutine::park).named("First"),
|
|
mech.run(Coroutine::park).named("Second"));
|
|
})
|
|
.named("Group");
|
|
|
|
m_scheduler.schedule(group);
|
|
|
|
// Running should attempt to schedule multiple conflicting commands
|
|
var exception = assertThrows(IllegalArgumentException.class, m_scheduler::run);
|
|
assertEquals(
|
|
"Commands running in parallel cannot share requirements: "
|
|
+ "First and Second both require The Mechanism",
|
|
exception.getMessage());
|
|
}
|
|
|
|
@Test
|
|
void innerCommandMayInterruptOtherInnerCommand() {
|
|
var mechanism = new DummyMechanism("The mechanism", m_scheduler);
|
|
var firstRan = new AtomicBoolean(false);
|
|
var secondRan = new AtomicBoolean(false);
|
|
|
|
var first =
|
|
mechanism
|
|
.run(
|
|
c -> {
|
|
firstRan.set(true);
|
|
c.park();
|
|
})
|
|
.named("First");
|
|
|
|
var second =
|
|
mechanism
|
|
.run(
|
|
c -> {
|
|
secondRan.set(true);
|
|
c.park();
|
|
})
|
|
.named("Second");
|
|
|
|
var group =
|
|
Command.noRequirements(
|
|
co -> {
|
|
co.fork(first);
|
|
co.fork(second);
|
|
co.park();
|
|
})
|
|
.named("Group");
|
|
|
|
m_scheduler.schedule(group);
|
|
m_scheduler.run();
|
|
|
|
assertTrue(firstRan.get(), "First child should have run to a yield point");
|
|
assertTrue(secondRan.get(), "Second child should have run to a yield point");
|
|
assertFalse(
|
|
m_scheduler.isScheduledOrRunning(first), "First child should have been interrupted");
|
|
assertTrue(m_scheduler.isRunning(second), "Second child should still be running");
|
|
assertTrue(m_scheduler.isRunning(group), "Group should still be running");
|
|
}
|
|
|
|
@Test
|
|
void nestedOneShotCompositionsAllRunInOneCycle() {
|
|
var runs = new AtomicInteger(0);
|
|
Supplier<Command> makeOneShot =
|
|
() -> Command.noRequirements(_c -> runs.incrementAndGet()).named("One Shot");
|
|
var command =
|
|
Command.noRequirements(
|
|
co -> {
|
|
co.fork(makeOneShot.get());
|
|
co.fork(makeOneShot.get());
|
|
co.fork(
|
|
Command.noRequirements(inner -> inner.fork(makeOneShot.get()))
|
|
.named("Inner"));
|
|
co.fork(
|
|
Command.noRequirements(
|
|
co2 -> {
|
|
co2.fork(makeOneShot.get());
|
|
co2.fork(
|
|
Command.noRequirements(
|
|
co3 -> {
|
|
co3.fork(makeOneShot.get());
|
|
})
|
|
.named("3"));
|
|
})
|
|
.named("2"));
|
|
})
|
|
.named("Command");
|
|
|
|
m_scheduler.schedule(command);
|
|
m_scheduler.run();
|
|
assertEquals(5, runs.get(), "All oneshot commands should have run");
|
|
assertFalse(m_scheduler.isRunning(command), "Command should have exited after one cycle");
|
|
}
|
|
|
|
@Test
|
|
void childConflictsWithHigherPriorityTopLevel() {
|
|
var mechanism = new DummyMechanism("mechanism", m_scheduler);
|
|
var top = mechanism.run(Coroutine::park).withPriority(10).named("Top");
|
|
|
|
// Child conflicts with and is lower priority than the Top command
|
|
// It should not be scheduled, and the parent command should exit immediately
|
|
var child = mechanism.run(Coroutine::park).named("Child");
|
|
var parent = Command.noRequirements(co -> co.await(child)).named("Parent");
|
|
|
|
m_scheduler.schedule(top);
|
|
m_scheduler.schedule(parent);
|
|
m_scheduler.run();
|
|
|
|
assertTrue(m_scheduler.isRunning(top), "Top command should not have been interrupted");
|
|
assertFalse(m_scheduler.isRunning(child), "Conflicting child should not have run");
|
|
assertFalse(m_scheduler.isRunning(parent), "Parent of conflicting child should have exited");
|
|
}
|
|
|
|
@Test
|
|
void childConflictsWithLowerPriorityTopLevel() {
|
|
var mechanism = new DummyMechanism("mechanism", m_scheduler);
|
|
var top = mechanism.run(Coroutine::park).withPriority(-10).named("Top");
|
|
|
|
// Child conflicts with and is higher priority than the Top command
|
|
// It should be scheduled, and the top command should be interrupted
|
|
var child = mechanism.run(Coroutine::park).named("Child");
|
|
var parent = Command.noRequirements(co -> co.await(child)).named("Parent");
|
|
|
|
m_scheduler.schedule(top);
|
|
m_scheduler.schedule(parent);
|
|
m_scheduler.run();
|
|
|
|
assertFalse(m_scheduler.isRunning(top), "Top command should have been interrupted");
|
|
assertTrue(m_scheduler.isRunning(child), "Conflicting child should be running");
|
|
assertTrue(m_scheduler.isRunning(parent), "Parent of conflicting child should be running");
|
|
}
|
|
}
|