diff --git a/src/main/java/org/usfirst/frc4048/common/logging/CommandUtil.java b/src/main/java/org/usfirst/frc4048/common/logging/CommandUtil.java index 0aa5edf..275f7d6 100644 --- a/src/main/java/org/usfirst/frc4048/common/logging/CommandUtil.java +++ b/src/main/java/org/usfirst/frc4048/common/logging/CommandUtil.java @@ -32,6 +32,18 @@ * Command pickUp = CommandUtil.sequence("ArmSequence", new OpenGripper(), new RaiseArm(), new CloseGripper()); * Command pickupAndDrive = CommandUtil.parallel("WalkWhileChewingGum", pickup, new Drive()); * + * + * and also for nesting command groups that are custom-created: + * (Note the "extends SequentialLoggingCommandGroup" - DO NOT extend regular SequentialCommandGroup or this will not work) + *
+ *     class PickupGroup extends SequentialLoggingCommandGroup {
+ *         public PickupGroup() {
+ *             super("Pickup", new OpenGripper(), new RaiseArm(), new CloseGripper());
+ *         }
+ *     }
+ *     Command pickUp = new PickupGroup();
+ *     Command pickupAndDrive = CommandUtil.parallel("WalkWhileChewingGum", pickup, new Drive());
+ * 
*/ public class CommandUtil { public static final String COMMAND_PREFIX = "Commands"; @@ -52,6 +64,8 @@ public static Command logged(Command command) { /** * Make a logging command from the given command, with a custom name hierarchy. * The logging command will be placed within the given name prefix naming hierarchy instead of at the root. + * @param namePrefix the nesting hierarchy prefix for the command. If null the command is going to log at the + * root level. If not null it will be nested in this namespace. * @param command the command to wrap * @return a {@link LoggingCommand} that wraps the given command */ @@ -60,7 +74,7 @@ public static Command logged(String namePrefix, Command command) { } /** - * Return a logged command that runs once and stops (see {@link Commands#runOnce(Runnable, Subsystem...)}. + * Return a logged command that runs once and stops (see {@link edu.wpi.first.wpilibj2.command.Commands#runOnce(Runnable, Subsystem...)}. * This will return a loggable anonymous command with the given name. * @param name the name of the resulting command * @param action the action to perform (only once) @@ -76,28 +90,30 @@ public static Command runOnce(String name, Runnable action, Subsystem... require /** * Return a loggable {@link SequentialCommandGroup} that executes the given commands sequentially. The given commands * can be any command or command group - they will be converted to loggable commands implicitly. - * @param sequenceName the name of the resaulting command group + * @param sequenceName the name of the resulting command group * @param commands the commands to put in the group * @return the created loggable command group */ public static Command sequence(String sequenceName, Command... commands) { - LoggingCommand[] loggingCommands = wrapForLogging(sequenceName, commands); - return new SequentialLoggingCommand(sequenceName, loggingCommands); + return new SequentialLoggingCommand(sequenceName, commands); } /** * Return a loggable {@link ParallelCommandGroup} that executes the given commands in parallel. The given commands * can be any command or command group - they will be converted to loggable commands implicitly. - * @param sequenceName the name of the resaulting command group + * @param sequenceName the name of the resulting command group * @param commands the commands to put in the group * @return the created loggable command group */ public static Command parallel(String sequenceName, Command... commands) { - LoggingCommand[] loggingCommands = wrapForLogging(sequenceName, commands); - return new ParallelLoggingCommand(sequenceName, loggingCommands); + return new ParallelLoggingCommand(sequenceName, commands); + } + + public static Command race(String sequenceName, Command... commands) { + return new RaceLoggingCommand(sequenceName, commands); } - private static LoggingCommand[] wrapForLogging(String prefix, Command... commands) { + public static LoggingCommand[] wrapForLogging(String prefix, Command... commands) { // Do not use streams due to efficiency LoggingCommand[] newCommands = new LoggingCommand[commands.length]; for (int i = 0; i < commands.length; i++) { @@ -108,9 +124,7 @@ private static LoggingCommand[] wrapForLogging(String prefix, Command... command private static LoggingCommand wrapForLogging(String prefix, Command command) { if (command instanceof LoggingCommand loggingCommand) { - // change the prefix to include the current new parent - String childPrefix = prefix + CommandUtil.NAME_SEPARATOR + loggingCommand.getNamePrefix(); - loggingCommand.setNamePrefix(childPrefix); + loggingCommand.appendNamePrefix(prefix); return loggingCommand; } else { // New command located in the given sequence root diff --git a/src/main/java/org/usfirst/frc4048/common/logging/GroupLoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/GroupLoggingCommand.java new file mode 100644 index 0000000..dd895a1 --- /dev/null +++ b/src/main/java/org/usfirst/frc4048/common/logging/GroupLoggingCommand.java @@ -0,0 +1,72 @@ +package org.usfirst.frc4048.common.logging; + +import edu.wpi.first.wpilibj2.command.Command; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for logged grouped commands (parallel, race, sequential...) + */ +public abstract class GroupLoggingCommand extends LoggingCommand { + // the "leaf" name - this comes after the name prefix to make up the full command name + private static final String THIS_NAME = "-this"; + + // A copy of the children (since we can't access them from the regular groups) + private final List childLoggingCommands; + + /** + * Constructor for logged group command. + * + * @param namePrefix the prefix for the name (comes in front of hte group name) + * @param underlying the group command that is wrapped by this command + */ + public GroupLoggingCommand(String namePrefix, Command underlying) { + super(namePrefix, THIS_NAME, underlying); + childLoggingCommands = new ArrayList<>(); + } + + /** + * A special constructor to allow for the creation of the command when we can't create the underlying up front. + * It is assumed that the {@link #setUnderlying(Command)} method is called immediately following the construction. + */ + protected GroupLoggingCommand(String namePrefix) { + super(namePrefix, THIS_NAME); + childLoggingCommands = new ArrayList<>(); + } + + public final void addLoggingCommands(List commands) { + childLoggingCommands.addAll(commands); + } + + @Override + public void setName(String name) { + // Do not change the logging name for this command (it is fixed) + getUnderlying().setName(name); + } + + @Override + public void appendNamePrefix(String prefix) { + // Change the name for this command + super.appendNamePrefix(prefix); + // Change the name for the children + appendChildrenPrefix(prefix); + } + + @Override + public String toString() { + return getFullyQualifiedName(); + } + + // For testing + public List getChildLoggingCommands() { + return childLoggingCommands; + } + + private void appendChildrenPrefix(String prefix) { + // Recursively change the prefix for all child commands + for (LoggingCommand loggingCommand : childLoggingCommands) { + loggingCommand.appendNamePrefix(prefix); + } + } +} diff --git a/src/main/java/org/usfirst/frc4048/common/logging/LoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/LoggingCommand.java index 347378f..0a77861 100644 --- a/src/main/java/org/usfirst/frc4048/common/logging/LoggingCommand.java +++ b/src/main/java/org/usfirst/frc4048/common/logging/LoggingCommand.java @@ -8,10 +8,14 @@ import java.util.Set; public abstract class LoggingCommand extends Command { + // Prefix for the command name (appended ahead of the command name) - this can be appended to when the command gets + // embedded at another command through appendNamePrefix private String namePrefix; + // Private name for the command (last in the hierarchy for this command) - this can be changed through setName private String commandName; - private final Command underlying; - + // The delegate command where we refer all calls + private Command underlying; + // The name as it would be used in the logs private String fullyQualifiedName; // Lazy initialized log entry to make sure we don't create it until it is needed (and command is scheduled) // otherwise we get an empty line in the log visualization tool @@ -24,6 +28,23 @@ public LoggingCommand(String namePrefix, String commandName, Command underlying) resetLoggingName(namePrefix, commandName); } + /** + * A special constructor to allow for the creation of the command when we can't create the underlying up front. + * It is assumed that the {@link #setUnderlying(Command)} method is called immediately following the construction. + */ + protected LoggingCommand(String namePrefix, String commandName) { + this.namePrefix = namePrefix; + this.commandName = commandName; + resetLoggingName(namePrefix, commandName); + } + + /** + * Second phase of initialization - set the underlying command + */ + protected void setUnderlying(Command underlying) { + this.underlying = underlying; + } + @Override public void initialize() { getLoggingEntry().append(true); @@ -82,8 +103,12 @@ public String getNamePrefix() { return namePrefix; } - public void setNamePrefix(String prefix) { - this.namePrefix = prefix; + /** + * Add a new prefix in front of the current one + * @param prefix the new prefix + */ + public void appendNamePrefix(String prefix) { + this.namePrefix = prefix + CommandUtil.NAME_SEPARATOR + namePrefix; resetLoggingName(namePrefix, commandName); } @@ -111,3 +136,5 @@ private BooleanLogEntry getLoggingEntry() { return logEntry; } } + + diff --git a/src/main/java/org/usfirst/frc4048/common/logging/ParallelDeadlineLoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/ParallelDeadlineLoggingCommand.java new file mode 100644 index 0000000..fe4f378 --- /dev/null +++ b/src/main/java/org/usfirst/frc4048/common/logging/ParallelDeadlineLoggingCommand.java @@ -0,0 +1,41 @@ +package org.usfirst.frc4048.common.logging; + +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.ParallelDeadlineGroup; + +import java.util.Arrays; + +public class ParallelDeadlineLoggingCommand extends GroupLoggingCommand { + private LoggingCommand deadlineLoggingCommand; + + /** + * Constructor for parallel deadline command group. + * + * @param namePrefix the name for the group - this is where the sub-commands for this group will be nested in + * @param deadline the deadline command - the one that when ends, stops other commands + * @param commands the sub commands for this group (either regular commands or LoggingCommand are OK) + */ + public ParallelDeadlineLoggingCommand(String namePrefix, Command deadline, Command... commands) { + // Since we can't initialize the deadlineGroup with empty commands, use the special constructor to pass in the + // underlying afterward + super(namePrefix); + + deadlineLoggingCommand = CommandUtil.wrapForLogging(namePrefix, deadline)[0]; + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(namePrefix, commands); + setUnderlying(new ParallelDeadlineGroup(deadlineLoggingCommand, wrapped)); + addLoggingCommands(Arrays.asList(wrapped)); + } + + public final void addCommands(Command... commands) { + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(getNamePrefix(), commands); + ((ParallelDeadlineGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); + } + + @Override + public void appendNamePrefix(String prefix) { + super.appendNamePrefix(prefix); + // Change the name for the deadline + deadlineLoggingCommand.appendNamePrefix(prefix); + } +} diff --git a/src/main/java/org/usfirst/frc4048/common/logging/ParallelLoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/ParallelLoggingCommand.java index ea02509..62fe63e 100644 --- a/src/main/java/org/usfirst/frc4048/common/logging/ParallelLoggingCommand.java +++ b/src/main/java/org/usfirst/frc4048/common/logging/ParallelLoggingCommand.java @@ -1,38 +1,29 @@ package org.usfirst.frc4048.common.logging; +import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.ParallelCommandGroup; -public class ParallelLoggingCommand extends LoggingCommand { - private static final String THIS_NAME = "-this"; - - private LoggingCommand[] loggingCommands; - - public ParallelLoggingCommand(String namePrefix, LoggingCommand... commands) { - super(namePrefix, THIS_NAME, new ParallelCommandGroup(commands)); - this.loggingCommands = commands; - } - - @Override - public void setName(String name) { - // Do not change the logging name for this command (it is fixed) - getUnderlying().setName(name); - } - - @Override - public void setNamePrefix(String prefix) { - super.setNamePrefix(prefix); - setChildrenPrefix(prefix); - } - - @Override - public String toString() { - return getFullyQualifiedName(); +import java.util.Arrays; + +public class ParallelLoggingCommand extends GroupLoggingCommand { + /** + * Constructor for parallel command group. + * + * @param namePrefix the name for the group - this is where the sub-commands for this group will be nested in + * @param commands the sub commands for this group (either regular commands or LoggingCommand are OK) + */ + public ParallelLoggingCommand(String namePrefix, Command... commands) { + // Call super with an empty group, populate children afterward + super(namePrefix, new ParallelCommandGroup()); + + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(namePrefix, commands); + ((ParallelCommandGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); } - private void setChildrenPrefix(String prefix) { - // Recursively change the prefix for all child commands - for (LoggingCommand loggingCommand : loggingCommands) { - loggingCommand.setNamePrefix(prefix); - } + public final void addCommands(Command... commands) { + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(getNamePrefix(), commands); + ((ParallelCommandGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); } } diff --git a/src/main/java/org/usfirst/frc4048/common/logging/RaceLoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/RaceLoggingCommand.java new file mode 100644 index 0000000..f93a441 --- /dev/null +++ b/src/main/java/org/usfirst/frc4048/common/logging/RaceLoggingCommand.java @@ -0,0 +1,29 @@ +package org.usfirst.frc4048.common.logging; + +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.ParallelRaceGroup; + +import java.util.Arrays; + +public class RaceLoggingCommand extends GroupLoggingCommand { + /** + * Constructor for race command group. + * + * @param namePrefix the name for the group - this is where the sub-commands for this group will be nested in + * @param commands the sub commands for this group (either regular commands or LoggingCommand are OK) + */ + public RaceLoggingCommand(String namePrefix, Command... commands) { + // Call super with an empty group, populate children afterward + super(namePrefix, new ParallelRaceGroup()); + + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(namePrefix, commands); + ((ParallelRaceGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); + } + + public final void addCommands(Command... commands) { + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(getNamePrefix(), commands); + ((ParallelRaceGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); + } +} diff --git a/src/main/java/org/usfirst/frc4048/common/logging/SequentialLoggingCommand.java b/src/main/java/org/usfirst/frc4048/common/logging/SequentialLoggingCommand.java index d4aac2b..97ab270 100644 --- a/src/main/java/org/usfirst/frc4048/common/logging/SequentialLoggingCommand.java +++ b/src/main/java/org/usfirst/frc4048/common/logging/SequentialLoggingCommand.java @@ -1,38 +1,29 @@ package org.usfirst.frc4048.common.logging; +import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.SequentialCommandGroup; -public class SequentialLoggingCommand extends LoggingCommand { - private static final String THIS_NAME = "-this"; - - private final LoggingCommand[] loggingCommands; - - public SequentialLoggingCommand(String namePrefix, LoggingCommand... commands) { - super(namePrefix, THIS_NAME, new SequentialCommandGroup(commands)); - this.loggingCommands = commands; - } - - @Override - public void setName(String name) { - // Do not change the logging name for this command (it is fixed) - getUnderlying().setName(name); - } - - @Override - public void setNamePrefix(String prefix) { - super.setNamePrefix(prefix); - setChildrenPrefix(prefix); - } - - @Override - public String toString() { - return getFullyQualifiedName(); +import java.util.Arrays; + +public class SequentialLoggingCommand extends GroupLoggingCommand { + /** + * Constructor for sequential command group. + * + * @param namePrefix the name for the group - this is where the sub-commands for this group will be nested in + * @param commands the sub commands for this group (either regular commands or LoggingCommand are OK) + */ + public SequentialLoggingCommand(String namePrefix, Command... commands) { + // Call super with an empty group, populate children afterward + super(namePrefix, new SequentialCommandGroup()); + + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(namePrefix, commands); + ((SequentialCommandGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); } - private void setChildrenPrefix(String prefix) { - // Recursively change the prefix for all child commands - for (LoggingCommand loggingCommand : loggingCommands) { - loggingCommand.setNamePrefix(prefix); - } + public final void addCommands(Command... commands) { + LoggingCommand[] wrapped = CommandUtil.wrapForLogging(getNamePrefix(), commands); + ((SequentialCommandGroup) getUnderlying()).addCommands(wrapped); + addLoggingCommands(Arrays.asList(wrapped)); } } diff --git a/src/test/java/org/usfirst/frc4048/common/logging/LoggingTest.java b/src/test/java/org/usfirst/frc4048/common/logging/LoggingTest.java new file mode 100644 index 0000000..9732d80 --- /dev/null +++ b/src/test/java/org/usfirst/frc4048/common/logging/LoggingTest.java @@ -0,0 +1,171 @@ +package org.usfirst.frc4048.common.logging; + +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.Commands; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class LoggingTest { + + @Test + public void testLoggingCommandSingle() { + LoggingCommand command = (LoggingCommand) CommandUtil.logged(Commands.runOnce(() -> System.out.println("Hello"))); + Assertions.assertNull(command.getNamePrefix()); + Assertions.assertEquals("Commands/InstantCommand", command.getFullyQualifiedName()); + } + + @Test + public void testLoggingCommandSingleWithPrefix() { + LoggingCommand command = (LoggingCommand) CommandUtil.logged("test", Commands.runOnce(() -> System.out.println("Hello"))); + Assertions.assertEquals("test", command.getNamePrefix()); + Assertions.assertEquals("Commands/test/InstantCommand", command.getFullyQualifiedName()); + } + + @Test + public void testLoggingCommandGroup() { + LoggingCommand command = (LoggingCommand) CommandUtil.logged("test", Commands.parallel(Commands.runOnce(() -> System.out.println("Hello")))); + Assertions.assertEquals("test", command.getNamePrefix()); + Assertions.assertEquals("Commands/test/ParallelCommandGroup", command.getFullyQualifiedName()); + } + + @Test + public void testSequenceInstantCommand() { + Command command = Commands.runOnce(() -> System.out.println("Hello")); + SequentialLoggingCommand sequence = (SequentialLoggingCommand) CommandUtil.sequence("sequence", command); + Assertions.assertEquals("sequence", sequence.getNamePrefix()); + Assertions.assertEquals("Commands/sequence/-this", sequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/sequence/InstantCommand", sequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceRegularCommand() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand sequence = (SequentialLoggingCommand) CommandUtil.sequence("sequence", command); + Assertions.assertEquals("sequence", sequence.getNamePrefix()); + Assertions.assertEquals("Commands/sequence/-this", sequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/sequence/command", sequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceLoggingInstantCommand() { + Command command = CommandUtil.logged("command", Commands.runOnce(() -> System.out.println("Hello"))); + SequentialLoggingCommand sequence = (SequentialLoggingCommand) CommandUtil.sequence("sequence", command); + Assertions.assertEquals("sequence", sequence.getNamePrefix()); + Assertions.assertEquals("Commands/sequence/-this", sequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/sequence/command/InstantCommand", sequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceLoggingRegularCommand() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand sequence = (SequentialLoggingCommand) CommandUtil.sequence("sequence", CommandUtil.logged("logged", command)); + Assertions.assertEquals("sequence", sequence.getNamePrefix()); + Assertions.assertEquals("Commands/sequence/-this", sequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/sequence/logged/command", sequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceLoggingRegularNoNamedCommand() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand sequence = (SequentialLoggingCommand) CommandUtil.sequence("sequence", CommandUtil.logged(command)); + Assertions.assertEquals("sequence", sequence.getNamePrefix()); + Assertions.assertEquals("Commands/sequence/-this", sequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/sequence/null/command", sequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceOfSequence() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand inner = (SequentialLoggingCommand) CommandUtil.sequence("inner", command); + SequentialLoggingCommand outer = (SequentialLoggingCommand) CommandUtil.sequence("outer", inner); + Assertions.assertEquals("outer", outer.getNamePrefix()); + Assertions.assertEquals("Commands/outer/-this", outer.getFullyQualifiedName()); + inner = (SequentialLoggingCommand) outer.getChildLoggingCommands().get(0); + Assertions.assertEquals("Commands/outer/inner/-this", inner.getFullyQualifiedName()); + Assertions.assertEquals("Commands/outer/inner/command", inner.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testSequenceOfSequenceOfLoggedCommand() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand inner = (SequentialLoggingCommand) CommandUtil.sequence("inner", CommandUtil.logged("logged", command)); + SequentialLoggingCommand outer = (SequentialLoggingCommand) CommandUtil.sequence("outer", inner); + Assertions.assertEquals("outer", outer.getNamePrefix()); + Assertions.assertEquals("Commands/outer/-this", outer.getFullyQualifiedName()); + inner = (SequentialLoggingCommand) outer.getChildLoggingCommands().get(0); + Assertions.assertEquals("Commands/outer/inner/-this", inner.getFullyQualifiedName()); + Assertions.assertEquals("Commands/outer/inner/logged/command", inner.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testFourLevelNesting() { + Command command = new Command() { + @Override + public String getName() { + return "command"; + } + }; + SequentialLoggingCommand first = (SequentialLoggingCommand) CommandUtil.sequence("first", CommandUtil.logged("logged", command)); + SequentialLoggingCommand second = (SequentialLoggingCommand) CommandUtil.sequence("second", first); + SequentialLoggingCommand third = (SequentialLoggingCommand) CommandUtil.sequence("third", second); + SequentialLoggingCommand fourth = (SequentialLoggingCommand) CommandUtil.sequence("fourth", third); + + Assertions.assertEquals("Commands/fourth/-this", fourth.getFullyQualifiedName()); + Assertions.assertEquals("Commands/fourth/third/-this", third.getFullyQualifiedName()); + Assertions.assertEquals("Commands/fourth/third/second/-this", second.getFullyQualifiedName()); + Assertions.assertEquals("Commands/fourth/third/second/first/-this", first.getFullyQualifiedName()); + } + + @Test + public void testCustomSequentialCommand() { + SequentialLoggingCommand customSequence = new CustomSequentialGroup(); + Assertions.assertEquals("Sequence", customSequence.getNamePrefix()); + Assertions.assertEquals("Commands/Sequence/-this", customSequence.getFullyQualifiedName()); + Assertions.assertEquals("Commands/Sequence/Command", customSequence.getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + @Test + public void testCustomSequenceOfSequence() { + SequentialLoggingCommand customSequence = new CustomSequentialGroup(); + SequentialLoggingCommand outer = (SequentialLoggingCommand) CommandUtil.sequence("outer", customSequence); + Assertions.assertEquals("outer", outer.getNamePrefix()); + Assertions.assertEquals("Commands/outer/Sequence/-this", outer.getChildLoggingCommands().get(0).getFullyQualifiedName()); + Assertions.assertEquals("Commands/outer/Sequence/Command", ((SequentialLoggingCommand) outer.getChildLoggingCommands().get(0)).getChildLoggingCommands().get(0).getFullyQualifiedName()); + } + + private static class CustomSequentialGroup extends SequentialLoggingCommand { + public CustomSequentialGroup() { + super("Sequence", new Command() { + @Override + public String getName() { + return "Command"; + } + }); + } + } +}