[commands] Revamp Interruptible (#4192)

This commit is contained in:
Starlight220
2022-08-30 07:53:47 +03:00
committed by GitHub
parent f2a8d38d2a
commit c3a93fb995
26 changed files with 369 additions and 592 deletions

View File

@@ -44,12 +44,13 @@ class CommandRequirementsTest extends CommandTestBase {
try (CommandScheduler scheduler = new CommandScheduler()) {
Subsystem requirement = new SubsystemBase() {};
MockCommandHolder interruptedHolder = new MockCommandHolder(true, requirement);
Command notInterrupted = interruptedHolder.getMock();
Command notInterrupted =
new RunCommand(() -> {}, requirement)
.withInterruptBehavior(Command.InterruptionBehavior.kCancelIncoming);
MockCommandHolder interrupterHolder = new MockCommandHolder(true, requirement);
Command interrupter = interrupterHolder.getMock();
scheduler.schedule(false, notInterrupted);
scheduler.schedule(notInterrupted);
scheduler.schedule(interrupter);
assertTrue(scheduler.isScheduled(notInterrupted));

View File

@@ -66,7 +66,7 @@ class CommandScheduleTest extends CommandTestBase {
MockCommandHolder command3Holder = new MockCommandHolder(true);
Command command3 = command3Holder.getMock();
scheduler.schedule(true, command1, command2, command3);
scheduler.schedule(command1, command2, command3);
assertTrue(scheduler.isScheduled(command1, command2, command3));
scheduler.run();
assertTrue(scheduler.isScheduled(command1, command2, command3));

View File

@@ -9,19 +9,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.wpilibj2.command.Command.InterruptionBehavior;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.EnumSource;
class SchedulingRecursionTest extends CommandTestBase {
/**
* <a href="https://github.com/wpilibsuite/allwpilib/issues/4259">wpilibsuite/allwpilib#4259</a>.
*/
@ValueSource(booleans = {true, false})
@EnumSource(InterruptionBehavior.class)
@ParameterizedTest
void cancelFromInitialize(boolean interruptible) {
void cancelFromInitialize(InterruptionBehavior interruptionBehavior) {
try (CommandScheduler scheduler = new CommandScheduler()) {
AtomicBoolean hasOtherRun = new AtomicBoolean();
Subsystem requirement = new SubsystemBase() {};
@@ -35,14 +36,18 @@ class SchedulingRecursionTest extends CommandTestBase {
public void initialize() {
scheduler.cancel(this);
}
@Override
public InterruptionBehavior getInterruptionBehavior() {
return interruptionBehavior;
}
};
Command other = new RunCommand(() -> hasOtherRun.set(true), requirement);
assertDoesNotThrow(
() -> {
scheduler.schedule(interruptible, selfCancels);
scheduler.schedule(selfCancels);
scheduler.run();
// interruptibility of new arrival isn't checked
scheduler.schedule(other);
});
assertFalse(scheduler.isScheduled(selfCancels));
@@ -52,9 +57,9 @@ class SchedulingRecursionTest extends CommandTestBase {
}
}
@ValueSource(booleans = {true, false})
@EnumSource(InterruptionBehavior.class)
@ParameterizedTest
void defaultCommand(boolean interruptible) {
void defaultCommandGetsRescheduledAfterSelfCanceling(InterruptionBehavior interruptionBehavior) {
try (CommandScheduler scheduler = new CommandScheduler()) {
AtomicBoolean hasOtherRun = new AtomicBoolean();
Subsystem requirement = new SubsystemBase() {};
@@ -68,13 +73,18 @@ class SchedulingRecursionTest extends CommandTestBase {
public void initialize() {
scheduler.cancel(this);
}
@Override
public InterruptionBehavior getInterruptionBehavior() {
return interruptionBehavior;
}
};
Command other = new RunCommand(() -> hasOtherRun.set(true), requirement);
scheduler.setDefaultCommand(requirement, other);
assertDoesNotThrow(
() -> {
scheduler.schedule(interruptible, selfCancels);
scheduler.schedule(selfCancels);
scheduler.run();
});
scheduler.run();
@@ -161,12 +171,13 @@ class SchedulingRecursionTest extends CommandTestBase {
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void scheduleInitializeFromDefaultCommand(boolean interruptible) {
@EnumSource(InterruptionBehavior.class)
void scheduleInitializeFromDefaultCommand(InterruptionBehavior interruptionBehavior) {
try (CommandScheduler scheduler = new CommandScheduler()) {
AtomicInteger counter = new AtomicInteger();
Subsystem requirement = new SubsystemBase() {};
Command other = new InstantCommand(() -> {}, requirement);
Command other =
new InstantCommand(() -> {}, requirement).withInterruptBehavior(interruptionBehavior);
Command defaultCommand =
new CommandBase() {
{
@@ -176,7 +187,7 @@ class SchedulingRecursionTest extends CommandTestBase {
@Override
public void initialize() {
counter.incrementAndGet();
scheduler.schedule(interruptible, other);
scheduler.schedule(other);
}
};

View File

@@ -31,11 +31,11 @@ class ButtonTest extends CommandTestBase {
button.setPressed(false);
button.whenPressed(command1);
scheduler.run();
verify(command1, never()).schedule(true);
verify(command1, never()).schedule();
button.setPressed(true);
scheduler.run();
scheduler.run();
verify(command1).schedule(true);
verify(command1).schedule();
}
@Test
@@ -48,11 +48,11 @@ class ButtonTest extends CommandTestBase {
button.setPressed(true);
button.whenReleased(command1);
scheduler.run();
verify(command1, never()).schedule(true);
verify(command1, never()).schedule();
button.setPressed(false);
scheduler.run();
scheduler.run();
verify(command1).schedule(true);
verify(command1).schedule();
}
@Test
@@ -65,11 +65,11 @@ class ButtonTest extends CommandTestBase {
button.setPressed(false);
button.whileHeld(command1);
scheduler.run();
verify(command1, never()).schedule(true);
verify(command1, never()).schedule();
button.setPressed(true);
scheduler.run();
scheduler.run();
verify(command1, times(2)).schedule(true);
verify(command1, times(2)).schedule();
button.setPressed(false);
scheduler.run();
verify(command1).cancel();
@@ -85,11 +85,11 @@ class ButtonTest extends CommandTestBase {
button.setPressed(false);
button.whenHeld(command1);
scheduler.run();
verify(command1, never()).schedule(true);
verify(command1, never()).schedule();
button.setPressed(true);
scheduler.run();
scheduler.run();
verify(command1).schedule(true);
verify(command1).schedule();
button.setPressed(false);
scheduler.run();
verify(command1).cancel();
@@ -105,12 +105,12 @@ class ButtonTest extends CommandTestBase {
button.setPressed(false);
button.toggleWhenPressed(command1);
scheduler.run();
verify(command1, never()).schedule(true);
verify(command1, never()).schedule();
button.setPressed(true);
scheduler.run();
when(command1.isScheduled()).thenReturn(true);
scheduler.run();
verify(command1).schedule(true);
verify(command1).schedule();
button.setPressed(false);
scheduler.run();
verify(command1, never()).cancel();
@@ -200,13 +200,13 @@ class ButtonTest extends CommandTestBase {
button.setPressed(true);
scheduler.run();
verify(command, never()).schedule(true);
verify(command, never()).schedule();
SimHooks.stepTiming(0.3);
button.setPressed(true);
scheduler.run();
verify(command).schedule(true);
verify(command).schedule();
}
@Test

View File

@@ -37,10 +37,10 @@ class NetworkButtonTest extends CommandTestBase {
entry.setBoolean(false);
button.whenPressed(command);
scheduler.run();
verify(command, never()).schedule(true);
verify(command, never()).schedule();
entry.setBoolean(true);
scheduler.run();
scheduler.run();
verify(command).schedule(true);
verify(command).schedule();
}
}

View File

@@ -7,6 +7,7 @@
#include "CommandTestBase.h"
#include "frc2/command/CommandScheduler.h"
#include "frc2/command/ConditionalCommand.h"
#include "frc2/command/FunctionalCommand.h"
#include "frc2/command/InstantCommand.h"
#include "frc2/command/ParallelCommandGroup.h"
#include "frc2/command/ParallelDeadlineGroup.h"
@@ -49,26 +50,35 @@ TEST_F(CommandRequirementsTest, RequirementUninterruptible) {
TestSubsystem requirement;
MockCommand command1({&requirement});
MockCommand command2({&requirement});
int initCounter = 0;
int exeCounter = 0;
int endCounter = 0;
EXPECT_CALL(command1, Initialize());
EXPECT_CALL(command1, Execute()).Times(2);
EXPECT_CALL(command1, End(true)).Times(0);
EXPECT_CALL(command1, End(false)).Times(0);
std::unique_ptr<Command> command1 =
FunctionalCommand([&initCounter] { initCounter++; },
[&exeCounter] { exeCounter++; },
[&endCounter](bool interruptible) { endCounter++; },
[] { return false; }, {&requirement})
.WithInterruptBehavior(
Command::InterruptionBehavior::kCancelIncoming);
MockCommand command2({&requirement});
EXPECT_CALL(command2, Initialize()).Times(0);
EXPECT_CALL(command2, Execute()).Times(0);
EXPECT_CALL(command2, End(true)).Times(0);
EXPECT_CALL(command2, End(false)).Times(0);
scheduler.Schedule(false, &command1);
scheduler.Schedule(command1.get());
EXPECT_EQ(1, initCounter);
scheduler.Run();
EXPECT_TRUE(scheduler.IsScheduled(&command1));
EXPECT_EQ(1, exeCounter);
EXPECT_TRUE(scheduler.IsScheduled(command1.get()));
scheduler.Schedule(&command2);
EXPECT_TRUE(scheduler.IsScheduled(&command1));
EXPECT_TRUE(scheduler.IsScheduled(command1.get()));
EXPECT_FALSE(scheduler.IsScheduled(&command2));
scheduler.Run();
EXPECT_EQ(2, exeCounter);
EXPECT_EQ(0, endCounter);
}
TEST_F(CommandRequirementsTest, DefaultCommandRequirementError) {

View File

@@ -10,6 +10,7 @@
#include <frc/simulation/DriverStationSim.h>
#include "frc2/command/CommandGroupBase.h"
#include "frc2/command/CommandHelper.h"
#include "frc2/command/CommandScheduler.h"
#include "frc2/command/SetUtilities.h"
#include "frc2/command/SubsystemBase.h"
@@ -26,7 +27,10 @@ class CommandTestBase : public ::testing::Test {
class TestSubsystem : public SubsystemBase {};
protected:
class MockCommand : public Command {
/**
* NOTE: Moving mock objects causes EXPECT_CALL to not work correctly!
*/
class MockCommand : public CommandHelper<CommandBase, MockCommand> {
public:
MOCK_CONST_METHOD0(GetRequirements, wpi::SmallSet<Subsystem*, 4>());
MOCK_METHOD0(IsFinished, bool());
@@ -65,7 +69,7 @@ class CommandTestBase : public ::testing::Test {
.WillRepeatedly(::testing::Return(m_requirements));
}
MockCommand(const MockCommand& other) : Command{other} {}
MockCommand(const MockCommand& other) : CommandHelper{other} {}
void SetFinished(bool finished) {
EXPECT_CALL(*this, IsFinished())
@@ -77,11 +81,6 @@ class CommandTestBase : public ::testing::Test {
scheduler.Cancel(this);
}
protected:
std::unique_ptr<Command> TransferOwnership() && { // NOLINT
return std::make_unique<MockCommand>(std::move(*this));
}
private:
wpi::SmallSet<Subsystem*, 4> m_requirements;
};

View File

@@ -3,43 +3,51 @@
// the WPILib BSD license file in the root directory of this project.
#include "CommandTestBase.h"
#include "frc2/command/Command.h"
#include "frc2/command/CommandHelper.h"
#include "frc2/command/RunCommand.h"
#include "gtest/gtest.h"
using namespace frc2;
class SchedulingRecursionTest : public CommandTestBaseWithParam<bool> {};
class SchedulingRecursionTest
: public CommandTestBaseWithParam<Command::InterruptionBehavior> {};
class SelfCancellingCommand
: public CommandHelper<CommandBase, SelfCancellingCommand> {
public:
SelfCancellingCommand(CommandScheduler* scheduler, Subsystem* requirement)
: m_scheduler(scheduler) {
SelfCancellingCommand(CommandScheduler* scheduler, Subsystem* requirement,
Command::InterruptionBehavior interruptionBehavior =
Command::InterruptionBehavior::kCancelSelf)
: m_scheduler(scheduler), m_interrupt(interruptionBehavior) {
AddRequirements(requirement);
}
void Initialize() override { m_scheduler->Cancel(this); }
InterruptionBehavior GetInterruptionBehavior() const override {
return m_interrupt;
}
private:
CommandScheduler* m_scheduler;
InterruptionBehavior m_interrupt;
};
/**
* Checks <a
* href="https://github.com/wpilibsuite/allwpilib/issues/4259">wpilibsuite/allwpilib#4259</a>.
*/
TEST_P(SchedulingRecursionTest, CancelFromInitialize) {
TEST_F(SchedulingRecursionTest, CancelFromInitialize) {
CommandScheduler scheduler = GetScheduler();
bool hasOtherRun = false;
TestSubsystem requirement;
SelfCancellingCommand selfCancels{&scheduler, &requirement};
auto selfCancels = SelfCancellingCommand(&scheduler, &requirement);
RunCommand other =
RunCommand([&hasOtherRun] { hasOtherRun = true; }, {&requirement});
scheduler.Schedule(GetParam(), &selfCancels);
scheduler.Schedule(&selfCancels);
scheduler.Run();
// interruptibility of new arrival isn't checked
scheduler.Schedule(&other);
EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
@@ -48,16 +56,18 @@ TEST_P(SchedulingRecursionTest, CancelFromInitialize) {
EXPECT_TRUE(hasOtherRun);
}
TEST_P(SchedulingRecursionTest, DefaultCommand) {
TEST_P(SchedulingRecursionTest,
DefaultCommandGetsRescheduledAfterSelfCanceling) {
CommandScheduler scheduler = GetScheduler();
bool hasOtherRun = false;
TestSubsystem requirement;
SelfCancellingCommand selfCancels{&scheduler, &requirement};
auto selfCancels =
SelfCancellingCommand(&scheduler, &requirement, GetParam());
RunCommand other =
RunCommand([&hasOtherRun] { hasOtherRun = true; }, {&requirement});
scheduler.SetDefaultCommand(&requirement, std::move(other));
scheduler.Schedule(GetParam(), &selfCancels);
scheduler.Schedule(&selfCancels);
scheduler.Run();
scheduler.Run();
EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
@@ -93,5 +103,7 @@ TEST_F(SchedulingRecursionTest, CancelFromEnd) {
EXPECT_FALSE(scheduler.IsScheduled(&selfCancels));
}
INSTANTIATE_TEST_SUITE_P(SchedulingRecursionTests, SchedulingRecursionTest,
testing::Bool());
INSTANTIATE_TEST_SUITE_P(
SchedulingRecursionTests, SchedulingRecursionTest,
testing::Values(Command::InterruptionBehavior::kCancelSelf,
Command::InterruptionBehavior::kCancelIncoming));