package org.dcache.util.cli; import dmg.util.command.CommandLine; import dmg.util.command.HelpFormat; import dmg.util.command.Option; import dmg.util.command.Argument; import dmg.util.command.Command; import org.junit.Before; import org.junit.Test; import java.io.File; import java.sql.Time; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import dmg.util.CommandSyntaxException; import org.dcache.util.Args; import static java.util.Arrays.asList; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class AnnotatedCommandScannerTest { private enum AnEnum { FOO, BAR } private AnnotatedCommandScanner _scanner; @Before public void setUp() throws Exception { _scanner = new AnnotatedCommandScanner(); } @Test public void shouldUseCommandAnnotationToIdentifyCallableCommands() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertThat(commands, hasKey(asList("test"))); } @Test public void shouldIgnoreNonCallableCommands() throws Exception { class SUT { @Command(name="test") class TestCommand { } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertTrue(commands.isEmpty()); } @Test public void shouldIgnoreUnannotatedCallables() throws Exception { class SUT { class TestCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertTrue(commands.isEmpty()); } @Test public void shouldUseAclAnnotationForSingleAcl() throws Exception { class SUT { @Command(name="test", acl="acl") class TestCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertThat(commands.get(asList("test")).hasACLs(), is(true)); assertThat(commands.get(asList("test")) .getACLs(), hasItemInArray("acl")); } @Test public void shouldUseAclAnnotationForMultibleAcls() throws Exception { class SUT { @Command(name="test", acl={"acl1", "acl2"}) class TestCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertThat(commands.get(asList("test")).hasACLs(), is(true)); assertThat(commands.get(asList("test")).getACLs(), hasItemInArray("acl1")); assertThat(commands.get(asList("test")) .getACLs(), hasItemInArray("acl2")); } @Test public void shouldUseHintAnnotationForHelpHint() throws Exception { class SUT { @Command(name="test", hint="hint") class TestCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertThat(commands.get(asList("test")).getHelpHint(HelpFormat.PLAIN), is("# hint")); } @Test public void shouldUseArgumentAnnotationForArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index = 0) int argument1; @Argument(index = 1) String argument2; @Override public String call() throws Exception { assertThat(argument1, is(1)); assertThat(argument2, is("2")); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("1 2")); } @Test public void shouldUseNegativeArgumentArgumentIndexToCountFromTheEnd() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index = -2) int argument; @Override public String call() throws Exception { assertThat(argument, is(1)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("1 2")); } @Test public void shouldReturnResultOfCallable() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Override public String call() throws Exception { return "result"; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); assertThat(commands.get(asList("test")).execute(new Args("")), is((Object) "result")); } @Test(expected=CommandSyntaxException.class) public void shouldRejectTooFewArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument int required; @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("")); } @Test(expected=CommandSyntaxException.class) public void shouldRejectTooManyArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument int required; @Override public String call() throws Exception { return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("1 2")); } @Test public void shouldAcceptOptionalArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index=-2, required=false) Integer optional; @Argument(index=-1) Integer required; @Override public String call() throws Exception { assertThat(optional, is(1)); assertThat(required, is(2)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("1 2")); } @Test public void shouldNotRequireOptionalArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index=-2, required=false) Integer optional; @Argument(index=-1) Integer required; @Override public String call() throws Exception { assertThat(optional, is((Integer) null)); assertThat(required, is(2)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("2")); } @Test public void shouldRespectDefaultValues() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index=1, required=false) int optional = 10; @Override public String call() throws Exception { assertThat(optional, is(10)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("")); } @Test public void shouldAcceptCommonTypes() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Argument(index=0) Integer arg0; @Argument(index=1) int arg1; @Argument(index=2) Short arg2; @Argument(index=3) short arg3; @Argument(index=4) Long arg4; @Argument(index=5) long arg5; @Argument(index=6) Byte arg6; @Argument(index=7) byte arg7; @Argument(index=8) Float arg8; @Argument(index=9) float arg9; @Argument(index=10) Double arg10; @Argument(index=11) double arg11; @Argument(index=12) String arg12; @Argument(index=13) Character arg13; @Argument(index=14) char arg14; @Argument(index=15) AnEnum arg15; @Argument(index=16) File arg16; @Argument(index=17) Time arg17; @Argument(index=18) long[] arg18; @Override public String call() throws Exception { assertThat(arg0, is(0)); assertThat(arg1, is(1)); assertThat(arg2, is((short) 2)); assertThat(arg3, is((short) 3)); assertThat(arg4, is(4L)); assertThat(arg5, is(5L)); assertThat(arg6, is((byte) 6)); assertThat(arg7, is((byte) 7)); assertThat(arg8, is((float) 8.0)); assertThat(arg9, is((float) 9.0)); assertThat(arg10, is(10.0)); assertThat(arg11, is(11.0)); assertThat(arg12, is("12")); assertThat(arg13, is('a')); assertThat(arg14, is('b')); assertThat(arg15, is(AnEnum.BAR)); assertThat(arg16, is(new File("/my/file"))); assertThat(arg17, is(Time.valueOf("12:34:56"))); assertArrayEquals(arg18, new long[] { 100, 101 }); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("0 1 2 3 4 5 6 7 8.0 9 10 11.00 12 a b BAR /my/file 12:34:56 100 101")); } @Test public void shouldOptionAnnotationForOptions() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Option(name="foo") boolean bar; @Override public String call() throws Exception { assertThat(bar, is(true)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("-foo")); } @Test public void shouldAllowOptionArguments() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Option(name="foo") int bar; @Override public String call() throws Exception { assertThat(bar, is(2)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("-foo=2")); } @Test(expected=CommandSyntaxException.class) public void shouldEnforceRequiredOptions() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Option(name="foo", required=true) int bar; @Override public String call() throws Exception { assertThat(bar, is(2)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("")); } @Test public void shouldAllowOptionalOptions() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { @Option(name="foo") int bar; @Override public String call() throws Exception { assertThat(bar, is(0)); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("test")).execute(new Args("")); } @Test(expected = RuntimeException.class) public void shouldFailOnMissingConstructor() throws Exception { class SUT { @Command(name="test") class TestCommand implements Callable<String> { public TestCommand(int invalid) { } @Override public String call() throws Exception { return null; } } } _scanner.scan(new SUT()); } @Test(expected = RuntimeException.class) public void shouldFailOnNonSerializableReturnValue() throws Exception { class ReturnType { } class SUT { @Command(name = "test") class TestCommand implements Callable<ReturnType> { @Override public ReturnType call() throws Exception { return null; } } } _scanner.scan(new SUT()); } @Test public void shouldAcceptInheritedCommands() throws Exception { class SUT { class AbstractCommand implements Callable<String> { @Override public String call() throws Exception { return null; } } @Command(name = "test") class TestCommand extends AbstractCommand { } } _scanner.scan(new SUT()); } @Test public void shouldUseCommandOfSubClassWhenInjectingCommandLine() throws Exception { class SUT { @Command(name = "base") class BaseCommand implements Callable<String> { @CommandLine public String line; @Override public String call() throws Exception { assertThat(line, is("base")); return null; } } @Command(name = "test") class TestCommand extends BaseCommand { @Override public String call() throws Exception { assertThat(line, is("test")); return null; } } } Map<List<String>,? extends CommandExecutor> commands = _scanner.scan(new SUT()); commands.get(asList("base")).execute(new Args("")); commands.get(asList("test")).execute(new Args("")); } }