http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/e2f9d5ed/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java new file mode 100644 index 0000000..43e0732 --- /dev/null +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java @@ -0,0 +1,2921 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.logging.log4j.core.tools.picocli; + +import org.junit.Ignore; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.sql.Time; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import static java.util.concurrent.TimeUnit.*; +import static org.junit.Assert.*; +import static org.apache.logging.log4j.core.tools.picocli.CommandLine.*; + +/** + * Tests for the CommandLine argument parsing interpreter functionality. + */ +// DONE arrays +// DONE collection fields +// DONE all built-in types +// DONE private fields, public fields (TODO methods?) +// DONE arity 2, 3 +// DONE arity -1, -2, -3 +// TODO arity ignored for single-value types (non-array, non-collection) +// DONE positional arguments with '--' separator (where positional arguments look like options) +// DONE positional arguments without '--' separator (based on arity of last option?) +// DONE positional arguments based on arity of last option +// TODO ambiguous options: writing --input ARG (as opposed to --input=ARG) is ambiguous, +// meaning it is not possible to tell whether ARG is option's argument or a positional argument. +// In usage patterns this will be interpreted as an option with argument only if a description (covered below) +// for that option is provided. +// Otherwise it will be interpreted as an option and separate positional argument. +// TODO ambiguous short options: ambiguity with the -f FILE and -fFILE notation. +// In the latter case it is not possible to tell whether it is a number of stacked short options, +// or an option with an argument. These notations will be interpreted as an option with argument only if a +// description for the option is provided. +// DONE compact flags +// DONE compact flags where last option has an argument, separate by space +// DONE compact flags where last option has an argument attached to the option character +// DONE long options with argument separate by space +// DONE long options with argument separated by '=' (no spaces) +// TODO document that if arity>1 and args="-opt=val1 val2", arity overrules the "=": both values are assigned +// TODO test superclass bean and child class bean where child class field shadows super class and have same annotation Option name +// TODO test superclass bean and child class bean where child class field shadows super class and have different annotation Option name +// DONE -vrx, -vro outputFile, -vrooutputFile, -vro=outputFile, -vro:outputFile, -vro=, -vro:, -vro +// DONE --out outputFile, --out=outputFile, --out:outputFile, --out=, --out:, --out +public class CommandLineTest { + @Test + public void testVersion() { + assertEquals("0.9.8", CommandLine.VERSION); + } + + private static class SupportedTypes { + @Option(names = "-boolean") boolean booleanField; + @Option(names = "-Boolean") Boolean aBooleanField; + @Option(names = "-byte") byte byteField; + @Option(names = "-Byte") Byte aByteField; + @Option(names = "-char") char charField; + @Option(names = "-Character") Character aCharacterField; + @Option(names = "-short") short shortField; + @Option(names = "-Short") Short aShortField; + @Option(names = "-int") int intField; + @Option(names = "-Integer") Integer anIntegerField; + @Option(names = "-long") long longField; + @Option(names = "-Long") Long aLongField; + @Option(names = "-float") float floatField; + @Option(names = "-Float") Float aFloatField; + @Option(names = "-double") double doubleField; + @Option(names = "-Double") Double aDoubleField; + @Option(names = "-String") String aStringField; + @Option(names = "-StringBuilder") StringBuilder aStringBuilderField; + @Option(names = "-CharSequence") CharSequence aCharSequenceField; + @Option(names = "-File") File aFileField; + @Option(names = "-URL") URL anURLField; + @Option(names = "-URI") URI anURIField; + @Option(names = "-Date") Date aDateField; + @Option(names = "-Time") Time aTimeField; + @Option(names = "-BigDecimal") BigDecimal aBigDecimalField; + @Option(names = "-BigInteger") BigInteger aBigIntegerField; + @Option(names = "-Charset") Charset aCharsetField; + @Option(names = "-InetAddress") InetAddress anInetAddressField; + @Option(names = "-Pattern") Pattern aPatternField; + @Option(names = "-UUID") UUID anUUIDField; + } + @Test + public void testDefaults() { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes()); + assertEquals("boolean", false, bean.booleanField); + assertEquals("Boolean", null, bean.aBooleanField); + assertEquals("byte", 0, bean.byteField); + assertEquals("Byte", null, bean.aByteField); + assertEquals("char", 0, bean.charField); + assertEquals("Character", null, bean.aCharacterField); + assertEquals("short", 0, bean.shortField); + assertEquals("Short", null, bean.aShortField); + assertEquals("int", 0, bean.intField); + assertEquals("Integer", null, bean.anIntegerField); + assertEquals("long", 0, bean.longField); + assertEquals("Long", null, bean.aLongField); + assertEquals("float", 0f, bean.floatField, Float.MIN_VALUE); + assertEquals("Float", null, bean.aFloatField); + assertEquals("double", 0d, bean.doubleField, Double.MIN_VALUE); + assertEquals("Double", null, bean.aDoubleField); + assertEquals("String", null, bean.aStringField); + assertEquals("StringBuilder", null, bean.aStringBuilderField); + assertEquals("CharSequence", null, bean.aCharSequenceField); + assertEquals("File", null, bean.aFileField); + assertEquals("URL", null, bean.anURLField); + assertEquals("URI", null, bean.anURIField); + assertEquals("Date", null, bean.aDateField); + assertEquals("Time", null, bean.aTimeField); + assertEquals("BigDecimal", null, bean.aBigDecimalField); + assertEquals("BigInteger", null, bean.aBigIntegerField); + assertEquals("Charset", null, bean.aCharsetField); + assertEquals("InetAddress", null, bean.anInetAddressField); + assertEquals("Pattern", null, bean.aPatternField); + assertEquals("UUID", null, bean.anUUIDField); + } + @Test + public void testTypeConversionSucceedsForValidInput() + throws MalformedURLException, URISyntaxException, UnknownHostException, ParseException { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), + "-boolean", "-Boolean", // + "-byte", "12", "-Byte", "23", // + "-char", "p", "-Character", "i", // + "-short", "34", "-Short", "45", // + "-int", "56", "-Integer", "67", // + "-long", "78", "-Long", "89", // + "-float", "1.23", "-Float", "2.34", // + "-double", "3.45", "-Double", "4.56", // + "-String", "abc", "-StringBuilder", "bcd", "-CharSequence", "xyz", // + "-File", "abc.txt", // + "-URL", "http://pico-cli.github.io", // + "-URI", "http://pico-cli.github.io/index.html", // + "-Date", "2017-01-30", // + "-Time", "23:59:59", // + "-BigDecimal", "12345678901234567890.123", // + "-BigInteger", "123456789012345678901", // + "-Charset", "UTF8", // + "-InetAddress", InetAddress.getLocalHost().getHostName(), // + "-Pattern", "a*b", // + "-UUID", "c7d51423-bf9d-45dd-a30d-5b16fafe42e2" + ); + assertEquals("boolean", true, bean.booleanField); + assertEquals("Boolean", Boolean.TRUE, bean.aBooleanField); + assertEquals("byte", 12, bean.byteField); + assertEquals("Byte", Byte.valueOf((byte) 23), bean.aByteField); + assertEquals("char", 'p', bean.charField); + assertEquals("Character", Character.valueOf('i'), bean.aCharacterField); + assertEquals("short", 34, bean.shortField); + assertEquals("Short", Short.valueOf((short) 45), bean.aShortField); + assertEquals("int", 56, bean.intField); + assertEquals("Integer", Integer.valueOf(67), bean.anIntegerField); + assertEquals("long", 78L, bean.longField); + assertEquals("Long", Long.valueOf(89L), bean.aLongField); + assertEquals("float", 1.23f, bean.floatField, Float.MIN_VALUE); + assertEquals("Float", Float.valueOf(2.34f), bean.aFloatField); + assertEquals("double", 3.45, bean.doubleField, Double.MIN_VALUE); + assertEquals("Double", Double.valueOf(4.56), bean.aDoubleField); + assertEquals("String", "abc", bean.aStringField); + assertEquals("StringBuilder type", StringBuilder.class, bean.aStringBuilderField.getClass()); + assertEquals("StringBuilder", "bcd", bean.aStringBuilderField.toString()); + assertEquals("CharSequence", "xyz", bean.aCharSequenceField); + assertEquals("File", new File("abc.txt"), bean.aFileField); + assertEquals("URL", new URL("http://pico-cli.github.io"), bean.anURLField); + assertEquals("URI", new URI("http://pico-cli.github.io/index.html"), bean.anURIField); + assertEquals("Date", new SimpleDateFormat("yyyy-MM-dd").parse("2017-01-30"), bean.aDateField); + assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss").parse("23:59:59").getTime()), bean.aTimeField); + assertEquals("BigDecimal", new BigDecimal("12345678901234567890.123"), bean.aBigDecimalField); + assertEquals("BigInteger", new BigInteger("123456789012345678901"), bean.aBigIntegerField); + assertEquals("Charset", Charset.forName("UTF8"), bean.aCharsetField); + assertEquals("InetAddress", InetAddress.getByName(InetAddress.getLocalHost().getHostName()), bean.anInetAddressField); + assertEquals("Pattern", Pattern.compile("a*b").pattern(), bean.aPatternField.pattern()); + assertEquals("UUID", UUID.fromString("c7d51423-bf9d-45dd-a30d-5b16fafe42e2"), bean.anUUIDField); + } + @Test + public void testByteFieldsAreDecimal() { + try { + CommandLine.populateCommand(new SupportedTypes(), "-byte", "0x1F", "-Byte", "0x0F"); + fail("Should fail on hex input"); + } catch (ParameterException expected) { + assertEquals("Could not convert '0x1F' to byte for option '-byte'" + + ": java.lang.NumberFormatException: For input string: \"0x1F\"", expected.getMessage()); + } + } + @Test + public void testCustomByteConverterAcceptsHexadecimalDecimalAndOctal() { + SupportedTypes bean = new SupportedTypes(); + CommandLine commandLine = new CommandLine(bean); + ITypeConverter<Byte> converter = new ITypeConverter<Byte>() { + public Byte convert(String s) { + return Byte.decode(s); + } + }; + commandLine.registerConverter(Byte.class, converter); + commandLine.registerConverter(Byte.TYPE, converter); + commandLine.parse("-byte", "0x1F", "-Byte", "0x0F"); + assertEquals(0x1F, bean.byteField); + assertEquals(Byte.valueOf((byte) 0x0F), bean.aByteField); + + commandLine.parse("-byte", "010", "-Byte", "010"); + assertEquals(8, bean.byteField); + assertEquals(Byte.valueOf((byte) 8), bean.aByteField); + + commandLine.parse("-byte", "34", "-Byte", "34"); + assertEquals(34, bean.byteField); + assertEquals(Byte.valueOf((byte) 34), bean.aByteField); + } + @Test + public void testShortFieldsAreDecimal() { + try { + CommandLine.populateCommand(new SupportedTypes(), "-short", "0xFF", "-Short", "0x6FFE"); + fail("Should fail on hex input"); + } catch (ParameterException expected) { + assertEquals("Could not convert '0xFF' to short for option '-short'" + + ": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage()); + } + } + @Test + public void testCustomShortConverterAcceptsHexadecimalDecimalAndOctal() { + SupportedTypes bean = new SupportedTypes(); + CommandLine commandLine = new CommandLine(bean); + ITypeConverter<Short> shortConverter = new ITypeConverter<Short>() { + public Short convert(String s) { + return Short.decode(s); + } + }; + commandLine.registerConverter(Short.class, shortConverter); + commandLine.registerConverter(Short.TYPE, shortConverter); + commandLine.parse("-short", "0xFF", "-Short", "0x6FFE"); + assertEquals(0xFF, bean.shortField); + assertEquals(Short.valueOf((short) 0x6FFE), bean.aShortField); + + commandLine.parse("-short", "010", "-Short", "010"); + assertEquals(8, bean.shortField); + assertEquals(Short.valueOf((short) 8), bean.aShortField); + + commandLine.parse("-short", "34", "-Short", "34"); + assertEquals(34, bean.shortField); + assertEquals(Short.valueOf((short) 34), bean.aShortField); + } + @Test + public void testIntFieldsAreDecimal() { + try { + CommandLine.populateCommand(new SupportedTypes(), "-int", "0xFF", "-Integer", "0xFFFF"); + fail("Should fail on hex input"); + } catch (ParameterException expected) { + assertEquals("Could not convert '0xFF' to int for option '-int'" + + ": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage()); + } + } + @Test + public void testCustomIntConverterAcceptsHexadecimalDecimalAndOctal() { + SupportedTypes bean = new SupportedTypes(); + CommandLine commandLine = new CommandLine(bean); + ITypeConverter<Integer> intConverter = new ITypeConverter<Integer>() { + public Integer convert(String s) { + return Integer.decode(s); + } + }; + commandLine.registerConverter(Integer.class, intConverter); + commandLine.registerConverter(Integer.TYPE, intConverter); + commandLine.parse("-int", "0xFF", "-Integer", "0xFFFF"); + assertEquals(255, bean.intField); + assertEquals(Integer.valueOf(0xFFFF), bean.anIntegerField); + + commandLine.parse("-int", "010", "-Integer", "010"); + assertEquals(8, bean.intField); + assertEquals(Integer.valueOf(8), bean.anIntegerField); + + commandLine.parse("-int", "34", "-Integer", "34"); + assertEquals(34, bean.intField); + assertEquals(Integer.valueOf(34), bean.anIntegerField); + } + @Test + public void testLongFieldsAreDecimal() { + try { + CommandLine.populateCommand(new SupportedTypes(), "-long", "0xAABBCC", "-Long", "0xAABBCCDD"); + fail("Should fail on hex input"); + } catch (ParameterException expected) { + assertEquals("Could not convert '0xAABBCC' to long for option '-long'" + + ": java.lang.NumberFormatException: For input string: \"0xAABBCC\"", expected.getMessage()); + } + } + @Test + public void testCustomLongConverterAcceptsHexadecimalDecimalAndOctal() { + SupportedTypes bean = new SupportedTypes(); + CommandLine commandLine = new CommandLine(bean); + ITypeConverter<Long> longConverter = new ITypeConverter<Long>() { + public Long convert(String s) { + return Long.decode(s); + } + }; + commandLine.registerConverter(Long.class, longConverter); + commandLine.registerConverter(Long.TYPE, longConverter); + commandLine.parse("-long", "0xAABBCC", "-Long", "0xAABBCCDD"); + assertEquals(0xAABBCC, bean.longField); + assertEquals(Long.valueOf(0xAABBCCDDL), bean.aLongField); + + commandLine.parse("-long", "010", "-Long", "010"); + assertEquals(8, bean.longField); + assertEquals(Long.valueOf(8), bean.aLongField); + + commandLine.parse("-long", "34", "-Long", "34"); + assertEquals(34, bean.longField); + assertEquals(Long.valueOf(34), bean.aLongField); + } + @Test(expected = MissingParameterException.class) + public void testSingleValueFieldDefaultMinArityIs1() { + CommandLine.populateCommand(new SupportedTypes(), "-Long"); + } + @Test + public void testSingleValueFieldDefaultMinArityIsOne() { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Long", "-boolean"); + fail("should fail"); + } catch (ParameterException ex) { + assertEquals("Could not convert '-boolean' to Long for option '-Long'" + + ": java.lang.NumberFormatException: For input string: \"-boolean\"", ex.getMessage()); + } + } + @Test + public void testTimeFormatHHmmSupported() throws ParseException { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59"); + assertEquals("Time", new Time(new SimpleDateFormat("HH:mm").parse("23:59").getTime()), bean.aTimeField); + } + @Test + public void testTimeFormatHHmmssSupported() throws ParseException { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58"); + assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss").parse("23:59:58").getTime()), bean.aTimeField); + } + @Test + public void testTimeFormatHHmmssDotSSSSupported() throws ParseException { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58.123"); + assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss.SSS").parse("23:59:58.123").getTime()), bean.aTimeField); + } + @Test + public void testTimeFormatHHmmssCommaSSSSupported() throws ParseException { + SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58,123"); + assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss,SSS").parse("23:59:58,123").getTime()), bean.aTimeField); + } + @Test + public void testTimeFormatHHmmssSSSInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58;123"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'23:59:58;123' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); + } + } + @Test + public void testTimeFormatHHmmssDotInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58."); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'23:59:58.' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); + } + } + @Test + public void testTimeFormatHHmmsssInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:587"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'23:59:587' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); + } + } + @Test + public void testTimeFormatHHmmssColonInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'23:59:' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); + } + } + @Test + public void testDateFormatYYYYmmddInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Date", "20170131"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'20170131' is not a yyyy-MM-dd date for option '-Date'", expected.getMessage()); + } + } + @Test + public void testCharConverterInvalidError() throws ParseException { + try { + CommandLine.populateCommand(new SupportedTypes(), "-Character", "aa"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'aa' is not a single character for option '-Character'", expected.getMessage()); + } + try { + CommandLine.populateCommand(new SupportedTypes(), "-char", "aa"); + fail("Invalid format was accepted"); + } catch (ParameterException expected) { + assertEquals("'aa' is not a single character for option '-char'", expected.getMessage()); + } + } + @Test + public void testNumberConvertersInvalidError() { + parseInvalidValue("-Byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-Short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-Integer", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-int", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-Long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-Float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-Double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + parseInvalidValue("-BigDecimal", "aa", ": java.lang.NumberFormatException"); + parseInvalidValue("-BigInteger", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); + } + @Test + public void testURLConvertersInvalidError() { + parseInvalidValue("-URL", ":::", ": java.net.MalformedURLException: no protocol: :::"); + } + @Test + public void testURIConvertersInvalidError() { + parseInvalidValue("-URI", ":::", ": java.net.URISyntaxException: Expected scheme name at index 0: :::"); + } + @Test + public void testCharsetConvertersInvalidError() { + parseInvalidValue("-Charset", "aa", ": java.nio.charset.UnsupportedCharsetException: aa"); + } + @Test + public void testInetAddressConvertersInvalidError() { + parseInvalidValue("-InetAddress", "%$::a?*!a", ": java.net.UnknownHostException: %$::a?*!a"); + } + @Test + public void testUUIDConvertersInvalidError() { + parseInvalidValue("-UUID", "aa", ": java.lang.IllegalArgumentException: Invalid UUID string: aa"); + } + @Test + public void testRegexPatternConverterInvalidError() { + parseInvalidValue("-Pattern", "[[(aa", String.format(": java.util.regex.PatternSyntaxException: Unclosed character class near index 4%n" + + "[[(aa%n" + + " ^")); + } + + private void parseInvalidValue(String option, String value, String errorMessage) { + try { + CommandLine.populateCommand(new SupportedTypes(), option, value); + fail("Invalid format " + value + " was accepted for " + option); + } catch (ParameterException actual) { + String type = option.substring(1); + String expected = "Could not convert '" + value + "' to " + type + " for option '" + option + "'" + errorMessage; + assertTrue("expected:<" + expected + "> but was:<" + actual.getMessage() + ">", + actual.getMessage().startsWith(actual.getMessage())); + } + } + + @Test + public void testCustomConverter() { + class Glob { + public final String glob; + public Glob(String glob) { this.glob = glob; } + } + class App { + @Parameters Glob globField; + } + class GlobConverter implements ITypeConverter<Glob> { + public Glob convert(String value) throws Exception { return new Glob(value); } + } + CommandLine commandLine = new CommandLine(new App()); + commandLine.registerConverter(Glob.class, new GlobConverter()); + + String[] args = {"a*glob*pattern"}; + List<CommandLine> parsed = commandLine.parse(args); + assertEquals("not empty", 1, parsed.size()); + assertTrue(parsed.get(0).getCommand() instanceof App); + App app = (App) parsed.get(0).getCommand(); + assertEquals(args[0], app.globField.glob); + } + + static class EnumParams { + @Option(names = "-timeUnit") TimeUnit timeUnit; + @Option(names = "-timeUnitArray", arity = "2") TimeUnit[] timeUnitArray; + @Option(names = "-timeUnitList", type = TimeUnit.class, arity = "3") List<TimeUnit> timeUnitList; + } + @Test + public void testEnumTypeConversionSuceedsForValidInput() { + EnumParams params = CommandLine.populateCommand(new EnumParams(), + "-timeUnit DAYS -timeUnitArray HOURS MINUTES -timeUnitList SECONDS MICROSECONDS NANOSECONDS".split(" ")); + assertEquals(DAYS, params.timeUnit); + assertArrayEquals(new TimeUnit[]{HOURS, TimeUnit.MINUTES}, params.timeUnitArray); + List<TimeUnit> expected = new ArrayList<TimeUnit>(Arrays.asList(TimeUnit.SECONDS, TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS)); + assertEquals(expected, params.timeUnitList); + } + @Test + public void testEnumTypeConversionFailsForInvalidInput() { + try { + CommandLine.populateCommand(new EnumParams(), "-timeUnit", "xyz"); + fail("Accepted invalid timeunit"); + } catch (Exception ex) { + assertEquals("Could not convert 'xyz' to TimeUnit for option '-timeUnit'" + + ": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.xyz", ex.getMessage()); + } + } + @Ignore("Requires #14 case-insensitive enum parsing") + @Test + public void testEnumTypeConversionIsCaseInsensitive() { + EnumParams params = CommandLine.populateCommand(new EnumParams(), + "-timeUnit daYS -timeUnitArray hours miNutEs -timeUnitList SEConds MiCROsEconds nanoSEConds".split(" ")); + assertEquals(DAYS, params.timeUnit); + assertArrayEquals(new TimeUnit[]{HOURS, TimeUnit.MINUTES}, params.timeUnitArray); + List<TimeUnit> expected = new ArrayList<TimeUnit>(Arrays.asList(TimeUnit.SECONDS, TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS)); + assertEquals(expected, params.timeUnitList); + } + @Test + public void testEnumArrayTypeConversionFailsForInvalidInput() { + try { + CommandLine.populateCommand(new EnumParams(), "-timeUnitArray", "a", "b"); + fail("Accepted invalid timeunit"); + } catch (Exception ex) { + assertEquals("Could not convert 'a' to TimeUnit[] for option '-timeUnitArray' at index 0 (timeUnitArray)" + + ": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.a", ex.getMessage()); + } + } + @Test + public void testEnumListTypeConversionFailsForInvalidInput() { + try { + CommandLine.populateCommand(new EnumParams(), "-timeUnitList", "DAYS", "b", "c"); + fail("Accepted invalid timeunit"); + } catch (Exception ex) { + assertEquals("Could not convert 'b' to TimeUnit for option '-timeUnitList' at index 1 (timeUnitList)" + + ": java.lang.IllegalArgumentException: No enum constant java.util.concurrent.TimeUnit.b", + ex.getMessage()); + } + } + + @Test + public void testArrayOptionParametersAreAlwaysInstantiated() { + EnumParams params = new EnumParams(); + TimeUnit[] array = params.timeUnitArray; + new CommandLine(params).parse("-timeUnitArray", "DAYS", "HOURS"); + assertNotSame(array, params.timeUnitArray); + } + @Test + public void testListOptionParametersAreInstantiatedIfNull() { + EnumParams params = new EnumParams(); + assertNull(params.timeUnitList); + new CommandLine(params).parse("-timeUnitList", "DAYS", "HOURS", "DAYS"); + assertEquals(Arrays.asList(DAYS, HOURS, DAYS), params.timeUnitList); + } + @Test + public void testListOptionParametersAreReusedInstantiatedIfNonNull() { + EnumParams params = new EnumParams(); + List<TimeUnit> list = new ArrayList<TimeUnit>(); + params.timeUnitList = list; + new CommandLine(params).parse("-timeUnitList", "DAYS", "HOURS", "DAYS"); + assertEquals(Arrays.asList(DAYS, HOURS, DAYS), params.timeUnitList); + assertSame(list, params.timeUnitList); + } + @Test + public void testArrayPositionalParametersAreAppendedNotReplaced() { + class ArrayPositionalParams { + @Parameters() int[] array; + } + ArrayPositionalParams params = new ArrayPositionalParams(); + params.array = new int[3]; + int[] array = params.array; + new CommandLine(params).parse("3", "2", "1"); + assertNotSame(array, params.array); + assertArrayEquals(new int[]{0, 0, 0, 3, 2, 1}, params.array); + } + class ListPositionalParams { + @Parameters(type = Integer.class) List<Integer> list; + } + @Test + public void testListPositionalParametersAreInstantiatedIfNull() { + ListPositionalParams params = new ListPositionalParams(); + assertNull(params.list); + new CommandLine(params).parse("3", "2", "1"); + assertNotNull(params.list); + assertEquals(Arrays.asList(3, 2, 1), params.list); + } + @Test + public void testListPositionalParametersAreReusedIfNonNull() { + ListPositionalParams params = new ListPositionalParams(); + params.list = new ArrayList<Integer>(); + List<Integer> list = params.list; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.list); + assertEquals(Arrays.asList(3, 2, 1), params.list); + } + @Test + public void testListPositionalParametersAreAppendedToIfNonNull() { + ListPositionalParams params = new ListPositionalParams(); + params.list = new ArrayList<Integer>(); + params.list.add(234); + List<Integer> list = params.list; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.list); + assertEquals(Arrays.asList(234, 3, 2, 1), params.list); + } + class SortedSetPositionalParams { + @Parameters(type = Integer.class) SortedSet<Integer> sortedSet; + } + @Test + public void testSortedSetPositionalParametersAreInstantiatedIfNull() { + SortedSetPositionalParams params = new SortedSetPositionalParams(); + assertNull(params.sortedSet); + new CommandLine(params).parse("3", "2", "1"); + assertNotNull(params.sortedSet); + assertEquals(Arrays.asList(1, 2, 3), new ArrayList<Integer>(params.sortedSet)); + } + @Test + public void testSortedSetPositionalParametersAreReusedIfNonNull() { + SortedSetPositionalParams params = new SortedSetPositionalParams(); + params.sortedSet = new TreeSet<Integer>(); + SortedSet<Integer> list = params.sortedSet; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.sortedSet); + assertEquals(Arrays.asList(1, 2, 3), new ArrayList<Integer>(params.sortedSet)); + } + @Test + public void testSortedSetPositionalParametersAreAppendedToIfNonNull() { + SortedSetPositionalParams params = new SortedSetPositionalParams(); + params.sortedSet = new TreeSet<Integer>(); + params.sortedSet.add(234); + SortedSet<Integer> list = params.sortedSet; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.sortedSet); + assertEquals(Arrays.asList(1, 2, 3, 234), new ArrayList<Integer>(params.sortedSet)); + } + class SetPositionalParams { + @Parameters(type = Integer.class) Set<Integer> set; + } + @Test + public void testSetPositionalParametersAreInstantiatedIfNull() { + SetPositionalParams params = new SetPositionalParams(); + assertNull(params.set); + new CommandLine(params).parse("3", "2", "1"); + assertNotNull(params.set); + assertEquals(new HashSet(Arrays.asList(1, 2, 3)), params.set); + } + @Test + public void testSetPositionalParametersAreReusedIfNonNull() { + SetPositionalParams params = new SetPositionalParams(); + params.set = new TreeSet<Integer>(); + Set<Integer> list = params.set; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.set); + assertEquals(new HashSet(Arrays.asList(1, 2, 3)), params.set); + } + @Test + public void testSetPositionalParametersAreAppendedToIfNonNull() { + SetPositionalParams params = new SetPositionalParams(); + params.set = new TreeSet<Integer>(); + params.set.add(234); + Set<Integer> list = params.set; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.set); + assertEquals(new HashSet(Arrays.asList(1, 2, 3, 234)), params.set); + } + class QueuePositionalParams { + @Parameters(type = Integer.class) Queue<Integer> queue; + } + @Test + public void testQueuePositionalParametersAreInstantiatedIfNull() { + QueuePositionalParams params = new QueuePositionalParams(); + assertNull(params.queue); + new CommandLine(params).parse("3", "2", "1"); + assertNotNull(params.queue); + assertEquals(new LinkedList(Arrays.asList(3, 2, 1)), params.queue); + } + @Test + public void testQueuePositionalParametersAreReusedIfNonNull() { + QueuePositionalParams params = new QueuePositionalParams(); + params.queue = new LinkedList<Integer>(); + Queue<Integer> list = params.queue; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.queue); + assertEquals(new LinkedList(Arrays.asList(3, 2, 1)), params.queue); + } + @Test + public void testQueuePositionalParametersAreAppendedToIfNonNull() { + QueuePositionalParams params = new QueuePositionalParams(); + params.queue = new LinkedList<Integer>(); + params.queue.add(234); + Queue<Integer> list = params.queue; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.queue); + assertEquals(new LinkedList(Arrays.asList(234, 3, 2, 1)), params.queue); + } + class CollectionPositionalParams { + @Parameters(type = Integer.class) Collection<Integer> collection; + } + @Test + public void testCollectionPositionalParametersAreInstantiatedIfNull() { + CollectionPositionalParams params = new CollectionPositionalParams(); + assertNull(params.collection); + new CommandLine(params).parse("3", "2", "1"); + assertNotNull(params.collection); + assertEquals(Arrays.asList(3, 2, 1), params.collection); + } + @Test + public void testCollectionPositionalParametersAreReusedIfNonNull() { + CollectionPositionalParams params = new CollectionPositionalParams(); + params.collection = new ArrayList<Integer>(); + Collection<Integer> list = params.collection; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.collection); + assertEquals(Arrays.asList(3, 2, 1), params.collection); + } + @Test + public void testCollectionPositionalParametersAreAppendedToIfNonNull() { + CollectionPositionalParams params = new CollectionPositionalParams(); + params.collection = new ArrayList<Integer>(); + params.collection.add(234); + Collection<Integer> list = params.collection; + new CommandLine(params).parse("3", "2", "1"); + assertSame(list, params.collection); + assertEquals(Arrays.asList(234, 3, 2, 1), params.collection); + } + + @Test(expected = DuplicateOptionAnnotationsException.class) + public void testDuplicateOptionsAreRejected() { + /** Duplicate parameter names are invalid. */ + class DuplicateOptions { + @Option(names = "-duplicate") public int value1; + @Option(names = "-duplicate") public int value2; + } + new CommandLine(new DuplicateOptions()); + } + + @Test(expected = ParameterException.class) + public void testClashingAnnotationsAreRejected() { + class ClashingAnnotation { + @Option(names = "-o") + @Parameters + public String[] bothOptionAndParameters; + } + new CommandLine(new ClashingAnnotation()); + } + + private static class PrivateFinalOptionFields { + @Option(names = "-f") private final String field = null; + @Option(names = "-p") private final int primitive = 43; + } + @Test + public void testCanInitializePrivateFinalFields() { + PrivateFinalOptionFields ff = CommandLine.populateCommand(new PrivateFinalOptionFields(), "-f", "reference value"); + assertEquals("reference value", ff.field); + } + @Ignore("Needs Reject final primitive fields annotated with @Option or @Parameters #68") + @Test + public void testCanInitializeFinalPrimitiveFields() { + PrivateFinalOptionFields ff = CommandLine.populateCommand(new PrivateFinalOptionFields(), "-p", "12"); + assertEquals("primitive value", 12, ff.primitive); + } + @Test + public void testLastValueSelectedIfOptionSpecifiedMultipleTimes() { + CommandLine cmd = new CommandLine(new PrivateFinalOptionFields()).setOverwrittenOptionsAllowed(true); + cmd.parse("-f", "111", "-f", "222"); + PrivateFinalOptionFields ff = (PrivateFinalOptionFields) cmd.getCommand(); + assertEquals("222", ff.field); + } + + private static class PrivateFinalParameterFields { + @Parameters(index = "0") private final String field = null; + @Parameters(index = "1", arity = "0..1") private final int primitive = 43; + } + @Test + public void testCanInitializePrivateFinalParameterFields() { + PrivateFinalParameterFields ff = CommandLine.populateCommand(new PrivateFinalParameterFields(), "ref value"); + assertEquals("ref value", ff.field); + } + @Ignore("Needs Reject final primitive fields annotated with @Option or @Parameters #68") + @Test + public void testCannotInitializePrivateFinalPrimitiveParameterFields() { + PrivateFinalParameterFields ff = CommandLine.populateCommand(new PrivateFinalParameterFields(), "ref value", "12"); + assertEquals("ref value", ff.field); + assertEquals("primitive value", 12, ff.primitive); + } + + private static class RequiredField { + @Option(names = {"-?", "/?"}, help = true) boolean isHelpRequested; + @Option(names = {"-V", "--version"}, versionHelp= true) boolean versionHelp; + @Option(names = {"-h", "--help"}, usageHelp = true) boolean usageHelp; + @Option(names = "--required", required = true) private String required; + @Parameters private String[] remainder; + } + @Test + public void testErrorIfRequiredOptionNotSpecified() { + try { + CommandLine.populateCommand(new RequiredField(), "arg1", "arg2"); + fail("Missing required field should have thrown exception"); + } catch (MissingParameterException ex) { + assertEquals("Missing required option 'required'", ex.getMessage()); + } + } + @Test + public void testNoErrorIfRequiredOptionSpecified() { + CommandLine.populateCommand(new RequiredField(), "--required", "arg1", "arg2"); + } + @Test + public void testNoErrorIfRequiredOptionNotSpecifiedWhenHelpRequested() { + RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "-?"); + assertTrue("help requested", requiredField.isHelpRequested); + } + @Test + public void testNoErrorIfRequiredOptionNotSpecifiedWhenUsageHelpRequested() { + RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "--help"); + assertTrue("usage help requested", requiredField.usageHelp); + } + @Test + public void testNoErrorIfRequiredOptionNotSpecifiedWhenVersionHelpRequested() { + RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "--version"); + assertTrue("version info requested", requiredField.versionHelp); + } + @Test + public void testCommandLine_isUsageHelpRequested_trueWhenSpecified() { + List<CommandLine> parsedCommands = new CommandLine(new RequiredField()).parse("--help"); + assertTrue("usage help requested", parsedCommands.get(0).isUsageHelpRequested()); + } + @Test + public void testCommandLine_isVersionHelpRequested_trueWhenSpecified() { + List<CommandLine> parsedCommands = new CommandLine(new RequiredField()).parse("--version"); + assertTrue("version info requested", parsedCommands.get(0).isVersionHelpRequested()); + } + @Test + public void testCommandLine_isUsageHelpRequested_falseWhenNotSpecified() { + List<CommandLine> parsedCommands = new CommandLine(new RequiredField()).parse("--version"); + assertFalse("usage help requested", parsedCommands.get(0).isUsageHelpRequested()); + } + @Test + public void testCommandLine_isVersionHelpRequested_falseWhenNotSpecified() { + List<CommandLine> parsedCommands = new CommandLine(new RequiredField()).parse("--help"); + assertFalse("version info requested", parsedCommands.get(0).isVersionHelpRequested()); + } + @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: mandatory", ex.getMessage()); + } + } + @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: mandatory, anotherMandatory", ex.getMessage()); + } + try { + CommandLine.populateCommand(new Tricky1(), new String[] {"firstonly"}); + fail("Should not accept missing mandatory parameter"); + } catch (MissingParameterException ex) { + assertEquals("Missing required parameter: anotherMandatory", 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: mandatory", 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: alsoMandatory", 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: mandatory, alsoMandatory", 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: mandatory", ex.getMessage()); + } + } + @Test + public void testNoMissingRequiredParamErrorIfHelpOptionSpecified() { + class App { + @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message + List<String> 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: host, port", ex.getMessage()); + } + } + @Test + public void testHelpRequestedFlagResetWhenParsing_staticMethod() { + RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "-?"); + assertTrue("help requested", requiredField.isHelpRequested); + + requiredField.isHelpRequested = false; + + // should throw error again on second pass (no help was requested here...) + try { + CommandLine.populateCommand(requiredField, "arg1", "arg2"); + fail("Missing required field should have thrown exception"); + } catch (MissingParameterException ex) { + assertEquals("Missing required option 'required'", ex.getMessage()); + } + } + @Test + public void testHelpRequestedFlagResetWhenParsing_instanceMethod() { + RequiredField requiredField = new RequiredField(); + CommandLine commandLine = new CommandLine(requiredField); + commandLine.parse("-?"); + assertTrue("help requested", requiredField.isHelpRequested); + + requiredField.isHelpRequested = false; + + // should throw error again on second pass (no help was requested here...) + try { + commandLine.parse("arg1", "arg2"); + fail("Missing required field should have thrown exception"); + } catch (MissingParameterException ex) { + assertEquals("Missing required option 'required'", ex.getMessage()); + } + } + + private class CompactFields { + @Option(names = "-v") boolean verbose; + @Option(names = "-r") boolean recursive; + @Option(names = "-o") File outputFile; + @Parameters File[] inputFiles; + } + @Test + public void testCompactFieldsAnyOrder() { + //cmd -a -o arg path path + //cmd -o arg -a path path + //cmd -a -o arg -- path path + //cmd -a -oarg path path + //cmd -aoarg path path + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvoout"); + verifyCompact(compact, true, true, "out", null); + + // change order within compact group + compact = CommandLine.populateCommand(new CompactFields(), "-vroout"); + verifyCompact(compact, true, true, "out", null); + + compact = CommandLine.populateCommand(new CompactFields(), "-rv p1 p2".split(" ")); + verifyCompact(compact, true, true, null, fileArray("p1", "p2")); + + compact = CommandLine.populateCommand(new CompactFields(), "-voout p1 p2".split(" ")); + verifyCompact(compact, true, false, "out", fileArray("p1", "p2")); + + compact = CommandLine.populateCommand(new CompactFields(), "-voout -r p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + + compact = CommandLine.populateCommand(new CompactFields(), "-r -v -oout p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + + compact = CommandLine.populateCommand(new CompactFields(), "-oout -r -v p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + + compact = CommandLine.populateCommand(new CompactFields(), "-rvo out p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + + try { + CommandLine.populateCommand(new CompactFields(), "-oout -r -vp1 p2".split(" ")); + fail("should fail: -v does not take an argument"); + } catch (UnmatchedArgumentException ex) { + assertEquals("Unmatched arguments [-p1, p2]", ex.getMessage()); + } + } + + @Test + public void testCompactFieldsWithUnmatchedArguments() { + CommandLine cmd = new CommandLine(new CompactFields()).setUnmatchedArgumentsAllowed(true); + cmd.parse("-oout -r -vp1 p2".split(" ")); + assertEquals(Arrays.asList("-p1", "p2"), cmd.getUnmatchedArguments()); + } + + @Test + public void testCompactWithOptionParamSeparatePlusParameters() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-r -v -o out p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + } + + @Test + public void testCompactWithOptionParamAttachedEqualsSeparatorChar() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=out p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + } + + @Test + public void testCompactWithOptionParamAttachedColonSeparatorChar() { + CompactFields compact = new CompactFields(); + CommandLine cmd = new CommandLine(compact); + cmd.setSeparator(":"); + cmd.parse("-rvo:out p1 p2".split(" ")); + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + } + + /** See {@link #testGnuLongOptionsWithVariousSeparators()} */ + @Test + public void testDefaultSeparatorIsEquals() { + assertEquals("=", new CommandLine(new CompactFields()).getSeparator()); + } + + @Test + public void testOptionsAfterParamAreInterpretedAsParameters() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-r -v p1 -o out p2".split(" ")); + verifyCompact(compact, true, true, null, fileArray("p1", "-o", "out", "p2")); + } + @Test + public void testShortOptionsWithSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-ro= -v".split(" ")); + verifyCompact(compact, true, true, "", null); + } + @Test + public void testShortOptionsWithColonSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { + CompactFields compact = new CompactFields(); + CommandLine cmd = new CommandLine(compact); + cmd.setSeparator(":"); + cmd.parse("-ro: -v".split(" ")); + verifyCompact(compact, true, true, "", null); + } + @Test + public void testShortOptionsWithSeparatorButNoValueAssignsEmptyStringIfLast() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=".split(" ")); + verifyCompact(compact, true, true, "", null); + } + + + @Test + public void testDoubleDashSeparatesPositionalParameters() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-oout -- -r -v p1 p2".split(" ")); + verifyCompact(compact, false, false, "out", fileArray("-r", "-v", "p1", "p2")); + } + + private File[] fileArray(final String ... paths) { + File[] result = new File[paths.length]; + for (int i = 0; i < result.length; i++) { + result[i] = new File(paths[i]); + } + return result; + } + + private void verifyCompact(CompactFields compact, boolean verbose, boolean recursive, String out, File[] params) { + assertEquals("-v", verbose, compact.verbose); + assertEquals("-r", recursive, compact.recursive); + assertEquals("-o", out == null ? null : new File(out), compact.outputFile); + if (params == null) { + assertNull("args", compact.inputFiles); + } else { + assertArrayEquals("args=" + Arrays.toString(compact.inputFiles), params, compact.inputFiles); + } + } + + @Test + public void testNonSpacedOptions() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo arg path path".split(" ")); + assertTrue("-r", compact.recursive); + assertTrue("-v", compact.verbose); + assertEquals("-o", new File("arg"), compact.outputFile); + assertArrayEquals("args", new File[]{new File("path"), new File("path")}, compact.inputFiles); + } + + @Test + public void testPrimitiveParameters() { + class PrimitiveIntParameters { + @Parameters int[] intParams; + } + PrimitiveIntParameters params = CommandLine.populateCommand(new PrimitiveIntParameters(), "1 2 3 4".split(" ")); + assertArrayEquals(new int[] {1, 2, 3, 4}, params.intParams); + } + + @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 testArityForOption_booleanFieldImplicitArity0() throws Exception { + Range arity = Range.optionArity(SupportedTypes.class.getDeclaredField("booleanField")); + assertEquals(Range.valueOf("0"), arity); + assertEquals("0", arity.toString()); + } + @Test + public void testArityForOption_intFieldImplicitArity1() throws Exception { + Range arity = Range.optionArity(SupportedTypes.class.getDeclaredField("intField")); + assertEquals(Range.valueOf("1"), arity); + assertEquals("1", arity.toString()); + } + @Test + public void testArityForOption_isExplicitlyDeclaredValue() throws Exception { + Range arity = Range.optionArity(EnumParams.class.getDeclaredField("timeUnitList")); + assertEquals(Range.valueOf("3"), arity); + assertEquals("3", arity.toString()); + } + @Test + public void testArityForOption_listFieldImplicitArity0_n() throws Exception { + class ImplicitList { @Option(names = "-a") List<Integer> listIntegers; } + Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("listIntegers")); + assertEquals(Range.valueOf("0..*"), arity); + assertEquals("0..*", arity.toString()); + } + @Test + public void testArityForOption_arrayFieldImplicitArity0_n() throws Exception { + class ImplicitList { @Option(names = "-a") int[] intArray; } + Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("intArray")); + assertEquals(Range.valueOf("0..*"), arity); + assertEquals("0..*", arity.toString()); + } + @Test + public void testArityForParameters_booleanFieldImplicitArity0() throws Exception { + class ImplicitBoolField { @Parameters boolean boolSingleValue; } + Range arity = Range.parameterArity(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); + assertEquals(Range.valueOf("0"), arity); + assertEquals("0", 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_n() throws Exception { + Range arity = Range.parameterArity(ListPositionalParams.class.getDeclaredField("list")); + assertEquals(Range.valueOf("0..*"), arity); + assertEquals("0..*", arity.toString()); + } + @Test + public void testArityForParameters_arrayFieldImplicitArity0_n() throws Exception { + Range arity = Range.parameterArity(CompactFields.class.getDeclaredField("inputFiles")); + assertEquals(Range.valueOf("0..*"), arity); + assertEquals("0..*", 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 = 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", "-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 = CommandLine.populateCommand(new ArrayOptionArity2_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", "-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); + } + + private 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_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); + assertFalse(params.vOrOther); + assertArrayEquals(new String[]{ "123", "-other"}, 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 (ParameterException ex) { + assertEquals("'123' is not a boolean for option '-bool'", 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("Unmatched arguments [-234, -bool]", ok.getMessage()); + } + } + @Test + public void testBooleanOptionsArity0_nShortFormFailsIfAttachedParamNotABooleanWithUnmatchedArgsAllowed() { // ignores varargs + CommandLine cmd = new CommandLine(new BooleanOptionsArity0_nAndParameters()).setUnmatchedArgumentsAllowed(true); + cmd.parse("-rv234 -bool".split(" ")); + assertEquals(Arrays.asList("-234", "-bool"), 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 (ParameterException ex) { + assertEquals("'234' is not a boolean for option '-v'", 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 (ParameterException expected) { + assertEquals("'abc' is not a boolean for option '-bool'", 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 (ParameterException expected) { + assertEquals("Missing required parameter for option '-bool' at index 0 (aBoolean)", 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 + 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); + } + + 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); + } + @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); + } + + @Test + public void testArrayOptionWithoutArityConsumesAllArguments() { + 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', 'b', 'c', 'd'}, params.charOptions); + assertArrayEquals(Arrays.toString(params.charParams), null, params.charParams); + } + + @Test(expected = MissingTypeConverterException.class) + public void testMissingTypeConverter() { + class MissingConverter { + @Option(names = "--socket") Socket socket; + } + CommandLine.populateCommand(new MissingConverter(), "--socket anyString".split(" ")); + } + + @Test + public void testArrayParametersWithArityMinusOneToN() { + class ArrayParamsNegativeArity { + @Parameters(arity = "-1..*") + List<String> params; + } + ArrayParamsNegativeArity params = CommandLine.populateCommand(new ArrayParamsNegativeArity(), "a", "b", "c"); + assertEquals(Arrays.asList("a", "b", "c"), params.params); + + params = CommandLine.populateCommand(new ArrayParamsNegativeArity(), "a"); + assertEquals(Arrays.asList("a"), params.params); + + params = CommandLine.populateCommand(new ArrayParamsNegativeArity()); + assertEquals(null, params.params); + } + + @Test + public void testArrayParametersArity0_n() { + class ArrayParamsArity0_n { + @Parameters(arity = "0..*") + List<String> 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<String> 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 parameters at positions 0..*: params", ex.getMessage()); + } + } + + @Test + public void testArrayParametersArity2_n() { + class ArrayParamsArity2_n { + @Parameters(arity = "2..*") + List<String> 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..* (params) requires at least 2 values, but only 1 were specified.", 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..* (params) requires at least 2 values, but only 0 were specified.", ex.getMessage()); + } + } + + @Test + public void testNonVarargArrayParametersWithNegativeArityConsumesZeroArguments() { + class NonVarArgArrayParamsNegativeArity { + @Parameters(arity = "-1") + List<String> params; + } + NonVarArgArrayParamsNegativeArity params = CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity(), "a", "b", "c"); + assertEquals(Arrays.asList(), params.params); + + params = CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity(), "a"); + assertEquals(Arrays.asList(), params.params); + + params = CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity()); + assertEquals(null, params.params); + } + + @Test + public void testNonVarargArrayParametersWithArity0() { + class NonVarArgArrayParamsZeroArity { + @Parameters(arity = "0") + List<String> params; + } + NonVarArgArrayParamsZeroArity params = CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a", "b", "c"); + assertEquals(new ArrayList<String>(), params.params); + + params = CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a"); + assertEquals(new ArrayList<String>(), params.params); + + params = CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity()); + assertEquals(null, params.params); + } + + @Test + public void testNonVarargArrayParametersWithArity1() { + class NonVarArgArrayParamsArity1 { + @Parameters(arity = "1") + List<String> params; + } + NonVarArgArrayParamsArity1 params = CommandLine.populateCommand(new NonVarArgArrayParamsArity1(), "a", "b", "c"); + assertEquals(Arrays.asList("a"), params.params); + + 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: params", ex.getMessage()); + } + } + + @Test + public void testNonVarargArrayParametersWithArity2() { + class NonVarArgArrayParamsArity2 { + @Parameters(arity = "2") + List<String> params; + } + NonVarArgArrayParamsArity2 params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a", "b", "c"); + assertEquals(Arrays.asList("a", "b"), params.params); + + try { + params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a"); + fail("Should not accept input with missing parameter"); + } catch (MissingParameterException ex) { + assertEquals("positional parameter at index 0..* (params) requires at least 2 values, but only 1 were specified.", 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..* (params) requires at least 2 values, but only 0 were specified.", ex.getMessage()); + } + } + + @Test + public void testParametersDeclaredOutOfOrderWithNoArgs() { + class WithParams { + @Parameters(index = "1") String param1; + @Parameters(index = "0") String param0; + } + try { + CommandLine.populateCommand(new WithParams(), new String[0]); + } catch (MissingParameterException ex) { + assertEquals("Missing required parameters: param0, param1", ex.getMessage()); + } + } + + class VariousPrefixCharacters { + @Option(names = {"-d", "--dash"}) int dash; + @Option(names = {"/S"}) int slashS; + @Option(names = {"/T"}) int slashT; + @Option(names = {"/4"}) boolean fourDigit; + @Option(names = {"/Owner", "--owner"}) String owner; + @Option(names = {"-SingleDash"}) boolean singleDash; + @Option(names = {"[CPM"}) String cpm; + @Option(names = {"(CMS"}) String cms; + } + @Test + public void testOptionsMayDefineAnyPrefixChar() { + VariousPrefixCharacters params = CommandLine.populateCommand(new VariousPrefixCharacters(), + "-d 123 /4 /S 765 /T=98 /Owner=xyz -SingleDash [CPM CP/M (CMS=cmsVal".split(" ")); + assertEquals("-d", 123, params.dash); + assertEquals("/S", 765, params.slashS); + assertEquals("/T", 98, params.slashT); + assertTrue("/4", params.fourDigit); + assertTrue("-SingleDash", params.singleDash); + assertEquals("/Owner", "xyz", params.owner); + assertEquals("[CPM", "CP/M", params.cpm); + assertEquals("(CMS", "cmsVal", params.cms); + } + @Test + public void testGnuLongOptionsWithVariousSeparators() { + VariousPrefixCharacters params = CommandLine.populateCommand(new VariousPrefixCharacters(), "--dash 123".split(" ")); + assertEquals("--dash val", 123, params.dash); + + params = CommandLine.populateCommand(new VariousPrefixCharacters(), "--dash=234 --owner=x".split(" ")); + assertEquals("--dash=val", 234, params.dash); + assertEquals("--owner=x", "x", params.owner); + + params = new VariousPrefixCharacters(); + CommandLine cmd = new CommandLine(params); + cmd.setSeparator(":"); + cmd.parse("--dash:345"); + assertEquals("--dash:val", 345, params.dash); + + params = new VariousPrefixCharacters(); + cmd = new CommandLine(params); + cmd.setSeparator(":"); + cmd.parse("--dash:345 --owner:y".split(" ")); + assertEquals("--dash:val", 345, params.dash); + assertEquals("--owner:y", "y", params.owner); + } + @Test + public void testSeparatorCanBeSetDeclaratively() { + @Command(separator = ":") + class App { + @Option(names = "--opt", required = true) String opt; + } + try { + CommandLine.populateCommand(new App(), "--opt=abc"); + fail("Expected failure with unknown separator"); + } catch (UnmatchedArgumentException ok) { + assertEquals("Unmatched argument [--opt=abc]", ok.getMessage()); + } + } + @Test + public void testIfSeparatorSetTheDefaultSeparatorIsNotRecognized() { + @Command(separator =
<TRUNCATED>
