Files
allwpilib/commandsv3/src/test/java/org/wpilib/command3/SchedulerConflictTests.java

157 lines
5.8 KiB
Java
Raw Normal View History

// 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.
2025-11-07 19:55:43 -05:00
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 Mechanism("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 Mechanism("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 Mechanism("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 Mechanism("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");
}
}