package picocli; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ProvideSystemProperty; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TestRule; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.PositionalParamSpec; import picocli.CommandLine.Parameters; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.junit.Assert.*; public class RangeTest { // 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 testRelativeRangeWithoutAnchor() { class App { @Parameters(index = "+", descriptionKey = "a") String a; @Parameters(index = "+", descriptionKey = "b") String b; @Parameters(index = "+", descriptionKey = "c") String c; } CommandLine cmd = new CommandLine(new App()); List positionals = cmd.getCommandSpec().positionalParameters(); assertEquals("a", positionals.get(0).descriptionKey()); assertEquals(0, positionals.get(0).index().min()); assertEquals(0, positionals.get(0).index().max()); assertEquals("b", positionals.get(1).descriptionKey()); assertEquals(1, positionals.get(1).index().min()); assertEquals(1, positionals.get(1).index().max()); assertEquals("c", positionals.get(2).descriptionKey()); assertEquals(2, positionals.get(2).index().min()); assertEquals(2, positionals.get(2).index().max()); } @Test public void testRelativeRangeWithAnchor() { class App { @Parameters(index = "1+", descriptionKey = "a") String a; @Parameters(index = "4+", descriptionKey = "b") String b; @Parameters(index = "7+", descriptionKey = "c") String c; } CommandLine cmd = new CommandLine(new App()); List positionals = cmd.getCommandSpec().positionalParameters(); assertEquals("a", positionals.get(0).descriptionKey()); assertEquals(1, positionals.get(0).index().min()); assertEquals(1, positionals.get(0).index().max()); assertEquals("b", positionals.get(1).descriptionKey()); assertEquals(4, positionals.get(1).index().min()); assertEquals(4, positionals.get(1).index().max()); assertEquals("c", positionals.get(2).descriptionKey()); assertEquals(7, positionals.get(2).index().min()); assertEquals(7, positionals.get(2).index().max()); } // @junitparams.Parameters({ // }) @Test public void testRelativeRangeWithAnchorMixedWithAbsoluteIndex() { class App { @Parameters(index = "0+", descriptionKey = "a1") String a1; @Parameters(index = "1", descriptionKey = "a0") String a0; @Parameters(index = "1+", descriptionKey = "a2") String a2; @Parameters(index = "2", descriptionKey = "b0") String b0; @Parameters(index = "4+", descriptionKey = "c") String c; @Parameters(index = "+", descriptionKey = "d") String e; @Parameters(index = "7+", descriptionKey = "e") String f; } Object[][] expect = { {"a1", "0+ (0)", 0, 0}, {"a0", "1", 1, 1}, {"a2", "1+ (2)", 2, 2}, {"b0", "2", 2, 2}, {"c", "4+ (4)", 4, 4}, {"e", "7+ (7)", 7, 7}, {"d", "+ (8)", 8, 8}, }; CommandLine cmd = new CommandLine(new App()); List positionals = cmd.getCommandSpec().positionalParameters(); for (int i = 0; i < expect.length; i++) { PositionalParamSpec positional = positionals.get(i); CommandLine.Range index = positional.index(); assertEquals(i + ": " + index.originalValue(), expect[i][0], positional.descriptionKey()); assertEquals(i + ": " + index.originalValue(), expect[i][1], positional.index().internalToString()); assertEquals(i + ": " + index.originalValue(), expect[i][2], index.min()); assertEquals(i + ": " + index.originalValue(), expect[i][3], index.max()); } } @Test public void testGapExceptionIfAnyAbsoluteIndex() { class App { @Parameters(index = "1+", descriptionKey = "a1") String a1; @Parameters(index = "1", descriptionKey = "a0") String a0; @Parameters(index = "1+", descriptionKey = "a2") String a2; @Parameters(index = "2", descriptionKey = "b0") String b0; @Parameters(index = "4+", descriptionKey = "c") String c; @Parameters(index = "+", descriptionKey = "d") String e; @Parameters(index = "7+", descriptionKey = "e") String f; } try { new CommandLine(new App()); } catch (CommandLine.ParameterIndexGapException ex) { assertEquals("Command definition should have a positional parameter with index=0. " + "Nearest positional parameter '' has index=1. " + "(Full list: [1, 1+ (2), 1+ (3), 2, 4+ (4), 7+ (7), + (8)])", ex.getMessage()); } } //@Ignore // TODO @Test public void testRelativeRangeWithAnchorOverlappingWithAbsoluteIndex() { class App { @Parameters(index = "1+", descriptionKey = "1+(1)") String a; @Parameters(index = "1", descriptionKey = "1") String b; @Parameters(index = "0", descriptionKey = "0") String c; @Parameters(index = "2", descriptionKey = "2") String d; @Parameters(index = "1+", descriptionKey = "1+(2)") String a2; @Parameters(index = "0+", descriptionKey = "0+(1)") String e; @Parameters(index = "+", descriptionKey = "+(1)") String f; @Parameters(index = "7+", descriptionKey = "7+") String g; @Parameters(index = "+", descriptionKey = "+(2)") String h; @Parameters(index = "0+", descriptionKey = "0+(2)") String i; } Object[][] expect = { {"0", "0", 0, 0}, {"0+(1)", "0+ (1)", 1, 1}, {"0+(2)", "0+ (2)", 2, 2}, {"1", "1", 1, 1}, {"1+(1)", "1+ (2)", 2, 2}, {"1+(2)", "1+ (3)", 3, 3}, {"2", "2", 2, 2}, {"7+", "7+ (7)", 7, 7}, {"+(1)", "+ (8)", 8, 8}, {"+(2)", "+ (9)", 9, 9}, }; CommandLine cmd = new CommandLine(new App()); List positionals = cmd.getCommandSpec().positionalParameters(); for (int i = 0; i < expect.length; i++) { PositionalParamSpec positional = positionals.get(i); CommandLine.Range index = positional.index(); assertEquals(i + ": " + index.originalValue(), expect[i][0], positional.descriptionKey()); assertEquals(i + ": " + index.originalValue(), expect[i][1], index.internalToString()); assertEquals(i + ": " + positional.descriptionKey(), expect[i][2], index.min()); assertEquals(i + ": " + positional.descriptionKey(), expect[i][3], index.max()); } } @Test public void testPositionalParametersSorter() { String[] indices = new String[] { "1+", "1", "0", "2", "1+", "0+", "+", "7+", "+", "0+",}; int i = 0; List positionals = new ArrayList(); for (String index : indices) { positionals.add(PositionalParamSpec.builder().index(index).descriptionKey(String.valueOf(i++)).build()); } Collections.sort(positionals, new CommandLine.PositionalParametersSorter()); List after = new ArrayList(); for (PositionalParamSpec positional : positionals) { after.add(positional.index().internalToString() + " @" + positional.descriptionKey()); } List expected = Arrays.asList( "0 @2", "0+ (0) @5", "0+ (0) @9", "1 @1", "1+ (1) @0", "1+ (1) @4", "2 @3", "7+ (7) @7", "+ (+) @6", "+ (+) @8" ); assertEquals(expected, after); // now verify how the sort order is adjusted when the positionals are added to a CommandSpec CommandSpec spec = CommandSpec.create(); for (PositionalParamSpec positional : positionals) { spec.addPositional(positional); } after.clear(); for (PositionalParamSpec positional : spec.positionalParameters()) { after.add(positional.index().internalToString() + " @" + positional.descriptionKey()); } expected = Arrays.asList( "0 @2", "0+ (1) @5", // adjusted 0 -> 1 "0+ (2) @9", // adjusted 0 -> 2 "1 @1", "1+ (2) @0", // adjusted 1 -> 2 "1+ (3) @4", // adjusted 1 -> 3 "2 @3", "7+ (7) @7", "+ (8) @6", // adjusted 0..* -> 8 "+ (9) @8" // adjusted 0..* -> 9 ); assertEquals(expected, after); } @Test public void testPositionalParametersSorter2() { String[] indices = new String[] { "+", "1", "+"}; int i = 0; List positionals = new ArrayList(); for (String index : indices) { positionals.add(PositionalParamSpec.builder().index(index).descriptionKey(String.valueOf(i++)).build()); } Collections.sort(positionals, new CommandLine.PositionalParametersSorter()); List after = new ArrayList(); for (PositionalParamSpec positional : positionals) { after.add(positional.index().internalToString() + " @" + positional.descriptionKey()); } // indices are adjusted when positionals are added to a CommandSpec. List expected = Arrays.asList( "1 @1", "+ (+) @0", "+ (+) @2" ); assertEquals(expected, after); // now verify how the sort order is adjusted when the positionals are added to a CommandSpec CommandSpec spec = CommandSpec.create(); for (PositionalParamSpec positional : positionals) { spec.addPositional(positional); } after.clear(); for (PositionalParamSpec positional : spec.positionalParameters()) { after.add(positional.index().internalToString() + " @" + positional.descriptionKey()); } expected = Arrays.asList( "1 @1", "+ (2) @0", // adjusted 0..* -> 2 "+ (3) @2" // adjusted 0..* -> 3 ); assertEquals(expected, after); } static class CommonMixinOne { @Parameters(index = "0+", paramLabel = "COMMON-PARAM-ONE") private String commonMixinOneParam; } static class CommonMixinTwo { @Parameters(index = "2+", paramLabel = "COMMON-PARAM-TWO") private String commonMixinTwoParam; } @Test // test for https://github.com/remkop/picocli/issues/564 public void testMixinsWithVariableIndex() { @Command(name = "testCommand", description = "Example for issue 564") class TestCommand { @Mixin private CommonMixinOne myCommonMixinOne; @Parameters(index = "1", paramLabel = "TEST-COMMAND-PARAM") private String testCommandParam; @Mixin private CommonMixinTwo myCommonMixinTwo; } CommandLine cmd = new CommandLine(new TestCommand()); String expected = String.format("" + "Usage: testCommand COMMON-PARAM-ONE TEST-COMMAND-PARAM COMMON-PARAM-TWO%n" + "Example for issue 564%n" + " COMMON-PARAM-ONE%n" + " TEST-COMMAND-PARAM%n" + " COMMON-PARAM-TWO%n"); String actual = cmd.getUsageMessage(); assertEquals(expected, actual); } static class MixinOne { @Parameters(paramLabel = "COMMON-PARAM-ONE") private String commonMixinOneParam; } static class MixinTwo { @Parameters(index = "+", paramLabel = "COMMON-PARAM-TWO") private String commonMixinTwoParam; } @Test public void testDefaultRelativeIndex() { @Command(name = "testCommand", description = "Example for issue 564") class TestCommand { @Mixin private MixinOne myCommonMixinOne; @Parameters(index = "1", paramLabel = "TEST-COMMAND-PARAM") private String testCommandParam; @Mixin private MixinTwo myCommonMixinTwo; } CommandLine cmd = new CommandLine(new TestCommand()); List positionals = cmd.getCommandSpec().positionalParameters(); Object[][] expected = new Object[][] { {"0", "0+ (0)", 0, 0, true }, {"1", "1", 1, 1, false}, {"2", "+ (2)", 2, 2, true }, }; for (int i = 0; i < positionals.size(); i++) { CommandLine.Range index = positionals.get(i).index(); String desc = "pos@" + i + " (" + index.internalToString() + ")"; assertEquals(desc, expected[i][0], index.toString()); assertEquals(desc, expected[i][1], index.internalToString()); assertEquals(desc, expected[i][2], index.min()); assertEquals(desc, expected[i][3], index.max()); assertEquals(desc, expected[i][4], index.isRelative()); } String expectedUsage = String.format("" + "Usage: testCommand COMMON-PARAM-ONE TEST-COMMAND-PARAM COMMON-PARAM-TWO%n" + "Example for issue 564%n" + " COMMON-PARAM-ONE%n" + " TEST-COMMAND-PARAM%n" + " COMMON-PARAM-TWO%n"); String actual = cmd.getUsageMessage(); assertEquals(expectedUsage, actual); } @Test public void testDefaultRange() { class R1 { @Parameters int a; } PositionalParamSpec positional = new CommandLine(new R1()).getCommandSpec().positionalParameters().get(0); CommandLine.Range range = positional.index(); assertEquals(0, range.min()); assertEquals(0, range.max()); assertEquals(false, range.isUnspecified()); assertEquals(true, range.isRelative()); assertEquals(false, range.isVariable()); assertEquals(false, range.isUnresolved()); assertEquals("0", range.toString()); assertEquals("0+ (0)", range.internalToString()); } @Test public void testUserManualAutomaticIndexExample() { class AutomaticIndex { @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message List allParameters; // no "index" attribute: captures _all_ arguments (as Strings) @Parameters String group; // assigned index = "0" @Parameters String artifact; // assigned index = "1" @Parameters String version; // assigned index = "2" } //TestUtil.setTraceLevel("DEBUG"); String[] args = { "info.picocli", "picocli", "4.3.0" }; AutomaticIndex params = CommandLine.populateCommand(new AutomaticIndex(), args); assertEquals("info.picocli", params.group); assertEquals("picocli", params.artifact); assertEquals("4.3.0", params.version); assertEquals(Arrays.asList("info.picocli", "picocli", "4.3.0"), params.allParameters); } @Ignore("Methods ordering is not reliable") @Test public void testUserManualAutomaticIndexWithMethods() { class WithMethod { @Parameters void group(String s) {} // assigned index = "0" @Parameters void artifact(String s) {} // assigned index = "1" @Parameters void version(String s) {} // assigned index = "2" } CommandSpec spec = CommandSpec.forAnnotatedObject(new WithMethod()); for (int i = 0; i < spec.positionalParameters().size(); i++) { PositionalParamSpec param = spec.positionalParameters().get(i); CommandLine.Range index = param.index(); switch (i) { case 0: assertEquals("", param.paramLabel()); assertEquals(0, index.min()); assertEquals(0, index.max()); assertEquals("0+ (0)", index.internalToString()); break; case 1: assertEquals("", param.paramLabel()); assertEquals(1, index.min()); assertEquals(1, index.max()); assertEquals("0+ (1)", index.internalToString()); break; case 2: assertEquals("", param.paramLabel()); assertEquals(2, index.min()); assertEquals(2, index.max()); assertEquals("0+ (2)", index.internalToString()); break; } } } @Test public void testUserManualAnchoredIndexExample() { class ExplicitAndAutomaticIndexes { @Parameters(index = "0") String p0; @Parameters(index = "1") String p1; @Parameters String pAuto0; @Parameters String pAuto1; @Parameters(index = "1+") String pAnchored0; @Parameters(index = "1+") String pAnchored1; } CommandSpec spec = CommandSpec.forAnnotatedObject(new ExplicitAndAutomaticIndexes()); for (int i = 0; i < spec.positionalParameters().size(); i++) { PositionalParamSpec param = spec.positionalParameters().get(i); CommandLine.Range index = param.index(); switch (i) { case 0: assertEquals("", param.paramLabel()); assertEquals(0, index.min()); assertEquals(0, index.max()); assertEquals("0", index.internalToString()); break; case 1: assertEquals("", param.paramLabel()); assertEquals(1, index.min()); assertEquals(1, index.max()); assertEquals("0+ (1)", index.internalToString()); break; case 2: assertEquals("", param.paramLabel()); assertEquals(2, index.min()); assertEquals(2, index.max()); assertEquals("0+ (2)", index.internalToString()); break; case 3: assertEquals("", param.paramLabel()); assertEquals(1, index.min()); assertEquals(1, index.max()); assertEquals("1", index.internalToString()); break; case 4: assertEquals("", param.paramLabel()); assertEquals(2, index.min()); assertEquals(2, index.max()); assertEquals("1+ (2)", index.internalToString()); break; case 5: assertEquals("", param.paramLabel()); assertEquals(3, index.min()); assertEquals(3, index.max()); assertEquals("1+ (3)", index.internalToString()); break; } } } @Test public void testUserManualUnanchoredExample() { class Unanchored { @Parameters(index = "0") String p0; @Parameters(index = "1+") String p1; // assigned index = "1" @Parameters(index = "1+") String p2; // assigned index = "2" @Parameters(index = "3") String p3; @Parameters(index = "+") String p4; // assigned index = "4" <-- unanchored @Parameters(index = "+") String p5; // assigned index = "5" <-- unanchored } CommandSpec spec = CommandSpec.forAnnotatedObject(new Unanchored()); for (int i = 0; i < spec.positionalParameters().size(); i++) { PositionalParamSpec param = spec.positionalParameters().get(i); CommandLine.Range index = param.index(); switch (i) { case 0: assertEquals("", param.paramLabel()); assertEquals(0, index.min()); assertEquals(0, index.max()); assertEquals("0", index.internalToString()); break; case 1: assertEquals("", param.paramLabel()); assertEquals(1, index.min()); assertEquals(1, index.max()); assertEquals("1+ (1)", index.internalToString()); break; case 2: assertEquals("", param.paramLabel()); assertEquals(2, index.min()); assertEquals(2, index.max()); assertEquals("1+ (2)", index.internalToString()); break; case 3: assertEquals("", param.paramLabel()); assertEquals(3, index.min()); assertEquals(3, index.max()); assertEquals("3", index.internalToString()); break; case 4: assertEquals("", param.paramLabel()); assertEquals(4, index.min()); assertEquals(4, index.max()); assertEquals("+ (4)", index.internalToString()); break; case 5: assertEquals("", param.paramLabel()); assertEquals(5, index.min()); assertEquals(5, index.max()); assertEquals("+ (5)", index.internalToString()); break; } } } @Test public void testUnanchoredWithVariable_AssignedIndex() { class UnanchoredWithVariable { @Parameters(index = "0..*") List all; @Parameters(index = "+") String p1; @Parameters(index = "+") String p2; } CommandSpec spec = CommandSpec.forAnnotatedObject(new UnanchoredWithVariable()); for (int i = 0; i < spec.positionalParameters().size(); i++) { PositionalParamSpec param = spec.positionalParameters().get(i); CommandLine.Range index = param.index(); switch (i) { case 0: assertEquals("", param.paramLabel()); assertEquals(0, index.min()); assertEquals(Integer.MAX_VALUE, index.max()); assertEquals("0..*", index.internalToString()); break; case 1: assertEquals("", param.paramLabel()); assertEquals(Integer.MAX_VALUE, index.min()); assertEquals(Integer.MAX_VALUE, index.max()); assertEquals("+ (+)", index.internalToString()); break; case 2: assertEquals("", param.paramLabel()); assertEquals(Integer.MAX_VALUE, index.min()); assertEquals(Integer.MAX_VALUE, index.max()); assertEquals("+ (+)", index.internalToString()); break; } } } @Test public void testUnanchoredWithVariable_ParseResult() { class UnanchoredAfterOpenEndedIndex { @Parameters(index = "0..*") List all; @Parameters(index = "+") String last; } try { CommandLine.populateCommand(new UnanchoredAfterOpenEndedIndex(), "a", "b", "c"); fail("Expected exception"); } catch (CommandLine.MissingParameterException ex) { assertEquals("Missing required parameter: ''", ex.getMessage()); } } }