/* 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.*; import org.junit.contrib.java.lang.system.ProvideSystemProperty; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.contrib.java.lang.system.SystemErrRule; import org.junit.rules.TestRule; import picocli.CommandLine.*; import picocli.CommandLine.Model.PositionalParamSpec; import java.io.File; import java.lang.reflect.Method; import java.net.InetAddress; import java.util.*; import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; import static picocli.TestUtil.setTraceLevel; public class ArityTest { @Before public void setUp() { System.clearProperty("picocli.trace"); } @After public void tearDown() { System.clearProperty("picocli.trace"); } @Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog().muteForSuccessfulTests(); // 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 testArityConstructor_fixedRange() { Range arity = new Range(1, 23, false, false, null); assertEquals("min", 1, arity.min()); assertEquals("max", 23, arity.max()); assertEquals("1..23", arity.toString()); assertEquals(Range.valueOf("1..23"), arity); } @Test public void testArityConstructor_variableRange() { Range arity = new Range(1, Integer.MAX_VALUE, true, false, null); assertEquals("min", 1, arity.min()); assertEquals("max", Integer.MAX_VALUE, arity.max()); assertEquals("1..*", arity.toString()); assertEquals(Range.valueOf("1..*"), arity); } @Test public void testValueOfDisallowsInvalidRange() { try { Range.valueOf("1..0"); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid range (min=1, max=0)", ex.getMessage()); } try { Range.valueOf("3..1"); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid range (min=3, max=1)", ex.getMessage()); } } @Test public void testValueOfDisallowsNegativeRange() { try { Range.valueOf("-1..0"); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid negative range (min=-1, max=0)", ex.getMessage()); } try { Range.valueOf("-3..1"); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid negative range (min=-3, max=1)", ex.getMessage()); } } @Test public void testConstructorDisallowsNegativeRange() { try { new Range(-1, 0, true, true, ""); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid negative range (min=-1, max=0)", ex.getMessage()); } try { new Range(-3, -1, true, true, ""); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid negative range (min=-3, max=-1)", ex.getMessage()); } } @Test public void testConstructorDisallowsInvalidRange() { try { new Range(1, 0, true, true, ""); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid range (min=1, max=0)", ex.getMessage()); } try { new Range(3, 1, true, true, ""); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid range (min=3, max=1)", ex.getMessage()); } } private static class SupportedTypes2 { String nonOptionField; @Option(names = "-boolean") boolean booleanField; @Option(names = "-int") int intField; } @Test public void testOptionArity_forNonAnnotatedField() throws Exception { Range arity = Range.optionArity(SupportedTypes2.class.getDeclaredField("nonOptionField")); assertEquals(0, arity.max()); assertEquals(0, arity.min()); assertEquals(false, arity.isVariable()); assertEquals("0", arity.toString()); } @Test public void testArityForOption_booleanFieldImplicitArity0() throws Exception { Range arity = Range.optionArity(SupportedTypes2.class.getDeclaredField("booleanField")); assertEquals(Range.valueOf("0"), arity); assertEquals("0", arity.toString()); } @Test public void testArityForOption_intFieldImplicitArity1() throws Exception { Range arity = Range.optionArity(SupportedTypes2.class.getDeclaredField("intField")); assertEquals(Range.valueOf("1"), arity); assertEquals("1", arity.toString()); } @Test public void testArityForOption_isExplicitlyDeclaredValue() throws Exception { class Params { @Option(names = "-timeUnitList", type = TimeUnit.class, arity = "3") List timeUnitList; } Range arity = Range.optionArity(Params.class.getDeclaredField("timeUnitList")); assertEquals(Range.valueOf("3"), arity); assertEquals("3", arity.toString()); } @Test public void testArityForOption_listFieldImplicitArity1() throws Exception { class ImplicitList { @Option(names = "-a") List listIntegers; } Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("listIntegers")); assertEquals(Range.valueOf("1"), arity); assertEquals("1", arity.toString()); } @Test public void testArityForOption_arrayFieldImplicitArity1() throws Exception { class ImplicitList { @Option(names = "-a") int[] intArray; } Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("intArray")); assertEquals(Range.valueOf("1"), arity); assertEquals("1", arity.toString()); } @Test public void testParameterArityWithOptionMember() throws Exception { class ImplicitBoolField { @Option(names = "-x") boolean boolSingleValue; } Range arity = Range.parameterArity(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); assertEquals(0, arity.max()); assertEquals(0, arity.min()); assertEquals(false, arity.isVariable()); assertEquals("0", arity.toString()); } @Test public void testParameterIndex_WhenUndefinedSingleValue() throws Exception { class ImplicitBoolField { @Parameters boolean boolSingleValue; } Range index = Range.parameterIndex(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); assertEquals(0, index.max()); assertEquals(0, index.min()); assertEquals(false, index.isVariable()); assertEquals("0", index.toString()); assertEquals("0+ (0)", index.internalToString()); assertEquals(true, index.isRelative()); assertEquals(true, index.isRelativeToAnchor()); assertEquals(0, index.anchor()); } @Test public void testParameterIndex_WhenUndefinedMultiValue() throws Exception { class ImplicitBoolArrayField { @Parameters boolean[] boolMultiValue; } Range index = Range.parameterIndex(ImplicitBoolArrayField.class.getDeclaredField("boolMultiValue")); assertEquals(Integer.MAX_VALUE, index.max()); assertEquals(0, index.min()); assertEquals(true, index.isVariable()); assertEquals("0..*", index.toString()); } @Test public void testParameterIndex_WhenDefined() throws Exception { class ImplicitBoolField { @Parameters(index = "2..3") boolean boolSingleValue; } Range arity = Range.parameterIndex(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); assertEquals(3, arity.max()); assertEquals(2, arity.min()); assertEquals(false, arity.isVariable()); assertEquals("2..3", arity.toString()); } @Test public void testDefaultArity_Field() throws Exception { class ImplicitBoolField { @Option(names = "-x") boolean x; @Option(names = "-y") int y; @Option(names = "-z") List z; @Parameters boolean a; @Parameters int b; @Parameters List c; } assertEquals(Range.valueOf("0"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("x"))); assertEquals(Range.valueOf("1"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("y"))); assertEquals(Range.valueOf("1"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("z"))); assertEquals(Range.valueOf("1"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("a"))); assertEquals(Range.valueOf("1"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("b"))); assertEquals(Range.valueOf("0..1"), Range.defaultArity(ImplicitBoolField.class.getDeclaredField("c"))); } @SuppressWarnings("deprecation") @Test public void testDefaultArity_Class() { assertEquals(Range.valueOf("0"), Range.defaultArity(Boolean.TYPE)); assertEquals(Range.valueOf("0"), Range.defaultArity(Boolean.class)); assertEquals(Range.valueOf("1"), Range.defaultArity(Integer.TYPE)); assertEquals(Range.valueOf("1"), Range.defaultArity(Integer.class)); assertEquals(Range.valueOf("1"), Range.defaultArity(List.class)); assertEquals(Range.valueOf("1"), Range.defaultArity(String[].class)); assertEquals(Range.valueOf("1"), Range.defaultArity(Map.class)); } @Test public void testParameterCapacity() throws Exception { PositionalParamSpec paramSpec = PositionalParamSpec.builder().index(Range.valueOf("1..2")).arity(Range.valueOf("*")).build(); Method capacity = PositionalParamSpec.class.getDeclaredMethod("capacity"); capacity.setAccessible(true); Range c = (Range) capacity.invoke(paramSpec); assertEquals(Range.valueOf("*"), c); } @Test public void testValueOf_EmptyString() throws Exception { assertEquals(Range.valueOf("*"), Range.valueOf("")); } @Test public void testValueOf_Invalid() throws Exception { assertEquals(Range.valueOf("0..3"), Range.valueOf("..3")); } @Test public void testMaxSetter() { assertEquals(Range.valueOf("0..3"), Range.valueOf("0").max(3)); } @Test public void testIsUnspecified() { class App { @Parameters List unspecified; @Parameters(arity = "2") List specified; } CommandLine cmd = new CommandLine(new App()); assertTrue(cmd.getCommandSpec().positionalParameters().get(0).arity().isUnspecified()); assertFalse(cmd.getCommandSpec().positionalParameters().get(1).arity().isUnspecified()); } @Test public void testRangeEquals_OtherType() { assertFalse(Range.valueOf("0").equals(123)); assertNotEquals(Range.valueOf("0"), 123); } @Test public void testRangeEquals_MinMaxVariable() { assertNotEquals("different max", Range.valueOf("1..1"), Range.valueOf("1..2")); assertNotEquals("different min", Range.valueOf("2..2"), Range.valueOf("1..2")); assertNotEquals("different isVariable", Range.valueOf("1..*"), Range.valueOf("1..2")); assertNotEquals("different min and isVariable", Range.valueOf("1..*"), Range.valueOf("2..2")); assertEquals("same", Range.valueOf("1..*"), Range.valueOf("1..*")); } @Test public void testArityForParameters_booleanFieldImplicitArity1() throws Exception { class ImplicitBoolField { @Parameters boolean boolSingleValue; } Range arity = Range.parameterArity(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); assertEquals(Range.valueOf("1"), arity); assertEquals("1", arity.toString()); } @Test public void testArityForParameters_intFieldImplicitArity1() throws Exception { class ImplicitSingleField { @Parameters int intSingleValue; } Range arity = Range.parameterArity(ImplicitSingleField.class.getDeclaredField("intSingleValue")); assertEquals(Range.valueOf("1"), arity); assertEquals("1", arity.toString()); } @Test public void testArityForParameters_listFieldImplicitArity0_1() throws Exception { class Params { @Parameters(type = Integer.class) List list; } Range arity = Range.parameterArity(Params.class.getDeclaredField("list")); assertEquals(Range.valueOf("0..1"), arity); assertEquals("0..1", arity.toString()); } @Test public void testArityForParameters_arrayFieldImplicitArity0_1() throws Exception { class Args { @Parameters File[] inputFiles; } Range arity = Range.parameterArity(Args.class.getDeclaredField("inputFiles")); assertEquals(Range.valueOf("0..1"), arity); assertEquals("0..1", arity.toString()); } @Test public void testArrayOptionsWithArity0_nConsumeAllArguments() { final double[] DEFAULT_PARAMS = new double[] {1, 2}; class ArrayOptionsArity0_nAndParameters { @Parameters double[] doubleParams = DEFAULT_PARAMS; @Option(names = "-doubles", arity = "0..*") double[] doubleOptions; } ArrayOptionsArity0_nAndParameters params = CommandLine.populateCommand(new ArrayOptionsArity0_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); assertArrayEquals(DEFAULT_PARAMS, params.doubleParams, 0.000001); } @Test public void testArrayOptionsWithArity1_nConsumeAllArguments() { class ArrayOptionsArity1_nAndParameters { @Parameters double[] doubleParams; @Option(names = "-doubles", arity = "1..*") double[] doubleOptions; } ArrayOptionsArity1_nAndParameters params = CommandLine.populateCommand(new ArrayOptionsArity1_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); assertArrayEquals(null, params.doubleParams, 0.000001); } @Test public void testArrayOptionsWithArity2_nConsumeAllArguments() { class ArrayOptionsArity2_nAndParameters { @Parameters double[] doubleParams; @Option(names = "-doubles", arity = "2..*") double[] doubleOptions; } ArrayOptionsArity2_nAndParameters params = CommandLine.populateCommand(new ArrayOptionsArity2_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); assertArrayEquals(null, params.doubleParams, 0.000001); } @Test public void testArrayOptionArity2_nConsumesAllArgumentsUpToClusteredOption() { class ArrayOptionsArity2_nAndParameters { @Parameters String[] stringParams; @Option(names = "-s", arity = "2..*") String[] stringOptions; @Option(names = "-v") boolean verbose; @Option(names = "-f") File file; } ArrayOptionsArity2_nAndParameters params = CommandLine.populateCommand(new ArrayOptionsArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -vfFILE 5.5".split(" ")); assertArrayEquals(Arrays.toString(params.stringOptions), new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); assertTrue(params.verbose); assertEquals(new File("FILE"), params.file); assertArrayEquals(new String[] {"5.5"}, params.stringParams); } @Test public void testArrayOptionArity2_nConsumesAllArgumentIncludingQuotedSimpleOption() { class ArrayOptionArity2_nAndParameters { @Parameters String[] stringParams; @Option(names = "-s", arity = "2..*") String[] stringOptions; @Option(names = "-v") boolean verbose; @Option(names = "-f") File file; } ArrayOptionArity2_nAndParameters params = new ArrayOptionArity2_nAndParameters(); new CommandLine(params).setTrimQuotes(true).parseArgs("-s 1.1 2.2 3.3 4.4 \"-v\" \"-f\" \"FILE\" 5.5".split(" ")); assertArrayEquals(Arrays.toString(params.stringOptions), new String[] {"1.1", "2.2", "3.3", "4.4", "-v", "-f", "FILE", "5.5"}, params.stringOptions); assertFalse("verbose", params.verbose); assertNull("file", params.file); assertArrayEquals(null, params.stringParams); } @Test public void testArrayOptionArity2_nConsumesAllArgumentIncludingQuotedClusteredOption() { class ArrayOptionArity2_nAndParameters { @Parameters String[] stringParams; @Option(names = "-s", arity = "2..*") String[] stringOptions; @Option(names = "-v") boolean verbose; @Option(names = "-f") File file; } ArrayOptionArity2_nAndParameters params = new ArrayOptionArity2_nAndParameters(); new CommandLine(params).setTrimQuotes(true).parseArgs("-s 1.1 2.2 3.3 4.4 \"-vfFILE\" 5.5".split(" ")); assertArrayEquals(Arrays.toString(params.stringOptions), new String[] {"1.1", "2.2", "3.3", "4.4", "-vfFILE", "5.5"}, params.stringOptions); assertFalse("verbose", params.verbose); assertNull("file", params.file); assertArrayEquals(null, params.stringParams); } @Test public void testArrayOptionArity2_nConsumesAllArgumentsUpToNextSimpleOption() { class ArrayOptionArity2_nAndParameters { @Parameters double[] doubleParams; @Option(names = "-s", arity = "2..*") String[] stringOptions; @Option(names = "-v") boolean verbose; @Option(names = "-f") File file; } ArrayOptionArity2_nAndParameters params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -v -f=FILE 5.5".split(" ")); assertArrayEquals(Arrays.toString(params.stringOptions), new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); assertTrue(params.verbose); assertEquals(new File("FILE"), params.file); assertArrayEquals(new double[] {5.5}, params.doubleParams, 0.000001); } @Test public void testArrayOptionArity2_nConsumesAllArgumentsUpToNextOptionWithAttachment() { class ArrayOptionArity2_nAndParameters { @Parameters double[] doubleParams; @Option(names = "-s", arity = "2..*") String[] stringOptions; @Option(names = "-v") boolean verbose; @Option(names = "-f") File file; } ArrayOptionArity2_nAndParameters params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -f=FILE -v 5.5".split(" ")); assertArrayEquals(Arrays.toString(params.stringOptions), new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); assertTrue(params.verbose); assertEquals(new File("FILE"), params.file); assertArrayEquals(new double[] {5.5}, params.doubleParams, 0.000001); } @Test public void testArrayOptionArityNConsumeAllArguments() { class ArrayOptionArityNAndParameters { @Parameters char[] charParams; @Option(names = "-chars", arity = "*") char[] charOptions; } ArrayOptionArityNAndParameters params = CommandLine.populateCommand(new ArrayOptionArityNAndParameters(), "-chars a b c d".split(" ")); assertArrayEquals(Arrays.toString(params.charOptions), new char[] {'a', 'b', 'c', 'd'}, params.charOptions); assertArrayEquals(null, params.charParams); } @Test public void testMissingRequiredParams() { class Example { @Parameters(index = "1", arity = "0..1") String optional; @Parameters(index = "0") String mandatory; } try { CommandLine.populateCommand(new Example(), new String[] {"mandatory"}); } catch (MissingParameterException ex) { fail(); } try { CommandLine.populateCommand(new Example(), new String[0]); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); assertEquals(1, ex.getMissing().size()); assertEquals("", ex.getMissing().get(0).paramLabel()); } } @Test public void testMissingRequiredParams1() { class Tricky1 { @Parameters(index = "2") String anotherMandatory; @Parameters(index = "1", arity = "0..1") String optional; @Parameters(index = "0") String mandatory; } try { CommandLine.populateCommand(new Tricky1(), new String[0]); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameters: '', ''", ex.getMessage()); } try { CommandLine.populateCommand(new Tricky1(), new String[] {"firstonly"}); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void testMissingRequiredParams2() { class Tricky2 { @Parameters(index = "2", arity = "0..1") String anotherOptional; @Parameters(index = "1", arity = "0..1") String optional; @Parameters(index = "0") String mandatory; } try { CommandLine.populateCommand(new Tricky2(), new String[] {"mandatory"}); } catch (MissingParameterException ex) { fail(); } try { CommandLine.populateCommand(new Tricky2(), new String[0]); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void testMissingRequiredParamsWithOptions() { class Tricky3 { @Option(names="-v") boolean more; @Option(names="-t") boolean any; @Parameters(index = "1") String alsoMandatory; @Parameters(index = "0") String mandatory; } try { CommandLine.populateCommand(new Tricky3(), new String[] {"-t", "-v", "mandatory"}); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } try { CommandLine.populateCommand(new Tricky3(), new String[] { "-t", "-v"}); fail("Should not accept missing two mandatory parameters"); } catch (MissingParameterException ex) { assertEquals("Missing required parameters: '', ''", ex.getMessage()); } } @Test public void testMissingRequiredParamWithOption() { class Tricky3 { @Option(names="-t") boolean any; @Parameters(index = "0") String mandatory; } try { CommandLine.populateCommand(new Tricky3(), new String[] {"-t"}); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void testNoMissingRequiredParamErrorIfHelpOptionSpecified() { class App { @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message List allParameters; // no "index" attribute: captures _all_ arguments (as Strings) @Parameters(index = "0") InetAddress host; @Parameters(index = "1") int port; @Parameters(index = "2..*") File[] files; @Option(names = "-?", help = true) boolean help; } CommandLine.populateCommand(new App(), new String[] {"-?"}); try { CommandLine.populateCommand(new App(), new String[0]); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameters: '', ''", ex.getMessage()); } } @Test public void testNoMissingRequiredParamErrorWithLabelIfHelpOptionSpecified() { class App { @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message List allParameters; // no "index" attribute: captures _all_ arguments (as Strings) @Parameters(index = "0", paramLabel = "HOST") InetAddress host; @Parameters(index = "1", paramLabel = "PORT") int port; @Parameters(index = "2..*", paramLabel = "FILES") File[] files; @Option(names = "-?", help = true) boolean help; } CommandLine.populateCommand(new App(), new String[] {"-?"}); try { CommandLine.populateCommand(new App(), new String[0]); fail("Should not accept missing mandatory parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameters: 'HOST', 'PORT'", ex.getMessage()); } } static class BooleanOptionsArity0_nAndParameters { @Parameters String[] params; @Option(names = "-bool", arity = "0..*") boolean bool; @Option(names = {"-v", "-other"}, arity="0..*") boolean vOrOther; @Option(names = "-r") boolean rBoolean; } @Test public void testBooleanOptionsArity0_nFalse() { BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool false".split(" ")); assertFalse(params.bool); } @Test public void testBooleanOptionsArity0_nTrue() { BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool true".split(" ")); assertTrue(params.bool); assertNull(params.params); } @Test public void testBooleanOptionsArity0_nX() { BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool x".split(" ")); assertTrue(params.bool); assertArrayEquals(new String[]{ "x" }, params.params); } @Test public void testBooleanOptionsArity0_nConsume1ArgumentIfPossible() { // ignores varargs BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool=false false true".split(" ")); assertFalse(params.bool); assertArrayEquals(new String[]{ "false", "true" }, params.params); } @Test public void testBooleanOptionsArity0_nRequiresNoArgument() { // ignores varargs BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool".split(" ")); assertTrue(params.bool); } @Test public void testBooleanOptionsArity0_nConsume0ArgumentsIfNextArgIsOption() { // ignores varargs BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool -other".split(" ")); assertTrue(params.bool); assertTrue(params.vOrOther); } @Test public void testBooleanOptionsArity0_nConsume0ArgumentsIfNextArgIsParameter() { // ignores varargs BooleanOptionsArity0_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool 123 -other".split(" ")); assertTrue(params.bool); assertTrue(params.vOrOther); assertArrayEquals(new String[]{ "123"}, params.params); } @Test public void testBooleanOptionsArity0_nFailsIfAttachedParamNotABoolean() { // ignores varargs try { CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool=123 -other".split(" ")); fail("was able to assign 123 to boolean"); } catch (CommandLine.ParameterException ex) { assertEquals("Invalid value for option '-bool': '123' is not a boolean", ex.getMessage()); } } @Test public void testBooleanOptionsArity0_nShortFormFailsIfAttachedParamNotABoolean() { // ignores varargs try { CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-rv234 -bool".split(" ")); fail("Expected exception"); } catch (UnmatchedArgumentException ok) { assertEquals("Unknown option: '-234' (while processing option: '-rv234')", ok.getMessage()); } } @Test public void testBooleanOptionsArity0_nShortFormFailsIfAttachedParamNotABooleanWithUnmatchedArgsAllowed() { // ignores varargs setTraceLevel("OFF"); CommandLine cmd = new CommandLine(new BooleanOptionsArity0_nAndParameters()).setUnmatchedArgumentsAllowed(true); cmd.parseArgs("-rv234 -bool".split(" ")); assertEquals(Arrays.asList("-234"), cmd.getUnmatchedArguments()); } @Test public void testBooleanOptionsArity0_nShortFormFailsIfAttachedWithSepParamNotABoolean() { // ignores varargs try { CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-rv=234 -bool".split(" ")); fail("was able to assign 234 to boolean"); } catch (CommandLine.ParameterException ex) { assertEquals("Invalid value for option '-other': '234' is not a boolean", ex.getMessage()); } } private static class BooleanOptionsArity1_nAndParameters { @Parameters boolean[] boolParams; @Option(names = "-bool", arity = "1..*") boolean aBoolean; } @Test public void testBooleanOptionsArity1_nConsume1Argument() { // ignores varargs BooleanOptionsArity1_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool false false true".split(" ")); assertFalse(params.aBoolean); assertArrayEquals(new boolean[]{ false, true}, params.boolParams); params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool true false true".split(" ")); assertTrue(params.aBoolean); assertArrayEquals(new boolean[]{ false, true}, params.boolParams); } @Test public void testBooleanOptionsArity1_nCaseInsensitive() { // ignores varargs BooleanOptionsArity1_nAndParameters params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool fAlsE false true".split(" ")); assertFalse(params.aBoolean); assertArrayEquals(new boolean[]{ false, true}, params.boolParams); params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool FaLsE false true".split(" ")); assertFalse(params.aBoolean); assertArrayEquals(new boolean[]{ false, true}, params.boolParams); params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool tRuE false true".split(" ")); assertTrue(params.aBoolean); assertArrayEquals(new boolean[]{ false, true}, params.boolParams); } @Test public void testBooleanOptionsArity1_nErrorIfValueNotTrueOrFalse() { // ignores varargs try { CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool abc".split(" ")); fail("Invalid format abc was accepted for boolean"); } catch (CommandLine.ParameterException expected) { assertEquals("Invalid value for option '-bool': 'abc' is not a boolean", expected.getMessage()); } } @Test public void testBooleanOptionsArity1_nErrorIfValueMissing() { try { CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool".split(" ")); fail("Missing param was accepted for boolean with arity=1"); } catch (CommandLine.ParameterException expected) { assertEquals("Missing required parameter for option '-bool' at index 0 ()", expected.getMessage()); } } @Test public void testBooleanOptionArity0Consumes0Arguments() { class BooleanOptionArity0AndParameters { @Parameters boolean[] boolParams; @Option(names = "-bool", arity = "0") boolean aBoolean; } BooleanOptionArity0AndParameters params = CommandLine.populateCommand(new BooleanOptionArity0AndParameters(), "-bool true false true".split(" ")); assertTrue(params.aBoolean); assertArrayEquals(new boolean[]{true, false, true}, params.boolParams); } @Test(expected = MissingParameterException.class) public void testSingleValueFieldDefaultMinArityIs1() { class App { @Option(names = "-Long") Long aLongField; } CommandLine.populateCommand(new App(), "-Long"); } @Test public void testSingleValueFieldDefaultMinArityIsOne() { class App { @Option(names = "-boolean") boolean booleanField; @Option(names = "-Long") Long aLongField; } try { CommandLine.populateCommand(new App(), "-Long", "-boolean"); fail("should fail"); } catch (CommandLine.ParameterException ex) { assertEquals("Expected parameter for option '-Long' but found '-boolean'", ex.getMessage()); } } /** see issue #279 */ @Test public void testSingleValueFieldWithOptionalParameter_279() { @Command(name="sample") class Sample { @Option(names="--foo", arity="0..1") String foo; } Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertNull("optional option is null when option not specified", sample1.foo); Sample sample2 = CommandLine.populateCommand(new Sample(), "--foo"); // no arguments assertEquals("optional option is empty string when specified without args", "", sample2.foo); Sample sample3 = CommandLine.populateCommand(new Sample(), "--foo", "value"); // no arguments assertEquals("optional option has value when specified", "value", sample3.foo); } /** see issue #280 */ @Test public void testSingleValueFieldWithOptionalParameter_280() { @Command(name="sample") class Sample { @Option(names="--foo", arity="0..1", fallbackValue = "213") String foo; } Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertNull("optional option is null when option not specified", sample1.foo); Sample sample2 = CommandLine.populateCommand(new Sample(), "--foo"); // no arguments assertEquals("optional option is empty string when specified without args", "213", sample2.foo); Sample sample3 = CommandLine.populateCommand(new Sample(), "--foo", "value"); // no arguments assertEquals("optional option has value when specified", "value", sample3.foo); } @Test public void testSingleValueFieldWithTypedOptionalParameter_280() { @Command(name="sample") class Sample { @Option(names="--unit", arity="0..1", fallbackValue = "SECONDS") TimeUnit unit; } Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertNull("optional option is null when option not specified", sample1.unit); Sample sample2 = CommandLine.populateCommand(new Sample(), "--unit"); // no arguments assertEquals("optional option is empty string when specified without args", TimeUnit.SECONDS, sample2.unit); Sample sample3 = CommandLine.populateCommand(new Sample(), "--unit", "MILLISECONDS"); // no arguments assertEquals("optional option has value when specified", TimeUnit.MILLISECONDS, sample3.unit); } @Test public void testListFieldOptionalParamWithFallback_280() { @Command(name="sample") class Sample { @Option(names="--foo", arity="0..*", fallbackValue = "213", defaultValue = "567") List foo; @Option(names = "-x") boolean x; } Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertEquals("option gets default when option not specified", Arrays.asList("567"), sample1.foo); Sample sample2 = CommandLine.populateCommand(new Sample(), "--foo"); // no arguments assertEquals("optional option is fallback list when specified without args", Arrays.asList("213"), sample2.foo); Sample sample2a = CommandLine.populateCommand(new Sample(), "--foo", "--foo"); // no arguments, twice assertEquals("optional option is fallback list for each specified without args", Arrays.asList("213", "213"), sample2a.foo); Sample sample2b = CommandLine.populateCommand(new Sample(), "--foo", "-x"); // no arguments assertEquals("optional option is fallback list for each specified without args", Arrays.asList("213"), sample2b.foo); Sample sample3 = CommandLine.populateCommand(new Sample(), "--foo", "value"); // no arguments assertEquals("optional option has value when specified", Arrays.asList("value"), sample3.foo); } @Test public void testArrayFieldOptionalParamWithFallback_280() { @Command(name="sample") class Sample { @Option(names="--foo", arity="0..*", fallbackValue = "213", defaultValue = "567") String[] foo; @Option(names = "-x") boolean x; } Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertArrayEquals("option gets default when option not specified", new String[]{"567"}, sample1.foo); Sample sample2 = CommandLine.populateCommand(new Sample(), "--foo"); // no arguments assertArrayEquals("optional option is fallback list when specified without args", new String[]{"213"}, sample2.foo); Sample sample2a = CommandLine.populateCommand(new Sample(), "--foo", "--foo"); // no arguments, twice assertArrayEquals("optional option is fallback list for each specified without args", new String[]{"213", "213"}, sample2a.foo); Sample sample2b = CommandLine.populateCommand(new Sample(), "--foo", "-x"); // no arguments assertArrayEquals("optional option is fallback list for each specified without args", new String[]{"213"}, sample2b.foo); Sample sample3 = CommandLine.populateCommand(new Sample(), "--foo", "value"); // no arguments assertArrayEquals("optional option has value when specified", new String[]{"value"}, sample3.foo); } @Test public void testMapFieldOptionalParamWithFallback_280() { @Command(name="sample") class Sample { @Option(names="--foo", arity="0..*", fallbackValue = "a=b", defaultValue = "x=z") Map foo; @Option(names = "-x") boolean x; } Map expected = new LinkedHashMap(); expected.put("x", "z"); Sample sample1 = CommandLine.populateCommand(new Sample()); // not specified assertEquals("default when option not specified", expected, sample1.foo); expected.clear(); expected.put("a", "b"); Sample sample2 = CommandLine.populateCommand(new Sample(), "--foo"); // no arguments assertEquals("fallback when specified without args", expected, sample2.foo); expected.clear(); expected.put("a", "b"); Sample sample2a = CommandLine.populateCommand(new Sample(), "--foo", "--foo"); // no arguments, twice assertEquals("fallback when specified without args twice", expected, sample2a.foo); expected.clear(); expected.put("a", "b"); Sample sample2b = CommandLine.populateCommand(new Sample(), "--foo", "-x"); // no arguments, followed by other option assertEquals("fallback when specified without args followed by other option", expected, sample2b.foo); expected.clear(); expected.put("aa", "bb"); Sample sample3 = CommandLine.populateCommand(new Sample(), "--foo", "aa=bb"); // no arguments assertEquals("optional option has value when specified", expected, sample3.foo); } @Test public void testIntOptionArity1_nConsumes1Argument() { // ignores varargs class IntOptionArity1_nAndParameters { @Parameters int[] intParams; @Option(names = "-int", arity = "1..*") int anInt; } IntOptionArity1_nAndParameters params = CommandLine.populateCommand(new IntOptionArity1_nAndParameters(), "-int 23 42 7".split(" ")); assertEquals(23, params.anInt); assertArrayEquals(new int[]{ 42, 7 }, params.intParams); } @Test public void testArrayOptionsWithArity0Consume0Arguments() { class OptionsArray0ArityAndParameters { @Parameters double[] doubleParams; @Option(names = "-doubles", arity = "0") double[] doubleOptions; } OptionsArray0ArityAndParameters params = CommandLine.populateCommand(new OptionsArray0ArityAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[0], params.doubleOptions, 0.000001); assertArrayEquals(new double[]{1.1, 2.2, 3.3, 4.4}, params.doubleParams, 0.000001); } @Test public void testArrayOptionWithArity1Consumes1Argument() { class Options1ArityAndParameters { @Parameters double[] doubleParams; @Option(names = "-doubles", arity = "1") double[] doubleOptions; } Options1ArityAndParameters params = CommandLine.populateCommand(new Options1ArityAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1}, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{2.2, 3.3, 4.4}, params.doubleParams, 0.000001); // repeated occurrence params = CommandLine.populateCommand(new Options1ArityAndParameters(), "-doubles 1.1 -doubles 2.2 -doubles 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3}, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{4.4}, params.doubleParams, 0.000001); } private static class ArrayOptionArity2AndParameters { @Parameters double[] doubleParams; @Option(names = "-doubles", arity = "2") double[] doubleOptions; } @Test public void testArrayOptionWithArity2Consumes2Arguments() { ArrayOptionArity2AndParameters params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, }, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{3.3, 4.4}, params.doubleParams, 0.000001); // repeated occurrence params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles 1.1 2.2 -doubles 3.3 4.4 0".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3, 4.4 }, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{ 0.0 }, params.doubleParams, 0.000001); } @Test public void testArrayOptionsWithArity2Consume2ArgumentsEvenIfFirstIsAttached() { ArrayOptionArity2AndParameters params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1.1 2.2 3.3 4.4".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, }, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{3.3, 4.4}, params.doubleParams, 0.000001); // repeated occurrence params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1.1 2.2 -doubles=3.3 4.4 0".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); assertArrayEquals(new double[]{0}, params.doubleParams, 0.000001); } /** Arity should not limit the total number of values put in an array or collection #191 */ @Test public void testArrayOptionsWithArity2MayContainMoreThan2Values() { ArrayOptionArity2AndParameters params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1 2 -doubles 3 4 -doubles 5 6".split(" ")); assertArrayEquals(Arrays.toString(params.doubleOptions), new double[] {1, 2, 3, 4, 5, 6 }, params.doubleOptions, 0.000001); assertArrayEquals(null, params.doubleParams, 0.000001); } @Test public void testArrayOptionWithoutArityConsumesOneArgument() { // #192 class OptionsNoArityAndParameters { @Parameters char[] charParams; @Option(names = "-chars") char[] charOptions; } OptionsNoArityAndParameters params = CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars a b c d".split(" ")); assertArrayEquals(Arrays.toString(params.charOptions), new char[] {'a', }, params.charOptions); assertArrayEquals(Arrays.toString(params.charParams), new char[] {'b', 'c', 'd'}, params.charParams); // repeated occurrence params = CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars a -chars b c d".split(" ")); assertArrayEquals(Arrays.toString(params.charOptions), new char[] {'a', 'b', }, params.charOptions); assertArrayEquals(Arrays.toString(params.charParams), new char[] {'c', 'd'}, params.charParams); try { CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars".split(" ")); fail("expected MissingParameterException"); } catch (MissingParameterException ok) { assertEquals("Missing required parameter for option '-chars' ()", ok.getMessage()); assertEquals(1, ok.getMissing().size()); assertTrue(ok.getMissing().get(0).toString(), ok.getMissing().get(0) instanceof Model.OptionSpec); } } @Test public void testArrayParametersWithDefaultArity() { class ArrayParamsDefaultArity { @Parameters List params; } ArrayParamsDefaultArity params = CommandLine.populateCommand(new ArrayParamsDefaultArity(), "a", "b", "c"); assertEquals(Arrays.asList("a", "b", "c"), params.params); params = CommandLine.populateCommand(new ArrayParamsDefaultArity(), "a"); assertEquals(Arrays.asList("a"), params.params); params = CommandLine.populateCommand(new ArrayParamsDefaultArity()); assertEquals(null, params.params); } @Test public void testArrayParametersWithArityMinusOneToN() { class ArrayParamsNegativeArity { @Parameters(arity = "-1..*") List params; } try { new CommandLine(new ArrayParamsNegativeArity()); fail("Expected exception"); } catch (InitializationException ex) { assertEquals("Invalid negative range (min=-1, max=2147483647)", ex.getMessage()); } } @Test public void testArrayParametersArity0_n() { class ArrayParamsArity0_n { @Parameters(arity = "0..*") List params; } ArrayParamsArity0_n params = CommandLine.populateCommand(new ArrayParamsArity0_n(), "a", "b", "c"); assertEquals(Arrays.asList("a", "b", "c"), params.params); params = CommandLine.populateCommand(new ArrayParamsArity0_n(), "a"); assertEquals(Arrays.asList("a"), params.params); params = CommandLine.populateCommand(new ArrayParamsArity0_n()); assertEquals(null, params.params); } @Test public void testArrayParametersArity1_n() { class ArrayParamsArity1_n { @Parameters(arity = "1..*") List params; } ArrayParamsArity1_n params = CommandLine.populateCommand(new ArrayParamsArity1_n(), "a", "b", "c"); assertEquals(Arrays.asList("a", "b", "c"), params.params); params = CommandLine.populateCommand(new ArrayParamsArity1_n(), "a"); assertEquals(Arrays.asList("a"), params.params); try { params = CommandLine.populateCommand(new ArrayParamsArity1_n()); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void testMapOptionArity1_n() { class MapParamsArity1_n { @Option(names = {"-D", "--def"}, arity = "1..*", split = ",") Map params; } // most verbose MapParamsArity1_n params = CommandLine.populateCommand(new MapParamsArity1_n(), "--def", "a=1", "--def", "b=2", "--def", "c=3"); Map expected = new LinkedHashMap(); expected.put("a", 1); expected.put("b", 2); expected.put("c", 3); assertEquals(expected, params.params); // option name once, followed by values params = CommandLine.populateCommand(new MapParamsArity1_n(), "--def", "aa=11", "bb=22", "cc=33"); expected.clear(); expected.put("aa", 11); expected.put("bb", 22); expected.put("cc", 33); assertEquals(expected, params.params); // most compact params = CommandLine.populateCommand(new MapParamsArity1_n(), "-Dx=4,y=5,z=6"); expected.clear(); expected.put("x", 4); expected.put("y", 5); expected.put("z", 6); assertEquals(expected, params.params); try { params = CommandLine.populateCommand(new MapParamsArity1_n(), "--def"); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter for option '--def' at index 0 ()", ex.getMessage()); } } @Test public void testMissingPositionalParameters() { class App { @Parameters(index = "0", paramLabel = "PARAM1") String p1; @Parameters(index = "1", paramLabel = "PARAM2") String p2; @Parameters(index = "2", paramLabel = "PARAM3") String p3; } try { CommandLine.populateCommand(new App(), "a"); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameters: 'PARAM2', 'PARAM3'", ex.getMessage()); } } @Test public void testArrayParametersArity2_n() { class ArrayParamsArity2_n { @Parameters(arity = "2..*") List params; } ArrayParamsArity2_n params = CommandLine.populateCommand(new ArrayParamsArity2_n(), "a", "b", "c"); assertEquals(Arrays.asList("a", "b", "c"), params.params); try { params = CommandLine.populateCommand(new ArrayParamsArity2_n(), "a"); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [a]", ex.getMessage()); } try { params = CommandLine.populateCommand(new ArrayParamsArity2_n()); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but none were specified.", ex.getMessage()); } } @Test public void testNonVarargArrayParametersWithArity0() { class NonVarArgArrayParamsZeroArity { @Parameters(arity = "0") List params; } try { CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a", "b", "c"); fail("Expected UnmatchedArgumentException"); } catch (UnmatchedArgumentException ex) { assertEquals("Unmatched arguments from index 0: 'a', 'b', 'c'", ex.getMessage()); } try { CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a"); fail("Expected UnmatchedArgumentException"); } catch (UnmatchedArgumentException ex) { assertEquals("Unmatched argument at index 0: 'a'", ex.getMessage()); } NonVarArgArrayParamsZeroArity params = CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity()); assertEquals(null, params.params); } @Test public void testNonVarargArrayParametersWithArity1() { class NonVarArgArrayParamsArity1 { @Parameters(arity = "1") List params; } NonVarArgArrayParamsArity1 actual = CommandLine.populateCommand(new NonVarArgArrayParamsArity1(), "a", "b", "c"); assertEquals(Arrays.asList("a", "b", "c"), actual.params); NonVarArgArrayParamsArity1 params = CommandLine.populateCommand(new NonVarArgArrayParamsArity1(), "a"); assertEquals(Arrays.asList("a"), params.params); try { params = CommandLine.populateCommand(new NonVarArgArrayParamsArity1()); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void testNonVarargArrayParametersWithArity2() { class NonVarArgArrayParamsArity2 { @Parameters(arity = "2") List params; } NonVarArgArrayParamsArity2 params = null; try { CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a", "b", "c"); fail("expected MissingParameterException"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [c]", ex.getMessage()); } try { params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a"); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [a]", ex.getMessage()); } try { params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2()); fail("Should not accept input with missing parameter"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but none were specified.", ex.getMessage()); } } @Test public void testMixPositionalParamsWithOptions_ParamsUnboundedArity() { class Arg { @Parameters(arity = "1..*") List parameters; @Option(names = "-o") List options; } Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3", "p4"); assertEquals(Arrays.asList("p1", "p2", "p3", "p4"), result.parameters); assertEquals(Arrays.asList("v1", "v2"), result.options); Arg result2 = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "-o", "v2", "p3"); assertEquals(Arrays.asList("p1", "p3"), result2.parameters); assertEquals(Arrays.asList("v1", "v2"), result2.options); try { CommandLine.populateCommand(new Arg(), "-o", "v1", "-o", "v2"); fail("Expected MissingParameterException"); } catch (MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } @Test public void test130MixPositionalParamsWithOptions() { @Command(name = "test-command", description = "tests help from a command script") class Arg { @Parameters(description = "some parameters") List parameters; @Option(names = {"-cp", "--codepath"}, description = "the codepath") List codepath; } Arg result = CommandLine.populateCommand(new Arg(), "--codepath", "/usr/x.jar", "placeholder", "-cp", "/bin/y.jar", "another"); assertEquals(Arrays.asList("/usr/x.jar", "/bin/y.jar"), result.codepath); assertEquals(Arrays.asList("placeholder", "another"), result.parameters); } @Test public void test130MixPositionalParamsWithOptions1() { class Arg { @Parameters List parameters; @Option(names = "-o") List options; } Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3"); assertEquals(Arrays.asList("v1", "v2"), result.options); assertEquals(Arrays.asList("p1", "p2", "p3"), result.parameters); } @Test public void test130MixPositionalParamsWithOptionsArity() { class Arg { @Parameters(arity = "2") List parameters; @Option(names = "-o") List options; } Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3", "p4"); assertEquals(Arrays.asList("v1", "v2"), result.options); assertEquals(Arrays.asList("p1", "p2", "p3", "p4"), result.parameters); try { CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "-o", "v2", "p3"); fail("Expected MissingParameterException"); } catch (MissingParameterException ex) { assertEquals("Expected parameter 2 (of 2 mandatory parameters) for positional parameter at index 0..* () but found '-o'", ex.getMessage()); assertEquals(1, ex.getMissing().size()); assertTrue(ex.getMissing().get(0).toString(), ex.getMissing().get(0) instanceof PositionalParamSpec); } try { CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3"); fail("Expected MissingParameterException"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [p3]", ex.getMessage()); assertEquals(1, ex.getMissing().size()); assertTrue(ex.getMissing().get(0).toString(), ex.getMissing().get(0) instanceof PositionalParamSpec); } } @Test public void test365_StricterArityValidation() { class Cmd { @Option(names = "-a", arity = "2") String[] a; @Option(names = "-b", arity = "1..2") String[] b; @Option(names = "-c", arity = "2..3") String[] c; @Option(names = "-v") boolean verbose; } assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-a' but found '-a'", new Cmd(), "-a", "1", "-a", "2"); assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-a' but found '-v'", new Cmd(), "-a", "1", "-v"); assertMissing("Expected parameter for option '-b' but found '-v'", new Cmd(), "-b", "-v"); assertMissing("option '-c' at index 0 () requires at least 2 values, but only 1 were specified: [-a]", new Cmd(), "-c", "-a"); assertMissing("Expected parameter 1 (of 2 mandatory parameters) for option '-c' but found '-a'", new Cmd(), "-c", "-a", "1", "2"); assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-c' but found '-a'", new Cmd(), "-c", "1", "-a"); } @Test public void test365_StricterArityValidationWithMaps() { class Cmd { @Option(names = "-a", arity = "2") Map a; @Option(names = "-b", arity = "1..2") Map b; @Option(names = "-c", arity = "2..3") Map c; @Option(names = "-v") boolean verbose; } assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-a' but found '-a'", new Cmd(), "-a", "A=B", "-a", "C=D"); assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-a' but found '-v'", new Cmd(), "-a", "A=B", "-v"); assertMissing("Expected parameter for option '-b' but found '-v'", new Cmd(), "-b", "-v"); assertMissing("option '-c' at index 0 () requires at least 2 values, but only 1 were specified: [-a]", new Cmd(), "-c", "-a"); assertMissing("Expected parameter 1 (of 2 mandatory parameters) for option '-c' but found '-a'", new Cmd(), "-c", "-a", "A=B", "C=D"); assertMissing("Expected parameter 2 (of 2 mandatory parameters) for option '-c' but found '-a'", new Cmd(), "-c", "A=B", "-a"); } private void assertMissing(String expected, Object command, String... args) { try { CommandLine.populateCommand(command, args); fail("Expected missing param exception"); } catch (MissingParameterException ex) { assertEquals(expected, ex.getMessage()); } } @Test public void test285VarargPositionalShouldNotConsumeOptions() { class Cmd { @Option(names = "--alpha") String alpha; @Parameters(index = "0", arity = "1") String foo; @Parameters(index = "1..*", arity = "*") List params; } Cmd cmd = CommandLine.populateCommand(new Cmd(), "foo", "xx", "--alpha", "--beta"); assertEquals("foo", cmd.foo); assertEquals("--beta", cmd.alpha); assertEquals(Arrays.asList("xx"), cmd.params); } @Test public void test285VarargPositionalShouldConsumeOptionsAfterDoubleDash() { class Cmd { @Option(names = "--alpha") String alpha; @Parameters(index = "0", arity = "1") String foo; @Parameters(index = "1..*", arity = "*") List params; } Cmd cmd = CommandLine.populateCommand(new Cmd(), "foo", "--", "xx", "--alpha", "--beta"); assertEquals("foo", cmd.foo); assertEquals(null, cmd.alpha); assertEquals(Arrays.asList("xx", "--alpha", "--beta"), cmd.params); } @Test public void testPositionalShouldCaptureDoubleDashAfterDoubleDash() { class Cmd { @Parameters List params; } Cmd cmd = CommandLine.populateCommand(new Cmd(), "foo", "--", "--", "--"); assertEquals(Arrays.asList("foo", "--", "--"), cmd.params); } @Test public void testVarargPositionalShouldCaptureDoubleDashAfterDoubleDash() { class Cmd { @Parameters(index = "0..*", arity = "*") List params; } Cmd cmd = CommandLine.populateCommand(new Cmd(), "foo", "--", "--", "--"); assertEquals(Arrays.asList("foo", "--", "--"), cmd.params); } @Test public void testIfStopAtPositional_VarargPositionalShouldConsumeOptions() { class Cmd { @Option(names = "--alpha") String alpha; @Parameters(index = "0", arity = "1") String foo; @Parameters(index = "1..*", arity = "*") List params; } Cmd cmd = new Cmd(); System.setProperty("picocli.trace", "DEBUG"); new CommandLine(cmd).setStopAtPositional(true).parseArgs("foo", "xx", "--alpha", "--beta"); assertEquals("foo", cmd.foo); assertEquals(null, cmd.alpha); assertEquals(Arrays.asList("xx", "--alpha", "--beta"), cmd.params); assertTrue(systemErrRule.getLog().contains("Parser was configured with stopAtPositional=true, treating remaining arguments as positional parameters.")); } @Test public void testIfStopAtPositional_PositionalShouldConsumeOptions() { class Cmd { @Option(names = "--alpha") String alpha; @Parameters(index = "0") String foo; @Parameters(index = "1..*") List params; } Cmd cmd = new Cmd(); new CommandLine(cmd).setStopAtPositional(true).parseArgs("foo", "xx", "--alpha", "--beta"); assertEquals("foo", cmd.foo); assertEquals(null, cmd.alpha); assertEquals(Arrays.asList("xx", "--alpha", "--beta"), cmd.params); } @Test public void testPosixAttachedOnly1() { class ValSepC { @Option(names = "-a", arity="2") String[] a; @Option(names = "-b", arity="2", split=",") String[] b; @Option(names = "-c", arity="*", split=",") String[] c; @Option(names = "-d") boolean d; @Option(names = "-e", arity="1", split=",") boolean[] e; @Unmatched String[] remaining; } ValSepC val1 = parseCommonsCliCompatible(new ValSepC(), "-a 1 2 3 4".split(" ")); assertArrayEquals(new String[]{"1", "2"}, val1.a); assertArrayEquals(new String[]{"3", "4"}, val1.remaining); ValSepC val2 = parseCommonsCliCompatible(new ValSepC(), "-a1 -a2 3".split(" ")); assertArrayEquals(new String[]{"1", "2"}, val2.a); assertArrayEquals(new String[]{"3"}, val2.remaining); ValSepC val3 = parseCommonsCliCompatible(new ValSepC(), "-b1,2".split(" ")); assertArrayEquals(new String[]{"1", "2"}, val3.b); ValSepC val4 = parseCommonsCliCompatible(new ValSepC(), "-c 1".split(" ")); assertArrayEquals(new String[]{"1"}, val4.c); ValSepC val5 = parseCommonsCliCompatible(new ValSepC(), "-c1".split(" ")); assertArrayEquals(new String[]{"1"}, val5.c); ValSepC val6 = parseCommonsCliCompatible(new ValSepC(), "-c1,2,3".split(" ")); assertArrayEquals(new String[]{"1", "2", "3"}, val6.c); ValSepC val7 = parseCommonsCliCompatible(new ValSepC(), "-d".split(" ")); assertTrue(val7.d); assertNull(val7.e); ValSepC val8 = parseCommonsCliCompatible(new ValSepC(), "-e true".split(" ")); assertFalse(val8.d); assertTrue(val8.e[0]); } @Test public void testPosixClusteredBooleansAttached() { class App { @Option(names = "-a") boolean a; @Option(names = "-b") boolean b; @Option(names = "-c") boolean c; @Unmatched String[] remaining; } App app = parseCommonsCliCompatible(new App(), "-abc".split(" ")); assertTrue("a", app.a); assertTrue("b", app.b); assertTrue("c", app.c); assertNull(app.remaining); app = parseCommonsCliCompatible(new App(), "-abc -d".split(" ")); assertTrue("a", app.a); assertTrue("b", app.b); assertTrue("c", app.c); assertArrayEquals(new String[]{"-d"}, app.remaining); } @Test public void testPosixClusteredBooleanArraysAttached() { class App { @Option(names = "-a") boolean[] a; @Option(names = "-b") boolean[] b; @Option(names = "-c") boolean[] c; @Unmatched String[] remaining; } App app = parseCommonsCliCompatible(new App(), "-abc".split(" ")); assertArrayEquals("a", new boolean[]{true}, app.a); assertArrayEquals("b", new boolean[]{true}, app.b); assertArrayEquals("c", new boolean[]{true}, app.c); assertNull(app.remaining); app = parseCommonsCliCompatible(new App(), "-abc -d".split(" ")); assertArrayEquals("a", new boolean[]{true}, app.a); assertArrayEquals("b", new boolean[]{true}, app.b); assertArrayEquals("c", new boolean[]{true}, app.c); assertArrayEquals(new String[]{"-d"}, app.remaining); app = parseCommonsCliCompatible(new App(), "-aaabbccc -d".split(" ")); assertArrayEquals("a", new boolean[]{true, true, true}, app.a); assertArrayEquals("b", new boolean[]{true, true}, app.b); assertArrayEquals("c", new boolean[]{true, true, true}, app.c); assertArrayEquals(new String[]{"-d"}, app.remaining); } @Test public void testPosixAttachedOnly3() { class ValSepC { @Option(names = "-a", arity = "2") String[] a; @Unmatched String[] remaining; } try { parseCommonsCliCompatible(new ValSepC(), "-a 1 -a 2".split(" ")); fail("Expected exception: Arity not satisfied"); } catch (Exception ok) { assertEquals("Expected parameter 2 (of 2 mandatory parameters) for option '-a' but found '-a'", ok.getMessage()); } } @Test public void testPosixAttachedOnly2() { class ValSepC { @Option(names = "-a", arity="2") String[] a; @Unmatched String[] remaining; } try { parseCommonsCliCompatible(new ValSepC(), "-a 1".split(" ")); fail(); } catch (Exception ok) { } ValSepC val1 = parseCommonsCliCompatible(new ValSepC(), "-a1".split(" ")); assertArrayEquals(new String[]{"1"}, val1.a); val1 = parseCommonsCliCompatible(new ValSepC(), "-a1 -a2".split(" ")); assertArrayEquals(new String[]{"1", "2"}, val1.a); val1 = parseCommonsCliCompatible(new ValSepC(), "-a1 -a2 -a3".split(" ")); assertArrayEquals(new String[]{"1", "2", "3"}, val1.a); try { parseCommonsCliCompatible(new ValSepC(), "-a 1 -a 2 -a 3".split(" ")); fail(); } catch (Exception ok) { } val1 = parseCommonsCliCompatible(new ValSepC(), "-a 1 2".split(" ")); assertArrayEquals(new String[]{"1", "2"}, val1.a); val1 = parseCommonsCliCompatible(new ValSepC(), "-a1 2".split(" ")); assertArrayEquals(new String[]{"1"}, val1.a); assertArrayEquals(new String[]{"2"}, val1.remaining); } @Test public void testCommonsCliCompatibleSeparatorHandling() { class ValSepC { @Option(names = "-a", arity="1..2") String[] a; @Option(names = "-b", arity="1..2", split=",") String[] b; @Option(names = "-c", arity="1..*", split=",") String[] c; @Unmatched String[] remaining; } ValSepC val3a = parseCommonsCliCompatible(new ValSepC(), "-b1,2,3".split(" ")); assertArrayEquals(new String[]{"1", "2,3"}, val3a.b); } @Test public void testCommonsCliCompatibleSeparatorHandlingForMaps() { class ValSepC { @Option(names = "-b", arity="1..2", split=",") Map b; } ValSepC val3a = parseCommonsCliCompatible(new ValSepC(), "-ba=1,b=2,c=3".split(" ")); Map expected = new LinkedHashMap(); expected.put("a", "1"); expected.put("b", "2,c=3"); assertEquals(expected, val3a.b); } private T parseCommonsCliCompatible(T obj, String[] args) { CommandLine cmd = new CommandLine(obj); cmd.getCommandSpec().parser() .limitSplit(true) .aritySatisfiedByAttachedOptionParam(true); cmd.parseArgs(args); return obj; } @Ignore("#370 Needs support for `last position` in index") @Test public void test370UnboundedArityParam() { class App { @Parameters(index = "0", description = "Folder") private String folder; // TODO add support for $-x in index to mean the x-before-last element @Parameters(index = "1..$-1", description = "Source paths") private String [] sources; // TODO add support for $ in index to mean the last element @Parameters(index = "$", description = "Destination folder path") private String destination; } //setTraceLevel("DEBUG"); App app = new App(); new CommandLine(app) .setOverwrittenOptionsAllowed(true) .parseArgs("folder source1 source2 source3 destination".split(" ")); assertEquals("folder", app.folder); assertArrayEquals(new String[]{"source1", "source2", "source2"}, app.sources); assertEquals("destination", app.destination); //CommandLine.usage(new App(), System.out); } @Test public void testArityZeroForBooleanOption() { class App { @Option(names = "--explicit", arity = "0") boolean explicit; @Option(names = "--implicit") boolean implicit; } try { new CommandLine(new App()).parseArgs("--implicit=false --explicit=false".split(" ")); fail("--explicit option should not accept parameters"); } catch (ParameterException ex) { assertEquals("option '--explicit' () should be specified without 'false' parameter", ex.getMessage()); } } @Test(expected = InitializationException.class) public void testRangeConstructorDisallowsNegativeMin() { new Range(-1, 2, false, false, ""); } @Test(expected = InitializationException.class) public void testRangeConstructorDisallowsNegativeMax() { new Range(0, -2, false, false, ""); } @Test public void testRangeArityMismatchingTypeCount() { class InvalidMapTypes { @Option(names = "-D", arity = "1..3", type = Integer.class) // invalid: only one type TreeMap map; } try { CommandLine.populateCommand(new InvalidMapTypes(), "-D", "1=a"); fail("expect exception"); } catch (ParameterException ex) { assertEquals("field java.util.TreeMap picocli.ArityTest$1InvalidMapTypes.map needs two types (one for the map key, one for the value) but only has 1 types configured.", ex.getMessage()); } } @Test public void testVariableArityMap() { class App { @Option(names = "-D", arity = "1..*") TreeMap map; } App app = CommandLine.populateCommand(new App(), "-D", "1=a", "2=b", "3=c"); TreeMap map = new TreeMap(); map.put(1, "a"); map.put(2, "b"); map.put(3, "c"); assertEquals(map, app.map); } @Test public void testRangeArity0To3Map0() { class App { @Option(names = "-D", arity = "0..3") TreeMap map; } App app = CommandLine.populateCommand(new App(), "-D"); assertEquals(new TreeMap(), app.map); } @Test public void testRangeArity1To3Map1() { class App { @Option(names = "-D", arity = "1..3") TreeMap map; @Option(names = "-x") int x; } App app = CommandLine.populateCommand(new App(), "-D", "1=a", "-x", "123"); TreeMap map = new TreeMap(); map.put(1, "a"); assertEquals(map, app.map); assertEquals(123, app.x); } @Test public void testRangeArity1To3Map2() { class App { @Option(names = "-D", arity = "1..3") TreeMap map; } App app = CommandLine.populateCommand(new App(), "-D", "1=a", "2=b"); TreeMap map = new TreeMap(); map.put(1, "a"); map.put(2, "b"); assertEquals(map, app.map); } @Test public void testRangeArity1To3Map3() { class App { @Option(names = "-D", arity = "1..3") TreeMap map; } App app = CommandLine.populateCommand(new App(), "-D", "1=a", "2=b", "3=c"); TreeMap map = new TreeMap(); map.put(1, "a"); map.put(2, "b"); map.put(3, "c"); assertEquals(map, app.map); } @Test public void testMapArgumentsArity() { class App { @Parameters(arity = "2") Map map; } try { CommandLine.populateCommand(new App(), "a=c"); } catch (MissingParameterException ex) { assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [a=c]", ex.getMessage()); } } @Test public void testOverlap() { Range r1 = Range.valueOf("1..5"); assertTrue(r1.overlaps(Range.valueOf("1..1"))); assertTrue(r1.overlaps(Range.valueOf("2..2"))); assertTrue(r1.overlaps(Range.valueOf("2..5"))); assertTrue(r1.overlaps(Range.valueOf("5"))); assertTrue(r1.overlaps(Range.valueOf("0..6"))); assertTrue(r1.overlaps(Range.valueOf("0..*"))); assertFalse(r1.overlaps(Range.valueOf("6"))); assertFalse(r1.overlaps(Range.valueOf("0"))); } @Test public void testCustomEndOfOptionsDelimiter() { class App { @Option(names = "-x", arity = "*") List option; @Unmatched List unmatched; } App app = new App(); new CommandLine(app).setEndOfOptionsDelimiter(";").parseArgs("-x", "a", "b", ";", "x", "y"); assertEquals(Arrays.asList("a", "b"), app.option); assertEquals(Arrays.asList("x", "y"), app.unmatched); app = new App(); new CommandLine(app).parseArgs("-x", "a", "b", "--", "x", "y"); assertEquals(Arrays.asList("a", "b"), app.option); assertEquals(Arrays.asList("x", "y"), app.unmatched); app = new App(); new CommandLine(app).parseArgs("-x", "a", "b", ";", "x", "y"); assertEquals(Arrays.asList("a", "b", ";", "x", "y"), app.option); } @Test public void testUnmatchedListCleared() { class App { @Unmatched List unmatchedList; @Unmatched String[] unmatchedArray; } App app = new App(); CommandLine cmd = new CommandLine(app); cmd.parseArgs("--", "a", "b"); assertEquals(Arrays.asList("a", "b"), app.unmatchedList); assertArrayEquals(new String[]{"a", "b"}, app.unmatchedArray); cmd.parseArgs("--", "x", "y"); assertEquals(Arrays.asList("x", "y"), app.unmatchedList); assertArrayEquals(new String[]{"x", "y"}, app.unmatchedArray); } }