diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java index b869f4e086..4c87c66e25 100644 --- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java +++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/CommandScheduler.java @@ -585,7 +585,7 @@ public final class CommandScheduler implements Sendable, AutoCloseable { */ public void registerComposedCommands(Command... commands) { var commandSet = Set.of(commands); - requireNotComposed(commandSet); + requireNotComposedOrScheduled(commandSet); var exception = new Exception("Originally composed at:"); exception.fillInStackTrace(); for (var command : commands) { @@ -617,7 +617,7 @@ public final class CommandScheduler implements Sendable, AutoCloseable { } /** - * Requires that the specified command hasn't been already added to a composition. + * Requires that the specified command hasn't already been added to a composition. * * @param commands The commands to check * @throws IllegalArgumentException if the given commands have already been composed. @@ -635,7 +635,7 @@ public final class CommandScheduler implements Sendable, AutoCloseable { } /** - * Requires that the specified commands not have been already added to a composition. + * Requires that the specified commands have not already been added to a composition. * * @param commands The commands to check * @throws IllegalArgumentException if the given commands have already been composed. @@ -644,6 +644,34 @@ public final class CommandScheduler implements Sendable, AutoCloseable { requireNotComposed(commands.toArray(Command[]::new)); } + /** + * Requires that the specified command hasn't already been added to a composition, and is not + * currently scheduled. + * + * @param command The command to check + * @throws IllegalArgumentException if the given command has already been composed or scheduled. + */ + public void requireNotComposedOrScheduled(Command command) { + if (isScheduled(command)) { + throw new IllegalArgumentException( + "Commands that have been scheduled individually may not be added to a composition!"); + } + requireNotComposed(command); + } + + /** + * Requires that the specified commands have not already been added to a composition, and are not + * currently scheduled. + * + * @param commands The commands to check + * @throws IllegalArgumentException if the given commands have already been composed or scheduled. + */ + public void requireNotComposedOrScheduled(Collection commands) { + for (var command : commands) { + requireNotComposedOrScheduled(command); + } + } + /** * Check if the given command has been composed. * diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp index 63d1e3bf14..efeee4e644 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/CommandScheduler.cpp @@ -477,6 +477,29 @@ void CommandScheduler::RequireUngrouped( } } +void CommandScheduler::RequireUngroupedAndUnscheduled(const Command* command) { + if (IsScheduled(command)) { + throw FRC_MakeError(frc::err::CommandIllegalUse, + "Commands that have been scheduled individually may " + "not be added to another composition!"); + } + RequireUngrouped(command); +} + +void CommandScheduler::RequireUngroupedAndUnscheduled( + std::span> commands) { + for (auto&& command : commands) { + RequireUngroupedAndUnscheduled(command.get()); + } +} + +void CommandScheduler::RequireUngroupedAndUnscheduled( + std::initializer_list commands) { + for (auto&& command : commands) { + RequireUngroupedAndUnscheduled(command); + } +} + void CommandScheduler::InitSendable(wpi::SendableBuilder& builder) { builder.SetSmartDashboardType("Scheduler"); builder.AddStringArrayProperty( diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp index 4c07c7ae24..6ce33b7548 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ConditionalCommand.cpp @@ -12,7 +12,7 @@ ConditionalCommand::ConditionalCommand(std::unique_ptr&& onTrue, std::unique_ptr&& onFalse, std::function condition) : m_condition{std::move(condition)} { - CommandScheduler::GetInstance().RequireUngrouped( + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled( {onTrue.get(), onFalse.get()}); m_onTrue = std::move(onTrue); diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelCommandGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelCommandGroup.cpp index 99e0845c8d..4a234b1475 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelCommandGroup.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelCommandGroup.cpp @@ -63,7 +63,7 @@ Command::InterruptionBehavior ParallelCommandGroup::GetInterruptionBehavior() void ParallelCommandGroup::AddCommands( std::vector>&& commands) { - CommandScheduler::GetInstance().RequireUngrouped(commands); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(commands); if (isRunning) { throw FRC_MakeError(frc::err::CommandIllegalUse, diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp index a6b5c1c264..1c9b700534 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelDeadlineGroup.cpp @@ -62,7 +62,7 @@ Command::InterruptionBehavior ParallelDeadlineGroup::GetInterruptionBehavior() void ParallelDeadlineGroup::AddCommands( std::vector>&& commands) { - CommandScheduler::GetInstance().RequireUngrouped(commands); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(commands); if (!m_finished) { throw FRC_MakeError(frc::err::CommandIllegalUse, diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelRaceGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelRaceGroup.cpp index df658bcc73..334286f8e3 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelRaceGroup.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/ParallelRaceGroup.cpp @@ -50,7 +50,7 @@ Command::InterruptionBehavior ParallelRaceGroup::GetInterruptionBehavior() void ParallelRaceGroup::AddCommands( std::vector>&& commands) { - CommandScheduler::GetInstance().RequireUngrouped(commands); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(commands); if (isRunning) { throw FRC_MakeError(frc::err::CommandIllegalUse, diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp index 2e74c8c72f..2a08f1b043 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/RepeatCommand.cpp @@ -9,7 +9,7 @@ using namespace frc2; RepeatCommand::RepeatCommand(std::unique_ptr&& command) { - CommandScheduler::GetInstance().RequireUngrouped(command.get()); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(command.get()); m_command = std::move(command); m_command->SetComposed(true); AddRequirements(m_command->GetRequirements()); diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp index b9ea3d56ec..173b9d93c8 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/SequentialCommandGroup.cpp @@ -62,7 +62,7 @@ Command::InterruptionBehavior SequentialCommandGroup::GetInterruptionBehavior() void SequentialCommandGroup::AddCommands( std::vector>&& commands) { - CommandScheduler::GetInstance().RequireUngrouped(commands); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(commands); if (m_currentCommandIndex != invalid_index) { throw FRC_MakeError(frc::err::CommandIllegalUse, diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/WrapperCommand.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/WrapperCommand.cpp index f7928c964e..6391294f30 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/WrapperCommand.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/WrapperCommand.cpp @@ -9,7 +9,7 @@ using namespace frc2; WrapperCommand::WrapperCommand(std::unique_ptr&& command) { - CommandScheduler::GetInstance().RequireUngrouped(command.get()); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled(command.get()); m_command = std::move(command); m_command->SetComposed(true); // copy the wrapped command's name diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h b/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h index ce4dc914c2..1f8d74297a 100644 --- a/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h +++ b/wpilibNewCommands/src/main/native/include/frc2/command/CommandScheduler.h @@ -374,7 +374,7 @@ class CommandScheduler final : public wpi::Sendable, void OnCommandFinish(Action action); /** - * Requires that the specified command hasn't been already added to a + * Requires that the specified command hasn't already been added to a * composition. * * @param command The command to check @@ -383,7 +383,7 @@ class CommandScheduler final : public wpi::Sendable, void RequireUngrouped(const Command* command); /** - * Requires that the specified commands not have been already added to a + * Requires that the specified commands have not already been added to a * composition. * * @param commands The commands to check @@ -392,7 +392,7 @@ class CommandScheduler final : public wpi::Sendable, void RequireUngrouped(std::span> commands); /** - * Requires that the specified commands not have been already added to a + * Requires that the specified commands have not already been added to a * composition. * * @param commands The commands to check @@ -401,6 +401,38 @@ class CommandScheduler final : public wpi::Sendable, */ void RequireUngrouped(std::initializer_list commands); + /** + * Requires that the specified command has not already been added to a + * composition and is not currently scheduled. + * + * @param command The command to check + * @throws IllegalArgumentException if the given command has already been + * composed or scheduled. + */ + void RequireUngroupedAndUnscheduled(const Command* command); + + /** + * Requires that the specified commands have not already been added to a + * composition and are not currently scheduled. + * + * @param commands The commands to check + * @throws IllegalArgumentException if the given commands have already been + * composed. + */ + void RequireUngroupedAndUnscheduled( + std::span> commands); + + /** + * Requires that the specified commands have not already been added to a + * composition and are not currently scheduled. + * + * @param commands The commands to check + * @throws IllegalArgumentException if the given commands have already been + * composed or scheduled. + */ + void RequireUngroupedAndUnscheduled( + std::initializer_list commands); + void InitSendable(wpi::SendableBuilder& builder) override; private: diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h index d425d76c56..4c2ba66fde 100644 --- a/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h +++ b/wpilibNewCommands/src/main/native/include/frc2/command/SelectCommand.h @@ -56,7 +56,8 @@ class SelectCommand : public CommandHelper> { m_defaultCommand.SetComposed(true); for (auto&& command : foo) { - CommandScheduler::GetInstance().RequireUngrouped(command.second.get()); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled( + command.second.get()); command.second.get()->SetComposed(true); } @@ -77,7 +78,8 @@ class SelectCommand : public CommandHelper> { : m_selector{std::move(selector)} { m_defaultCommand.SetComposed(true); for (auto&& command : commands) { - CommandScheduler::GetInstance().RequireUngrouped(command.second.get()); + CommandScheduler::GetInstance().RequireUngroupedAndUnscheduled( + command.second.get()); command.second.get()->SetComposed(true); } diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandGroupErrorTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandGroupErrorTest.java deleted file mode 100644 index ba4a77b829..0000000000 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/CommandGroupErrorTest.java +++ /dev/null @@ -1,47 +0,0 @@ -// 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 edu.wpi.first.wpilibj2.command; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -class CommandGroupErrorTest extends CommandTestBase { - @Test - void commandInMultipleGroupsTest() { - MockCommandHolder command1Holder = new MockCommandHolder(true); - Command command1 = command1Holder.getMock(); - MockCommandHolder command2Holder = new MockCommandHolder(true); - Command command2 = command2Holder.getMock(); - - new ParallelCommandGroup(command1, command2); - assertThrows( - IllegalArgumentException.class, () -> new ParallelCommandGroup(command1, command2)); - } - - @Test - void commandInGroupExternallyScheduledTest() { - MockCommandHolder command1Holder = new MockCommandHolder(true); - Command command1 = command1Holder.getMock(); - MockCommandHolder command2Holder = new MockCommandHolder(true); - Command command2 = command2Holder.getMock(); - - new ParallelCommandGroup(command1, command2); - - assertThrows( - IllegalArgumentException.class, () -> CommandScheduler.getInstance().schedule(command1)); - } - - @Test - void redecoratedCommandErrorTest() { - Command command = new InstantCommand(); - - assertDoesNotThrow(() -> command.withTimeout(10).until(() -> false)); - assertThrows(IllegalArgumentException.class, () -> command.withTimeout(10)); - CommandScheduler.getInstance().removeComposedCommand(command); - assertDoesNotThrow(() -> command.withTimeout(10)); - } -} diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MultiCompositionTestBase.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MultiCompositionTestBase.java index 2b79550cb7..46cb03cd9a 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MultiCompositionTestBase.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/MultiCompositionTestBase.java @@ -13,11 +13,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -interface MultiCompositionTestBase extends SingleCompositionTestBase { - T compose(Command... members); +abstract class MultiCompositionTestBase extends SingleCompositionTestBase { + abstract T compose(Command... members); @Override - default T composeSingle(Command member) { + T composeSingle(Command member) { return compose(member); } @@ -63,7 +63,7 @@ interface MultiCompositionTestBase extends SingleCompositionT @MethodSource @ParameterizedTest(name = "interruptible[{index}]: {0}") - default void interruptible( + void interruptible( @SuppressWarnings("unused") String name, InterruptionBehavior expected, Command command1, @@ -103,7 +103,7 @@ interface MultiCompositionTestBase extends SingleCompositionT @MethodSource @ParameterizedTest(name = "runsWhenDisabled[{index}]: {0}") - default void runsWhenDisabled( + void runsWhenDisabled( @SuppressWarnings("unused") String name, boolean expected, Command command1, diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroupTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroupTest.java index 7dc91106ee..a837baac8a 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroupTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelCommandGroupTest.java @@ -14,8 +14,7 @@ import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; -class ParallelCommandGroupTest extends CommandTestBase - implements MultiCompositionTestBase { +class ParallelCommandGroupTest extends MultiCompositionTestBase { @Test void parallelGroupScheduleTest() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroupTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroupTest.java index 6fa644bb24..789827bf6c 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroupTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelDeadlineGroupTest.java @@ -14,8 +14,7 @@ import static org.mockito.internal.verification.VerificationModeFactory.times; import java.util.Arrays; import org.junit.jupiter.api.Test; -class ParallelDeadlineGroupTest extends CommandTestBase - implements MultiCompositionTestBase { +class ParallelDeadlineGroupTest extends MultiCompositionTestBase { @Test void parallelDeadlineScheduleTest() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroupTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroupTest.java index dd650c4bac..ea780c6216 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroupTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/ParallelRaceGroupTest.java @@ -16,8 +16,7 @@ import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; -class ParallelRaceGroupTest extends CommandTestBase - implements MultiCompositionTestBase { +class ParallelRaceGroupTest extends MultiCompositionTestBase { @Test void parallelRaceScheduleTest() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java index 53abdd8dc9..2aaecc6d93 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/RepeatCommandTest.java @@ -10,8 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; -class RepeatCommandTest extends CommandTestBase - implements SingleCompositionTestBase { +class RepeatCommandTest extends SingleCompositionTestBase { @Test void callsMethodsCorrectly() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java index 0ce3b79057..e364468186 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SelectCommandTest.java @@ -13,8 +13,7 @@ import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; -class SelectCommandTest extends CommandTestBase - implements MultiCompositionTestBase> { +class SelectCommandTest extends MultiCompositionTestBase> { @Test void selectCommandTest() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroupTest.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroupTest.java index ff578f45d4..abbc49062c 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroupTest.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SequentialCommandGroupTest.java @@ -12,8 +12,7 @@ import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; -class SequentialCommandGroupTest extends CommandTestBase - implements MultiCompositionTestBase { +class SequentialCommandGroupTest extends MultiCompositionTestBase { @Test void sequentialGroupScheduleTest() { try (CommandScheduler scheduler = new CommandScheduler()) { diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SingleCompositionTestBase.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SingleCompositionTestBase.java index 4e54e3ec00..a063a1de63 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SingleCompositionTestBase.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/command/SingleCompositionTestBase.java @@ -5,17 +5,19 @@ package edu.wpi.first.wpilibj2.command; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; -public interface SingleCompositionTestBase { - T composeSingle(Command member); +public abstract class SingleCompositionTestBase extends CommandTestBase { + abstract T composeSingle(Command member); @EnumSource(Command.InterruptionBehavior.class) @ParameterizedTest - default void interruptible(Command.InterruptionBehavior interruptionBehavior) { + void interruptible(Command.InterruptionBehavior interruptionBehavior) { var command = composeSingle( new WaitUntilCommand(() -> false).withInterruptBehavior(interruptionBehavior)); @@ -24,9 +26,38 @@ public interface SingleCompositionTestBase { @ValueSource(booleans = {true, false}) @ParameterizedTest - default void runWhenDisabled(boolean runsWhenDisabled) { + void runWhenDisabled(boolean runsWhenDisabled) { var command = composeSingle(new WaitUntilCommand(() -> false).ignoringDisable(runsWhenDisabled)); assertEquals(runsWhenDisabled, command.runsWhenDisabled()); } + + @Test + void commandInOtherCompositionTest() { + var command = new InstantCommand(); + new WrapperCommand(command) {}; + assertThrows(IllegalArgumentException.class, () -> composeSingle(command)); + } + + @Test + void commandInMultipleCompositionsTest() { + var command = new InstantCommand(); + composeSingle(command); + assertThrows(IllegalArgumentException.class, () -> composeSingle(command)); + } + + @Test + void composeThenScheduleTest() { + var command = new InstantCommand(); + composeSingle(command); + assertThrows( + IllegalArgumentException.class, () -> CommandScheduler.getInstance().schedule(command)); + } + + @Test + void scheduleThenComposeTest() { + var command = new RunCommand(() -> {}); + CommandScheduler.getInstance().schedule(command); + assertThrows(IllegalArgumentException.class, () -> composeSingle(command)); + } }