/* Copyright 2017 Remko Popma Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package picocli; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.*; import org.junit.rules.TestRule; import picocli.CommandLine.Model.CommandSpec; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import static java.lang.String.format; import static org.junit.Assert.*; import static picocli.CommandLine.*; @SuppressWarnings("deprecation") public class ExecuteLegacyTest { // 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"); @Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog().muteForSuccessfulTests(); @Rule public final SystemOutRule systemOutRule = new SystemOutRule().enableLog().muteForSuccessfulTests(); @Rule public final ExpectedSystemExit exit = ExpectedSystemExit.none(); interface Factory { Object create(); } @Test public void testParseWithHandlerRunXxxFailsIfNotRunnableOrCallable() { @Command class App { @Parameters String[] params; } Factory factory = new Factory() { public Object create() {return new App();} }; String[] args = { "abc" }; verifyAllFail(factory, "Parsed command (picocli.ExecuteLegacyTest$", ") is not a Method, Runnable or Callable", args); } @Test public void testParseWithHandlerRunXxxWithSubcommandFailsWithMissingSubcommandIfNotRunnableOrCallable() { @Command class App { @Parameters String[] params; @Command void sub() {} } Factory factory = new Factory() { public Object create() {return new App();} }; String expected = String.format("" + "Missing required subcommand%n" + "Usage:
[...] [COMMAND]%n" + " [...]%n" + "Commands:%n" + " sub%n"); IParseResultHandler[] handlers = new IParseResultHandler[] { new RunFirst(), new RunLast(), new RunAll() }; for (IParseResultHandler handler : handlers) { String[] args = { "abc" }; this.systemErrRule.clearLog(); new CommandLine(factory.create()).parseWithHandler(handler, System.err, args); assertEquals(expected, systemErrRule.getLog()); } } @Test public void testParseWithHandlerRunXxxCatchesAndRethrowsExceptionFromRunnable() { @Command class App implements Runnable { @Parameters String[] params; public void run() { throw new IllegalStateException("TEST EXCEPTION"); } } Factory factory = new Factory() { public Object create() {return new App();} }; verifyAllFail(factory, "Error while running command (picocli.ExecuteLegacyTest$", "): java.lang.IllegalStateException: TEST EXCEPTION", new String[0]); } @Test public void testParseWithHandlerRunXxxCatchesAndRethrowsExceptionFromCallable() { @Command class App implements Callable { @Parameters String[] params; public Object call() { throw new IllegalStateException("TEST EXCEPTION2"); } } Factory factory = new Factory() { public Object create() {return new App();} }; verifyAllFail(factory, "Error while calling command (picocli.ExecuteLegacyTest$", "): java.lang.IllegalStateException: TEST EXCEPTION2", new String[0]); } private void verifyAllFail(Factory factory, String prefix, String suffix, String[] args) { verifyAllFail(factory, prefix, suffix, args, ExecutionException.class); } @SuppressWarnings("deprecation") private void verifyAllFail(Factory factory, String prefix, String suffix, String[] args, Class xClass) { IParseResultHandler[] handlers = new IParseResultHandler[] { new RunFirst(), new RunLast(), new RunAll() }; for (IParseResultHandler handler : handlers) { String descr = handler.getClass().getSimpleName(); try { new CommandLine(factory.create()).parseWithHandler(handler, System.out, args); fail(descr + ": expected exception"); } catch (Exception ex) { assertTrue("Exception class " + ex.getClass().getSimpleName(), xClass.isAssignableFrom(ex.getClass())); String actual = ex.getMessage(); assertTrue(descr + ": " + actual, actual.startsWith(prefix)); assertTrue(descr + ": " + actual, actual.endsWith(suffix)); } } } @Test public void testParseWithHandlerRunXxxReturnsEmptyListIfHelpRequested() { @Command(version = "abc 1.3.4") class App implements Callable { @Option(names = "-h", usageHelp = true) boolean requestHelp; @Option(names = "-V", versionHelp = true) boolean requestVersion; public Object call() { return "RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App());} }; verifyReturnValueForBuiltInHandlers(factory, Collections.emptyList(), new String[] {"-h"}); verifyReturnValueForBuiltInHandlers(factory, Collections.emptyList(), new String[] {"-V"}); } @Test public void testParseWithHandlerRunXxxReturnsCallableResult() { @Command class App implements Callable { public Object call() { return "RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App());} }; verifyReturnValueForBuiltInHandlers(factory, Arrays.asList("RETURN VALUE"), new String[0]); } interface CommandLineFactory { CommandLine create(); } @SuppressWarnings("deprecation") private void verifyReturnValueForBuiltInHandlers(CommandLineFactory factory, Object expected, String[] args) { IParseResultHandler[] handlers = new IParseResultHandler[] { new RunFirst(), new RunLast(), new RunAll() }; PrintStream out = new PrintStream(new ByteArrayOutputStream()); for (IParseResultHandler handler : handlers) { String descr = handler.getClass().getSimpleName(); Object actual = factory.create().parseWithHandler(handler, out, args); assertEquals(descr + ": return value", expected, actual); } } @Test public void testParseWithHandler2RunXxxReturnsNullIfHelpRequested() { @Command(version = "abc 1.3.4") class App implements Callable { @Option(names = "-h", usageHelp = true) boolean requestHelp; @Option(names = "-V", versionHelp = true) boolean requestVersion; public Object call() { return "RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App());} }; verifyReturnValueForBuiltInHandlers2(factory, null, new String[] {"-h"}); verifyReturnValueForBuiltInHandlers2(factory, null, new String[] {"-V"}); } @Test public void testParseWithHandle2rRunXxxReturnsCallableResult() { @Command class App implements Callable { public Object call() { return "RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App());} }; verifyReturnValueForBuiltInHandlers2(factory, Arrays.asList("RETURN VALUE"), new String[0]); } private void verifyReturnValueForBuiltInHandlers2(CommandLineFactory factory, Object expected, String[] args) { IParseResultHandler2[] handlers = new IParseResultHandler2[] { new RunFirst(), new RunLast(), new RunAll() }; PrintStream out = new PrintStream(new ByteArrayOutputStream()); for (IParseResultHandler2 handler : handlers) { String descr = handler.getClass().getSimpleName(); Object actual = factory.create().parseWithHandler(handler, args); assertEquals(descr + ": return value", expected, actual); } } @Test public void testParseWithHandlerRunXxxReturnsCallableResultWithSubcommand() { @Command class App implements Callable { public Object call() { return "RETURN VALUE"; } } @Command(name = "sub") class Sub implements Callable { public Object call() { return "SUB RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App()).addSubcommand("sub", new Sub());} }; Object actual1 = factory.create().parseWithHandler(new RunFirst(), new String[] {"sub"}); assertEquals("RunFirst: return value", Arrays.asList("RETURN VALUE"), actual1); Object actual2 = factory.create().parseWithHandler(new RunLast(), new String[] {"sub"}); assertEquals("RunLast: return value", Arrays.asList("SUB RETURN VALUE"), actual2); Object actual3 = factory.create().parseWithHandler(new RunAll(), new String[] {"sub"}); assertEquals("RunAll: return value", Arrays.asList("RETURN VALUE", "SUB RETURN VALUE"), actual3); } @Test public void testParseWithHandler2RunXxxReturnsCallableResultWithSubcommand() { @Command class App implements Callable { public Object call() { return "RETURN VALUE"; } } @Command(name = "sub") class Sub implements Callable { public Object call() { return "SUB RETURN VALUE"; } } CommandLineFactory factory = new CommandLineFactory() { public CommandLine create() {return new CommandLine(new App()).addSubcommand("sub", new Sub());} }; PrintStream out = new PrintStream(new ByteArrayOutputStream()); Object actual1 = factory.create().parseWithHandler(new RunFirst(), new String[]{"sub"}); assertEquals("RunFirst: return value", Arrays.asList("RETURN VALUE"), actual1); Object actual2 = factory.create().parseWithHandler(new RunLast(), new String[]{"sub"}); assertEquals("RunLast: return value", Arrays.asList("SUB RETURN VALUE"), actual2); Object actual3 = factory.create().parseWithHandler(new RunAll(), new String[]{"sub"}); assertEquals("RunAll: return value", Arrays.asList("RETURN VALUE", "SUB RETURN VALUE"), actual3); } @Test public void testRunCallsRunnableIfParseSucceeds() { final boolean[] runWasCalled = {false}; @Command class App implements Runnable { public void run() { runWasCalled[0] = true; } } CommandLine.run(new App(), System.err); assertTrue(runWasCalled[0]); } @Test public void testRunPrintsErrorIfParseFails() throws UnsupportedEncodingException { final boolean[] runWasCalled = {false}; class App implements Runnable { @Option(names = "-number") int number; public void run() { runWasCalled[0] = true; } } PrintStream oldErr = System.err; StringPrintStream sps = new StringPrintStream(); System.setErr(sps.stream()); CommandLine.run(new App(), System.err, "-number", "not a number"); System.setErr(oldErr); assertFalse(runWasCalled[0]); assertEquals(String.format( "Invalid value for option '-number': 'not a number' is not an int%n" + "Usage:
[-number=]%n" + " -number=%n"), sps.toString()); } @Test(expected = InitializationException.class) public void testRunRequiresAnnotatedCommand() { class App implements Runnable { public void run() { } } CommandLine.run(new App(), System.err); } @Test public void testCallReturnsCallableResultParseSucceeds() throws Exception { @Command class App implements Callable { public Boolean call() { return true; } } assertTrue(CommandLine.call(new App(), System.err)); } @Test public void testCallReturnsNullAndPrintsErrorIfParseFails() throws Exception { class App implements Callable { @Option(names = "-number") int number; public Boolean call() { return true; } } PrintStream oldErr = System.err; StringPrintStream sps = new StringPrintStream(); System.setErr(sps.stream()); Boolean callResult = CommandLine.call(new App(), System.err, "-number", "not a number"); System.setErr(oldErr); assertNull(callResult); assertEquals(String.format( "Invalid value for option '-number': 'not a number' is not an int%n" + "Usage:
[-number=]%n" + " -number=%n"), sps.toString()); } @Test(expected = InitializationException.class) public void testCallRequiresAnnotatedCommand() throws Exception { class App implements Callable { public Object call() { return null; } } CommandLine.call(new App(), System.err); } @Test public void testExitCodeFromParseResultHandler() { @Command class App implements Runnable { public void run() { } } exit.expectSystemExitWithStatus(23); new CommandLine(new App()).parseWithHandler(new RunFirst().andExit(23), new String[]{}); } @Test public void testExitCodeFromParseResultHandler2() { @Command class App implements Runnable { public void run() { } } MyHandler handler = new MyHandler(); new CommandLine(new App()).parseWithHandler(handler.andExit(23), new String[]{}); assertEquals(23, handler.exitCode); } static class MyHandler extends RunLast { int exitCode; @Override protected void exit(int exitCode) { this.exitCode = exitCode; } } @Test public void testExitCodeFromExceptionHandler() { @Command class App implements Runnable { public void run() { throw new ParameterException(new CommandLine(this), "blah"); } } exit.expectSystemExitWithStatus(25); new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), defaultExceptionHandler().andExit(25)); assertEquals(format("" + "blah%n", "
"), systemErrRule.getLog()); } private DefaultExceptionHandler> defaultExceptionHandler() { return new DefaultExceptionHandler>(); } @Test public void testExitCodeFromExceptionHandler2() { @Command class App implements Runnable { public void run() { throw new ParameterException(new CommandLine(this), "blah"); } } CustomExceptionHandler> handler = new CustomExceptionHandler>(); new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), handler.andExit(25)); assertEquals(format("" + "blah%n" + "Usage:
%n"), systemErrRule.getLog()); assertEquals(25, handler.exitCode); } static class CustomExceptionHandler extends DefaultExceptionHandler { int exitCode; @Override protected void exit(int exitCode) { this.exitCode = exitCode; } } @Test public void testExitCodeFromExceptionHandler3() { @Command class App implements Runnable { public void run() { throw new ParameterException(new CommandLine(this), "blah"); } } CustomNoThrowExceptionHandler> handler = new CustomNoThrowExceptionHandler>(); new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), handler.andExit(25)); assertEquals(format("" + "blah%n" + "Usage:
%n"), systemErrRule.getLog()); assertEquals(25, handler.exitCode); } static class CustomNoThrowExceptionHandler extends DefaultExceptionHandler { int exitCode; ExecutionException caught; @Override protected R throwOrExit(ExecutionException ex) { try { super.throwOrExit(ex); } catch (ExecutionException caught) { this.caught = caught; } return null; } @Override protected void exit(int exitCode) { this.exitCode = exitCode; } } @Test public void testWithoutSystemExitForOtherExceptions() { @Command class App implements Runnable { public void run() { throw new RuntimeException("blah"); } } CustomNoThrowExceptionHandler> handler = new CustomNoThrowExceptionHandler>(); new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), handler); assertTrue(handler.caught.getCause() instanceof RuntimeException); assertEquals("blah", handler.caught.getCause().getMessage()); } @Test public void testSystemExitForOtherExceptions() { @Command class App implements Runnable { public void run() { throw new RuntimeException("blah"); } } exit.expectSystemExitWithStatus(25); exit.checkAssertionAfterwards(new Assertion() { public void checkAssertion() { String actual = systemErrRule.getLog(); assertTrue(actual.startsWith("picocli.CommandLine$ExecutionException: Error while running command (picocli.ExecuteLegacyTest")); assertTrue(actual.contains("java.lang.RuntimeException: blah")); } }); new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), defaultExceptionHandler().andExit(25)); } @Test(expected = InternalError.class) public void testNoSystemExitForErrors() { @Command class App implements Runnable { public void run() { throw new InternalError("blah"); } } new CommandLine(new App()).parseWithHandlers(new RunFirst().andExit(23), defaultExceptionHandler().andExit(25)); } @Command(name = "mycmd", mixinStandardHelpOptions = true, version = "MyCallable-1.0") static class MyCallable implements Callable { @Option(names = "-x", description = "this is an option") String option; public Object call() { throw new IllegalStateException("this is a test"); } } @Command(name = "mycmd", mixinStandardHelpOptions = true, version = "MyRunnable-1.0") static class MyRunnable implements Runnable { @Option(names = "-x", description = "this is an option") String option; public void run() { throw new IllegalStateException("this is a test"); } } private static final String MYCALLABLE_USAGE = format("" + "Usage: mycmd [-hV] [-x=