/* Copyright 2017 Remko Popma Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package picocli; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ProvideSystemProperty; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TestRule; import picocli.CommandLine.*; import picocli.CommandLine.Model.*; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Map; import static org.junit.Assert.*; import static picocli.TestUtil.usageString; public class MixinTest { // allows tests to set any kind of properties they like, without having to individually roll them back @Rule public final TestRule restoreSystemProperties = new RestoreSystemProperties(); @Rule public final ProvideSystemProperty ansiOFF = new ProvideSystemProperty("picocli.ansi", "false"); @Test public void testMixinAnnotationMustBeValidCommand_CommandAnnotation() { @Command class ValidMixin {} // valid command because it has @Command annotation class Receiver { // valid command because it has @Mixin annotated field @Mixin ValidMixin mixMeIn; } new CommandLine(new Receiver(), new InnerClassFactory(this)); // no exception } @Test public void testMixinAnnotationMustBeValidCommand_OptionAnnotatedField() { class ValidMixin { // valid command because it has @Option annotation @Option(names = "-a") int a; } class Receiver { // valid command because it has @Mixin annotated field @Mixin ValidMixin mixMeIn; } new CommandLine(new Receiver(), new InnerClassFactory(this)); // no exception } @Test public void testMixinAnnotationMustBeValidCommand_ParametersAnnotatedField() { class ValidMixin { // valid command because it has @Parameters annotation @Parameters int a; } class Receiver { // valid command because it has @Mixin annotated field @Mixin ValidMixin mixMeIn; } new CommandLine(new Receiver(), new InnerClassFactory(this)); // no exception } @Test public void testAddMixinMustBeValidCommand_CommandAnnotation() { @Command class ValidMixin {} // valid command because it has @Command annotation @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("valid", new ValidMixin()); // no exception } @Test public void testAddMixinMustBeValidCommand_OptionAnnotatedField() { class ValidMixin { // valid command because it has @Option annotation @Option(names = "-a") int a; } @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("valid", new ValidMixin()); // no exception } @Test public void testAddMixinMustBeValidCommand_ParametersAnnotatedField() { class ValidMixin { // valid command because it has @Parameters annotation @Parameters int a; } @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("valid", new ValidMixin()); // no exception } @Test public void testAddMixinMustBeValidCommand_SubCommandMethod() { @Command class ValidMixin { // valid command because it has @Command annotation } @Command class Receiver { @Command void sub(@Mixin ValidMixin mixin) { } } CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); CommandSpec commandSpec = commandLine.getCommandSpec().subcommands().get("sub").getCommandSpec().mixins().get("arg0"); assertEquals(ValidMixin.class, commandSpec.userObject().getClass()); commandLine.addMixin("valid", new ValidMixin()); // no exception } @Test public void testMixinIn_SubCommandMethod() { @Command class ValidMixin { @Option(names = "-x") int x; } @Command class Mixee { @Command int sub(@Mixin ValidMixin mixin) { return mixin.x; } } IFactory fact = new InnerClassFactory(this); assertEquals(3, new CommandLine(new Mixee(), fact).execute("sub", "-x=3")); assertEquals(4, new CommandLine(new Mixee(), fact).execute("sub", "-x=4")); } @Test public void testMixinMixeeIn_SubCommandMethod() { @Command class ValidMixin { @Spec CommandSpec spec; @Spec(Spec.Target.MIXEE) CommandSpec mixee; @Option(names = "-x") int x; } @Command class Mixee { @Command int sub(@Mixin ValidMixin mixin) throws NoSuchMethodException { assertNotSame (mixin.mixee, mixin.spec); assertNotEquals(mixin.mixee, mixin.spec); assertEquals(ValidMixin.class.getName(), mixin.spec.userObject().getClass().getName()); assertEquals(Mixee.class.getDeclaredMethod("sub", ValidMixin.class).toString(), mixin.mixee.userObject().toString()); return mixin.x; } } IFactory fact = new InnerClassFactory(this); assertEquals(3, new CommandLine(new Mixee(), fact).execute("sub", "-x=3")); } @Test public void testMixeeWithoutMixin() { @Command class SomeMixin { @Spec CommandSpec spec; @Spec(Spec.Target.MIXEE) CommandSpec mixee; @Option(names = "-x") int x; } SomeMixin mixin = new SomeMixin(); new CommandLine(mixin).parseArgs(); assertNull("Not mixed into anything: no mixee", mixin.mixee); assertNotNull(mixin.spec); } @Test public void testMixinAnnotationRejectedIfNotAValidCommand() { class Invalid {} class Receiver { @Mixin Invalid mixMeIn; } Receiver userObject = new Receiver(); try { new CommandLine(userObject, new InnerClassFactory(this)); } catch (InitializationException ex) { assertEquals(userObject.mixMeIn + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations", ex.getMessage()); } } @Test public void testAddMixinRejectedIfNotAValidCommand() { class Invalid {} @Command class Receiver {} Invalid mixin = new Invalid(); CommandLine commandLine = new CommandLine(new Receiver()); try { commandLine.addMixin("invalid", mixin); } catch (InitializationException ex) { assertEquals(mixin + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations", ex.getMessage()); } } static class CommandAttributes { @Command(name = "mixmein", version = "Mixin 1.0", separator = ":", description = "description from mixin", descriptionHeading = "Mixin Description Heading%n", header = "Mixin Header", headerHeading = "Mixin Header Heading%n", footer = "Mixin Footer", footerHeading = "Mixin Footer Heading%n", optionListHeading = "Mixin Option List Heading%n", parameterListHeading = "Mixin Parameter List Heading%n", commandListHeading = "Mixin Command List Heading%n", requiredOptionMarker = '%', synopsisHeading = "Mixin Synopsis Heading%n", abbreviateSynopsis = true, customSynopsis = "Mixin custom synopsis", showDefaultValues = true, sortOptions = false) static class MixMeIn {} } @Test public void testMixinAnnotationCommandAttributes() throws Exception { class Receiver { @Mixin CommandAttributes.MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver()); verifyMixinCommandAttributes(commandLine); } @Test public void testAddMixinCommandAttributes() throws Exception { @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("mixmein", new CommandAttributes.MixMeIn()); verifyMixinCommandAttributes(commandLine); } private void verifyMixinCommandAttributes(CommandLine commandLine) throws UnsupportedEncodingException { CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals("mixmein", commandSpec.name()); assertArrayEquals(new String[] {"Mixin 1.0"}, commandSpec.version()); assertEquals(":", commandSpec.parser().separator()); assertArrayEquals(new String[] {"description from mixin"}, commandSpec.usageMessage().description()); assertEquals("Mixin Description Heading%n", commandSpec.usageMessage().descriptionHeading()); assertArrayEquals(new String[] {"Mixin Header"}, commandSpec.usageMessage().header()); assertEquals("Mixin Header Heading%n", commandSpec.usageMessage().headerHeading()); assertArrayEquals(new String[] {"Mixin Footer"}, commandSpec.usageMessage().footer()); assertEquals("Mixin Footer Heading%n", commandSpec.usageMessage().footerHeading()); assertEquals("Mixin Option List Heading%n", commandSpec.usageMessage().optionListHeading()); assertEquals("Mixin Parameter List Heading%n", commandSpec.usageMessage().parameterListHeading()); assertEquals("Mixin Command List Heading%n", commandSpec.usageMessage().commandListHeading()); assertEquals('%', commandSpec.usageMessage().requiredOptionMarker()); assertEquals("Mixin Synopsis Heading%n", commandSpec.usageMessage().synopsisHeading()); assertTrue("abbreviateSynopsis", commandSpec.usageMessage().abbreviateSynopsis()); assertArrayEquals(new String[] {"Mixin custom synopsis"}, commandSpec.usageMessage().customSynopsis()); assertTrue("showDefaultValues", commandSpec.usageMessage().showDefaultValues()); assertFalse("sortOptions", commandSpec.usageMessage().sortOptions()); String expected = String.format("" + "Mixin Header Heading%n" + "Mixin Header%n" + "Mixin Synopsis Heading%n" + "Mixin custom synopsis%n" + "Mixin Description Heading%n" + "description from mixin%n" + "Mixin Footer Heading%n" + "Mixin Footer%n"); assertEquals(expected, usageString(commandLine, Help.Ansi.OFF)); } static class CommandAttributesDontOverwriteReceiverAttributes { @Command(name = "mixmein", version = "Mixin 1.0", separator = ":", description = "description from mixin", descriptionHeading = "Mixin Description Heading%n", header = "Mixin Header", headerHeading = "Mixin Header Heading%n", footer = "Mixin Footer", footerHeading = "Mixin Footer Heading%n", optionListHeading = "Mixin Option List Heading%n", parameterListHeading = "Mixin Parameter List Heading%n", commandListHeading = "Mixin Command List Heading%n", requiredOptionMarker = '%', synopsisHeading = "Mixin Synopsis Heading%n", abbreviateSynopsis = true, customSynopsis = "Mixin custom synopsis", showDefaultValues = true, sortOptions = false) static class MixMeIn {} } @Test public void testMixinAnnotationCommandAttributesDontOverwriteReceiverAttributes() throws Exception { @Command(name = "receiver", version = "Receiver 1.0", separator = "~", description = "Receiver description", descriptionHeading = "Receiver Description Heading%n", header = "Receiver Header", headerHeading = "Receiver Header Heading%n", footer = "Receiver Footer", footerHeading = "Receiver Footer Heading%n", optionListHeading = "Receiver Option List Heading%n", parameterListHeading = "Receiver Parameter List Heading%n", commandListHeading = "Receiver Command List Heading%n", requiredOptionMarker = '#', synopsisHeading = "Receiver Synopsis Heading%n", customSynopsis = "Receiver custom synopsis") class Receiver { @Mixin CommandAttributesDontOverwriteReceiverAttributes.MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver()); verifyMixinCommandAttributesDontOverwriteReceiverAttributes(commandLine); } @Test public void testAddMixinCommandAttributesDontOverwriteReceiverAttributes() throws Exception { @Command(name = "receiver", version = "Receiver 1.0", separator = "~", description = "Receiver description", descriptionHeading = "Receiver Description Heading%n", header = "Receiver Header", headerHeading = "Receiver Header Heading%n", footer = "Receiver Footer", footerHeading = "Receiver Footer Heading%n", optionListHeading = "Receiver Option List Heading%n", parameterListHeading = "Receiver Parameter List Heading%n", commandListHeading = "Receiver Command List Heading%n", requiredOptionMarker = '#', synopsisHeading = "Receiver Synopsis Heading%n", customSynopsis = "Receiver custom synopsis") class Receiver {} CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); commandLine.addMixin("mixMeIn", new CommandAttributesDontOverwriteReceiverAttributes.MixMeIn()); verifyMixinCommandAttributesDontOverwriteReceiverAttributes(commandLine); } private void verifyMixinCommandAttributesDontOverwriteReceiverAttributes(CommandLine commandLine) throws UnsupportedEncodingException { CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals("receiver", commandSpec.name()); assertArrayEquals(new String[] {"Receiver 1.0"}, commandSpec.version()); assertEquals("~", commandSpec.parser().separator()); assertArrayEquals(new String[] {"Receiver description"}, commandSpec.usageMessage().description()); assertEquals("Receiver Description Heading%n", commandSpec.usageMessage().descriptionHeading()); assertArrayEquals(new String[] {"Receiver Header"}, commandSpec.usageMessage().header()); assertEquals("Receiver Header Heading%n", commandSpec.usageMessage().headerHeading()); assertArrayEquals(new String[] {"Receiver Footer"}, commandSpec.usageMessage().footer()); assertEquals("Receiver Footer Heading%n", commandSpec.usageMessage().footerHeading()); assertEquals("Receiver Option List Heading%n", commandSpec.usageMessage().optionListHeading()); assertEquals("Receiver Parameter List Heading%n", commandSpec.usageMessage().parameterListHeading()); assertEquals("Receiver Command List Heading%n", commandSpec.usageMessage().commandListHeading()); assertEquals('#', commandSpec.usageMessage().requiredOptionMarker()); assertEquals("Receiver Synopsis Heading%n", commandSpec.usageMessage().synopsisHeading()); assertArrayEquals(new String[] {"Receiver custom synopsis"}, commandSpec.usageMessage().customSynopsis()); String expected = String.format("" + "Receiver Header Heading%n" + "Receiver Header%n" + "Receiver Synopsis Heading%n" + "Receiver custom synopsis%n" + "Receiver Description Heading%n" + "Receiver description%n" + "Receiver Footer Heading%n" + "Receiver Footer%n"); assertEquals(expected, usageString(commandLine, Help.Ansi.OFF)); } static class SuperClassCommandAttributesDontOverwriteSubclassAttributes { @Command(name = "mixmein", version = "Mixin 1.0", separator = ":", description = "description from mixin", descriptionHeading = "Mixin Description Heading%n", header = "Mixin Header", headerHeading = "Mixin Header Heading%n", footer = "Mixin Footer", footerHeading = "Mixin Footer Heading%n", optionListHeading = "Mixin Option List Heading%n", parameterListHeading = "Mixin Parameter List Heading%n", commandListHeading = "Mixin Command List Heading%n", requiredOptionMarker = '%', synopsisHeading = "Mixin Synopsis Heading%n", abbreviateSynopsis = true, customSynopsis = "Mixin custom synopsis", showDefaultValues = true, sortOptions = false) static class MixMeInSuper {} @Command(name = "mixmeinSub", version = "MixinSub 1.0", separator = "~", description = "description from mixinSub", descriptionHeading = "MixinSub Description Heading%n", header = "MixinSub Header", headerHeading = "MixinSub Header Heading%n", footer = "MixinSub Footer", footerHeading = "MixinSub Footer Heading%n", optionListHeading = "MixinSub Option List Heading%n", parameterListHeading = "MixinSub Parameter List Heading%n", commandListHeading = "MixinSub Command List Heading%n", requiredOptionMarker = '#', synopsisHeading = "MixinSub Synopsis Heading%n", abbreviateSynopsis = true, customSynopsis = "MixinSub custom synopsis", showDefaultValues = true, sortOptions = false) static class MixMeInSub extends MixMeInSuper {} } @Test public void testMixinAnnotationSuperClassCommandAttributesDontOverwriteSubclassAttributes() throws Exception { class Receiver { @Mixin SuperClassCommandAttributesDontOverwriteSubclassAttributes.MixMeInSub mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver()); verifyMixinSuperClassCommandAttributesDontOverwriteSubclassAttributes(commandLine); } @Test public void testAddMixinSuperClassCommandAttributesDontOverwriteSubclassAttributes() throws Exception { @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("mixMeIn", new SuperClassCommandAttributesDontOverwriteSubclassAttributes.MixMeInSub()); verifyMixinSuperClassCommandAttributesDontOverwriteSubclassAttributes(commandLine); } private void verifyMixinSuperClassCommandAttributesDontOverwriteSubclassAttributes(CommandLine commandLine) throws UnsupportedEncodingException { CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals("mixmeinSub", commandSpec.name()); assertArrayEquals(new String[] {"MixinSub 1.0"}, commandSpec.version()); assertEquals("~", commandSpec.parser().separator()); assertArrayEquals(new String[] {"description from mixinSub"}, commandSpec.usageMessage().description()); assertEquals("MixinSub Description Heading%n", commandSpec.usageMessage().descriptionHeading()); assertArrayEquals(new String[] {"MixinSub Header"}, commandSpec.usageMessage().header()); assertEquals("MixinSub Header Heading%n", commandSpec.usageMessage().headerHeading()); assertArrayEquals(new String[] {"MixinSub Footer"}, commandSpec.usageMessage().footer()); assertEquals("MixinSub Footer Heading%n", commandSpec.usageMessage().footerHeading()); assertEquals("MixinSub Option List Heading%n", commandSpec.usageMessage().optionListHeading()); assertEquals("MixinSub Parameter List Heading%n", commandSpec.usageMessage().parameterListHeading()); assertEquals("MixinSub Command List Heading%n", commandSpec.usageMessage().commandListHeading()); assertEquals('#', commandSpec.usageMessage().requiredOptionMarker()); assertEquals("MixinSub Synopsis Heading%n", commandSpec.usageMessage().synopsisHeading()); assertArrayEquals(new String[] {"MixinSub custom synopsis"}, commandSpec.usageMessage().customSynopsis()); String expected = String.format("" + "MixinSub Header Heading%n" + "MixinSub Header%n" + "MixinSub Synopsis Heading%n" + "MixinSub custom synopsis%n" + "MixinSub Description Heading%n" + "description from mixinSub%n" + "MixinSub Footer Heading%n" + "MixinSub Footer%n"); assertEquals(expected, usageString(commandLine, Help.Ansi.OFF)); } static class CombinesAttributes { @Command(name = "superName", version = "MixMeInSuper 1.0", separator = "$", description = "33", descriptionHeading = "333", header = "3333", headerHeading = "33333", //footer = "333 3", //footerHeading = "333 33", //optionListHeading = "333 333", parameterListHeading = "333 333 3", commandListHeading = "333 333 33", requiredOptionMarker = '3', synopsisHeading = "3333 3") static class MixMeInSuper {} @Command(description = "description from mixinSub", descriptionHeading = "MixinSub Description Heading%n", header = "MixinSub Header", headerHeading = "MixinSub Header Heading%n", //footer = "222", //footerHeading = "222 222", //optionListHeading = "222 222 222", parameterListHeading = "2 22", commandListHeading = "222 2", requiredOptionMarker = '2', synopsisHeading = "22222") static class MixMeInSub extends MixMeInSuper {} @Command( //name = "000 - set by MixinMeInSuper", //version = "0.0 - set by MixinMeInSuper", //separator = "0 - set by MixinMeInSuper", //description = "00 - set by MixMeInSub", //descriptionHeading = "000 - set by MixMeInSub", //header = "0000 - set by MixMeInSub", //headerHeading = "00000 - set by MixMeInSub", footer = "ReceiverSuper Footer", footerHeading = "ReceiverSuper Footer Heading%n", optionListHeading = "ReceiverSuper Option List Heading%n", parameterListHeading = "-1-1-1", commandListHeading = "--1--1--1", requiredOptionMarker = '1', synopsisHeading = "---1---1---1") static class ReceiverSuper {} } @Test public void testMixinAnnotationCombinesAttributes() throws Exception { @Command(parameterListHeading = "Receiver Parameter List Heading%n", commandListHeading = "Receiver Command List Heading%n", requiredOptionMarker = '#', synopsisHeading = "Receiver Synopsis Heading%n", //customSynopsis = "Receiver custom synopsis", // use standard generated synopsis showDefaultValues = true, sortOptions = false) class Receiver extends CombinesAttributes.ReceiverSuper { @Mixin CombinesAttributes.MixMeInSub mixMeIn; @Parameters(description = "some files") File[] files; } CommandLine commandLine = new CommandLine(new Receiver()); verifyMixinCombinesAttributes(commandLine); } @Test public void testAddMixinCombinesAttributes() throws Exception { @Command(parameterListHeading = "Receiver Parameter List Heading%n", commandListHeading = "Receiver Command List Heading%n", requiredOptionMarker = '#', synopsisHeading = "Receiver Synopsis Heading%n", //customSynopsis = "Receiver custom synopsis", // use standard generated synopsis showDefaultValues = true, sortOptions = false) class Receiver extends CombinesAttributes.ReceiverSuper { @Parameters(description = "some files") File[] files; } CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("mixMeIn", new CombinesAttributes.MixMeInSub()); verifyMixinCombinesAttributes(commandLine); } private void verifyMixinCombinesAttributes(CommandLine commandLine) throws UnsupportedEncodingException { CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals("superName", commandSpec.name()); assertArrayEquals(new String[] {"MixMeInSuper 1.0"}, commandSpec.version()); assertEquals("$", commandSpec.parser().separator()); UsageMessageSpec usage = commandSpec.usageMessage(); assertArrayEquals(new String[] {"description from mixinSub"}, usage.description()); assertEquals("MixinSub Description Heading%n", usage.descriptionHeading()); assertArrayEquals(new String[] {"MixinSub Header"}, usage.header()); assertEquals("MixinSub Header Heading%n", usage.headerHeading()); assertArrayEquals(new String[] {"ReceiverSuper Footer"}, usage.footer()); assertEquals("ReceiverSuper Footer Heading%n", usage.footerHeading()); assertEquals("ReceiverSuper Option List Heading%n", usage.optionListHeading()); assertEquals("Receiver Parameter List Heading%n", usage.parameterListHeading()); assertEquals("Receiver Command List Heading%n", usage.commandListHeading()); assertEquals('#', usage.requiredOptionMarker()); assertEquals("Receiver Synopsis Heading%n", usage.synopsisHeading()); assertArrayEquals(new String[0], usage.customSynopsis()); String expected = String.format("" + "MixinSub Header Heading%n" + "MixinSub Header%n" + "Receiver Synopsis Heading%n" + "superName [...]%n" + "MixinSub Description Heading%n" + "description from mixinSub%n" + "Receiver Parameter List Heading%n" + " [...] some files%n" + "ReceiverSuper Footer Heading%n" + "ReceiverSuper Footer%n"); assertEquals(expected, usageString(commandLine, Help.Ansi.OFF)); } static class InjectsOptionsAndParameters { static class MixMeIn { @Option(names = {"-a", "--alpha"}, description = "option from mixin") private int alpha; @Parameters(description = "parameters from mixin") File[] files; } } @Test public void testMixinAnnotationInjectsOptionsAndParameters() throws UnsupportedEncodingException { @Command(sortOptions = false) class Receiver { @Option(names = {"-b", "--beta"}, description = "Receiver option") private int beta; @Parameters(description = "parameters from receiver") File[] receiverFiles; @Mixin InjectsOptionsAndParameters.MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver()); verifyMixinInjectsOptionsAndParameters(commandLine); } @Test public void testAddMixinInjectsOptionsAndParameters() throws UnsupportedEncodingException { @Command(sortOptions = false) class Receiver { @Option(names = {"-b", "--beta"}, description = "Receiver option") private int beta; @Parameters(description = "parameters from receiver") File[] receiverFiles; } CommandLine commandLine = new CommandLine(new Receiver()); commandLine.addMixin("mixMeIn", new InjectsOptionsAndParameters.MixMeIn()); verifyMixinInjectsOptionsAndParameters(commandLine); } private void verifyMixinInjectsOptionsAndParameters(CommandLine commandLine) throws UnsupportedEncodingException { CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals(2, commandSpec.options().size()); assertArrayEquals(new String[]{"-b", "--beta"}, commandSpec.options().get(0).names()); assertArrayEquals(new String[]{"-a", "--alpha"}, commandSpec.options().get(1).names()); assertTrue(commandSpec.optionsMap().containsKey("--alpha")); assertTrue(commandSpec.optionsMap().containsKey("--beta")); assertTrue(commandSpec.optionsMap().containsKey("-a")); assertTrue(commandSpec.optionsMap().containsKey("-b")); assertTrue(commandSpec.posixOptionsMap().containsKey('a')); assertTrue(commandSpec.posixOptionsMap().containsKey('b')); assertEquals(2, commandSpec.positionalParameters().size()); assertEquals("", commandSpec.positionalParameters().get(0).paramLabel()); assertEquals("", commandSpec.positionalParameters().get(1).paramLabel()); String expects = String.format("" + "Usage:
[-a=] [-b=] [...] [...]%n" + " [...] parameters from receiver%n" + " [...] parameters from mixin%n" + " -b, --beta= Receiver option%n" + " -a, --alpha= option from mixin%n"); assertEquals(expects, usageString(commandLine, Help.Ansi.OFF)); } @Test public void testMixinAnnotationParsesOptionsAndParameters() throws UnsupportedEncodingException { @Command(sortOptions = false) class Receiver { @Option(names = {"-b", "--beta"}, description = "Receiver option") private int beta; @Parameters(description = "parameters from receiver") File[] receiverFiles; @Mixin InjectsOptionsAndParameters.MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver()); commandLine.parseArgs("-a", "111", "-b", "222", "a", "b"); Receiver receiver = commandLine.getCommand(); assertEquals(222, receiver.beta); assertEquals(111, receiver.mixMeIn.alpha); assertArrayEquals(new File[] {new File("a"), new File("b")}, receiver.receiverFiles); assertArrayEquals(new File[] {new File("a"), new File("b")}, receiver.mixMeIn.files); } @Test public void testAddMixinParsesOptionsAndParameters() throws UnsupportedEncodingException { @Command(sortOptions = false) class Receiver { @Option(names = {"-b", "--beta"}, description = "Receiver option") private int beta; @Parameters(description = "parameters from receiver") File[] receiverFiles; } CommandLine commandLine = new CommandLine(new Receiver()); InjectsOptionsAndParameters.MixMeIn mixin = new InjectsOptionsAndParameters.MixMeIn(); commandLine.addMixin("mixin", mixin); commandLine.parseArgs("-a", "111", "-b", "222", "a", "b"); Receiver receiver = commandLine.getCommand(); assertEquals(222, receiver.beta); assertEquals(111, mixin.alpha); assertArrayEquals(new File[] {new File("a"), new File("b")}, receiver.receiverFiles); assertArrayEquals(new File[] {new File("a"), new File("b")}, mixin.files); assertSame(mixin, commandLine.getMixins().get("mixin")); assertSame(mixin, commandLine.getCommandSpec().mixins().get("mixin").userObject()); } @Test public void testMixinAnnotationInjectsOptionsAndParametersInDeclarationOrder() throws Exception { @Command(sortOptions = false) class Receiver { @Mixin InjectsOptionsAndParameters.MixMeIn mixMeIn; @Option(names = {"-b", "--beta"}, description = "Receiver option") private int beta; @Parameters(description = "parameters from receiver") File[] receiverFiles; } CommandLine commandLine = new CommandLine(new Receiver()); CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals(2, commandSpec.options().size()); assertArrayEquals(new String[]{"-a", "--alpha"}, commandSpec.options().get(0).names()); assertArrayEquals(new String[]{"-b", "--beta"}, commandSpec.options().get(1).names()); assertEquals(2, commandSpec.positionalParameters().size()); assertEquals("", commandSpec.positionalParameters().get(0).paramLabel()); assertEquals("", commandSpec.positionalParameters().get(1).paramLabel()); String expects = String.format("" + "Usage:
[-a=] [-b=] [...] [...]%n" + " [...] parameters from mixin%n" + " [...] parameters from receiver%n" + " -a, --alpha= option from mixin%n" + " -b, --beta= Receiver option%n"); assertEquals(expects, usageString(commandLine, Help.Ansi.OFF)); } @Test public void testMixinAnnotationRejectsDuplicateOptions() { class MixMeInDuplicate { @Option(names = {"-a", "--alpha"}, description = "option from mixin") private int alpha; } class ReceiverDuplicate { @Option(names = {"-a"}, description = "Receiver option") private int beta; @Mixin MixMeInDuplicate mixMeIn; } try { new CommandLine(new ReceiverDuplicate(), new InnerClassFactory(this)); fail("Expected exception"); } catch (DuplicateOptionAnnotationsException ex) { assertEquals("Option name '-a' is used by both field int picocli.MixinTest$1MixMeInDuplicate.alpha and field int picocli.MixinTest$1ReceiverDuplicate.beta", ex.getMessage()); } } @Test public void testMixinAnnotationWithSubcommands() { @Command(name = "mixinsub") class MixedInSubCommand {} @Command(subcommands = MixedInSubCommand.class) class MixMeIn {} class Receiver { @Mixin MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); CommandSpec commandSpec = commandLine.getCommandSpec(); assertEquals(1, commandLine.getSubcommands().size()); assertEquals(1, commandSpec.subcommands().size()); CommandLine subcommandLine = commandSpec.subcommands().get("mixinsub"); assertSame(subcommandLine, commandLine.getSubcommands().get("mixinsub")); assertTrue(subcommandLine.getCommand() instanceof MixedInSubCommand); } @Test public void testMixinAnnotationCanBeRetrievedByAnnotationName() { @Command class MixMeIn {} @Command class Receiver { @Mixin(name = "aMixin") MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); assertFalse("mixin was registered", commandLine.getMixins().isEmpty()); assertTrue(commandLine.getMixins().get("aMixin") instanceof MixMeIn); Receiver receiver = commandLine.getCommand(); assertNotNull(receiver.mixMeIn); assertSame(receiver.mixMeIn, commandLine.getMixins().get("aMixin")); assertSame(receiver.mixMeIn, commandLine.getCommandSpec().mixins().get("aMixin").userObject()); } @Test public void testMixinAnnotationCanBeRetrievedByFieldName() { @Command class MixMeIn {} @Command class Receiver { @Mixin MixMeIn mixMeIn; } CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); assertFalse("mixin was registered", commandLine.getMixins().isEmpty()); assertTrue(commandLine.getMixins().get("mixMeIn") instanceof MixMeIn); Receiver receiver = commandLine.getCommand(); assertNotNull(receiver.mixMeIn); assertSame(receiver.mixMeIn, commandLine.getMixins().get("mixMeIn")); assertSame(receiver.mixMeIn, commandLine.getCommandSpec().mixins().get("mixMeIn").userObject()); } @Test public void testAddMixin_CanBeRetrievedByFieldName() { @Command class MixMeIn {} @Command class Receiver {} CommandLine commandLine = new CommandLine(new Receiver(), new InnerClassFactory(this)); commandLine.addMixin("mixin", new MixMeIn()); assertFalse("mixin was registered", commandLine.getMixins().isEmpty()); assertTrue(commandLine.getMixins().get("mixin") instanceof MixMeIn); } @Test public void testMixinStandardHelpOptions_AreAddedLast() { @Command(mixinStandardHelpOptions = true, sortOptions = false) class App { @Option(names = "-a", description = "a option") boolean aOpt; @Option(names = "-z", description = "z option") boolean zOpt; } CommandLine commandLine = new CommandLine(new App(), new InnerClassFactory(this)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); commandLine.usage(new PrintStream(baos)); String expected = String.format("" + "Usage:
[-ahVz]%n" + " -a a option%n" + " -z z option%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n" ); assertEquals(expected, baos.toString()); } static class Issue439Mixin { @Spec CommandSpec spec; @Option(names = "--trex") void setTRexFences(final String value) { throw new ParameterException(spec.commandLine(), "TREX error"); } } static class Issue439Command { @Mixin Issue439Mixin mixin; @Spec CommandSpec spec; @Option(names = "--raptor") void setRaptorFences(final String value) { throw new ParameterException(spec.commandLine(), "RAPTOR error"); } } @Test public void testIssue439InjectedSpecInMixinHasNullCommandLineAnnotations() { CommandLine cmd = new CommandLine(new Issue439Command()); assertExceptionThrownFromSetter(cmd); } @Test public void testIssue439InjectedSpecInMixinHasNullCommandLineProgrammatic() { final CommandSpec mixinSpec = CommandSpec.create(); ISetter trexSetter = new ISetter() { public T set(T value) { throw new ParameterException(mixinSpec.commandLine(), "TREX error"); } }; mixinSpec.addOption(OptionSpec.builder("--trex"). type(String.class).setter(trexSetter).build()); final CommandSpec commandSpec = CommandSpec.create(); commandSpec.addMixin("mixin", mixinSpec); ISetter raptorSetter = new ISetter() { public T set(T value) { throw new ParameterException(commandSpec.commandLine(), "RAPTOR error"); } }; commandSpec.addOption(OptionSpec.builder("--raptor"). type(String.class).setter(raptorSetter).build()); CommandLine cmd = new CommandLine(commandSpec); assertExceptionThrownFromSetter(cmd); } private void assertExceptionThrownFromSetter(CommandLine cmd) { try { cmd.parseArgs("--trex", "abc"); fail("expected ParameterException"); } catch (ParameterException ex) { assertEquals("TREX error", ex.getMessage()); } try { cmd.parseArgs("--raptor", "xyz"); fail("expected ParameterException"); } catch (ParameterException ex) { assertEquals("RAPTOR error", ex.getMessage()); } } @Command(name="super") static class SuperClass { } @Command(name="sub") static class SubClass extends SuperClass { @Command(name="method") public void method() {} } @Command(name="main", subcommands= {SuperClass.class, SubClass.class}) static class Main { } @Test public void testIssue619MethodSubcommandInSubclassAddedTwice() { //setTraceLevel("DEBUG"); CommandLine commandLine = new CommandLine(new Main()); assertEquals(2, commandLine.getSubcommands().size()); CommandLine zuper = commandLine.getSubcommands().get("super"); assertEquals(0, zuper.getSubcommands().size()); CommandLine sub = commandLine.getSubcommands().get("sub"); assertEquals(1, sub.getSubcommands().size()); CommandLine method = sub.getSubcommands().get("method"); assertEquals(0, method.getSubcommands().size()); } @Command static class Issue924 { @Mixin Issue924Mixin myMixin; Issue924Mixin methodMixin; @Command void subcommand(@Mixin(name = "blah") Issue924Mixin myMixin) { methodMixin = myMixin; } } static class Issue924Mixin { @Option(names = "-v") boolean verbose; } @Test public void testIssue924mixinAnnotatedElement() { Issue924 userObject = new Issue924(); CommandLine commandLine = new CommandLine(userObject); assertNotNull(userObject.myMixin); Map mixinElements = commandLine.getCommandSpec().mixinAnnotatedElements(); assertEquals(1, mixinElements.size()); IAnnotatedElement element = mixinElements.get("myMixin"); assertNotNull(element); assertEquals("myMixin", element.getName()); assertEquals(Issue924Mixin.class, element.getTypeInfo().getType()); assertTrue(String.valueOf(element.getter()), element.getter() instanceof FieldBinding); CommandLine subcommand = commandLine.getSubcommands().get("subcommand"); Map mixinMethodElements = subcommand.getCommandSpec().mixinAnnotatedElements(); assertEquals(1, mixinMethodElements.size()); IAnnotatedElement methodElement = mixinMethodElements.get("blah"); assertNotNull(methodElement); assertEquals("arg0", methodElement.getName()); assertEquals(Issue924Mixin.class, methodElement.getTypeInfo().getType()); } @Command(subcommands = Issue925Sub.class) static class Issue925Top { boolean verbose; @Option(names = "-v") public void setVerbose(boolean verbose) { this.verbose = verbose; } } // Define a mixin that delegates to the parent command. static class Issue925MyMixin { @ParentCommand Issue925Top top; @Option(names = "-v") public void setVerbose(boolean verbose) { top.setVerbose(verbose); } } // Now subcommands just need to mix in the `MyMixin` // to get a `-v` option that delegates to the parent command. @Command(name = "sub") static class Issue925Sub { @Mixin Issue925MyMixin mymixin; // This works for @Command-annotated methods also. // But needs to have the correct parent! @Command(name = "subsub") void subsub(@Mixin(name = "key") Issue925SubMixin mymixin) { // business logic for the subsub command... } } static class Issue925SubMixin { @ParentCommand Issue925Sub sub; @Option(names = "-v") public void setVerbose(boolean verbose) { sub.mymixin.setVerbose(verbose); } } @Test public void testIssue925ParentCommandAnnotationInMixins() { Issue925Top top = new Issue925Top(); CommandLine commandLine = new CommandLine(top); Issue925Sub subCmdObject = commandLine.getSubcommands().get("sub").getCommand(); assertNotNull(subCmdObject.mymixin); assertNotNull(subCmdObject.mymixin.top); assertSame(top, subCmdObject.mymixin.top); CommandSpec subsub = commandLine.getSubcommands().get("sub").getSubcommands().get("subsub").getCommandSpec(); assertEquals(1, subsub.mixins().size()); CommandSpec mixin = subsub.mixins().get("key"); Issue925SubMixin mixinInstance = (Issue925SubMixin) mixin.userObject(); assertNotNull(mixinInstance.sub); assertSame(subCmdObject, mixinInstance.sub); } static class VerbosityMixin { @Spec CommandSpec mySpec; @Spec(Spec.Target.MIXEE) CommandSpec commandSpec; @Option(names = "-v") boolean verbose; } @Test public void testMixinSpecTarget() { class MyApp { @Mixin VerbosityMixin verbosityMixin; } MyApp myApp = new MyApp(); new CommandLine(myApp).parseArgs("-v"); assertNotNull(myApp.verbosityMixin.mySpec); assertNotNull(myApp.verbosityMixin.commandSpec); assertNotSame(myApp.verbosityMixin.commandSpec, myApp.verbosityMixin.mySpec); assertTrue(myApp.verbosityMixin.mySpec.userObject() instanceof VerbosityMixin); assertSame(myApp, myApp.verbosityMixin.commandSpec.userObject()); } @Command(usageHelpWidth = 123 ) static class MixinWidth {} @Test public void testMixinWidth() { class MyApp { @Mixin MixinWidth mixin; } CommandLine cmd = new CommandLine(new MyApp()); assertEquals(123, cmd.getCommandSpec().usageMessage().width()); } @Test public void testMixinLongOptionsMaxWidth() { CommandSpec spec = CommandSpec.create(); assertNotEquals(43, spec.usageMessage().longOptionsMaxWidth()); spec.usageMessage().longOptionsMaxWidth(43); CommandSpec another = CommandSpec.create(); another.addMixin("a", spec); assertEquals(43, another.usageMessage().longOptionsMaxWidth()); } }