-
Notifications
You must be signed in to change notification settings - Fork 433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support intercepting the help message generated by a multiple-subcommand application #218
Comments
About the snippet you provide with the About testing, your approach of passing in a custom printStream is exactly how many of the tests in You may want to create a convenience method like below in the test:
Note that the output will contain line separators. To make your tests run in any environment, it may be useful to use Would this work for you? |
Actually, you've inspired me! Exiting with a given error code is probably a common use case. I'm thinking to provide two more convenience methods that decorate a
That way, client code that want to exit with an error code, like your application, could look like this:
|
Oh cool, thank you very much for pointing me in the direction of those tests! It's a shame that I have to use I'll test it and let you know if it works as a solution for me when I get into work next week. :)
IIRC, I'm already asserting in my tests that the line separators in my program's help message are equal to |
I think providing What do you think of providing a cmd.parseWithHandler(exit(0, new RunLast()),
/* out= */ System.out,
/* err= */ System.err,
Help.Ansi.AUTO,
exit(-1, new DefaultExceptionHandler()),
args); |
Talking about the I don't know if you've thought about this already, but I wonder if it would be good to provide a private static String usageString(CommandLine commandLine, Help.Ansi ansi) {
StringWriter out = new StringWriter();
commandLine.usage(new PrintWriter(out, true), ansi);
String result = out.toString();
return result;
} |
I just thought of a way to address your requirements while also reducing the number of arguments passed to the
User code, like your application, would look like this:
Test code could look like this:
This looks a lot cleaner than the current API and I wish I had thought of it earlier... This also makes it possible to have much simpler
Unfortunately I can't think of another way to avoid breaking backwards compatibility than adding additional interfaces with different names. Darn, I really wish we could have had this conversation a week ago! :-) |
Yes, it's really a shame that I only discovered this need a few days ago and (re-)discovered picocli a few days prior! :P I've managed to resolve my testing needs not with a @Test
void theTest {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
ExitCode exitCode =
CommandLineEntryPoint.parseArgsAndExecute(
new PrintStream(out, true, "UTF8"), new PrintStream(err, true, "UTF8"));
assertAll(
() -> assertThat(out.toString()).contains(HIGH_LEVEL_USAGE_HELP),
() -> assertThat(err.toString()).isEmpty(),
() -> assertThat(exitCode.numericalExitCode()).isEqualTo(0));
} |
So it's no longer urgent for me to have an overload of |
If anything, I think it would be even nicer, and probably more important for the maintainability of my program at this point, to have implementation(s) of |
Thank you very much for all your help so far @remkop! |
I finally got around to working on this. Picocli 3.0 introduces these new classes and interfaces:
There is a new simplified Handlers that extend class MyCommand implements Runnable {
public void run() { ... }
public void static main(String... args) {
CommandLine cmd = new CommandLine(new MyCommand());
cmd.parseWithHandlers(
new RunLast().useOut(System.out).useAnsi(Help.Ansi.AUTO).andExit(123),
new DefaultExceptionHandler().useErr(System.err).andExit(456),
args);
}
} This is now in master. There is no convenience methods to specify a Thoughts? |
Thanks for taking the time to look into this @remkop. The new API looks pretty neat! I'll see if I can find the time in the near future to play around with it and let you know what I think. Regarding stdout/stderr streams and exit code testing, I'm sure that http://stefanbirkner.github.io/system-rules/#SystemErrAndOutRule would be a very good way of doing it for those people using JUnit 4, but I'm personally using JUnit 5, which doesn't provide |
Thanks for the response. From your test code snippet I can guess how you test the stderr and stdout streams, but can I ask how you test the exit code in JUnit 5? I'm thinking to do a write-up of how to test picocli command line applications. |
My solution to testing exit codes is admittedly not great. What I do is I organise the entry point of my application like this: public enum ExitCode {
SUCCESS(0),
ERROR(1);
private final int numericalExitCode;
ExitCode(int numericalExitCode) {
this.numericalExitCode = numericalExitCode;
}
public int numericalExitCode() {
return numericalExitCode;
}
}
public final class Application {
private Application() {}
public static void main(String[] args) {
ExitCode exitCode = execute(System.out, System.err, args);
System.exit(exitCode.numericalExitCode());
}
@VisibleForTesting
static ExitCode execute(PrintStream out, PrintStream err, String... args) {
// Do useful stuff here!
}
} and then I write tests which test against @Test
void main_noArgs_printsHelp() throws UnsupportedEncodingException {
// given
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
// when
ExitCode exitCode =
Application.execute(new PrintStream(out, true, "UTF8"), new PrintStream(err, true, "UTF8"));
// then
assertAll(
() -> assertThat(out.toString()).isEmpty(),
() -> assertContainsImportantHelpKeywords(err.toString()),
() -> assertThat(exitCode.numericalExitCode()).isEqualTo(0));
} FYI, my tests are written using JUnit 5's Jupiter API assertions and Truth, and (I used an enum for the Since I use JUnit Jupiter (part of JUnit 5) and thus I don't have access to https://stefanbirkner.github.io/system-rules/, I believe that I'm forced to write my exit code tests this way. I certainly don't see a way that I could migrate to the cool new API you've written for setting exit codes without downgrading from JUnit Jupiter to JUnit 4 just so I could use https://stefanbirkner.github.io/system-rules/. It's my hope that if or when https://stefanbirkner.github.io/system-rules/ is ported to JUnit Jupiter (stefanbirkner/system-rules#55), then I could look into migrating to the new exit-code-setting API. |
Closing this ticket, since #307 (finally) added the requested Please feel free to reopen or raise another one for any follow-up issues. |
Great! Thank you very much @remkop. :) I hope that my response above for exit code testing was helpful in some way? I've not yet observed any info in the User Guide that shows how one can test exit codes, but I observe from picocli's tests that the way you're doing it right now for picocli itself is with https://stefanbirkner.github.io/system-rules/. |
Yes it certainly was helpful! |
Currently, if one has a command-line application with multiple subcommands, and one wants to write a high-level help message to the console, they would write something like this:
or if things are a bit more complicated (as is the case for me):
But sadly, this is not quite enough for me, because I need some way of "intercepting" the text sent to
System.{out,err}
so that I can assert on the contents of auto-generated help message at unit-test time.The closest solution I've thought of so far is to use
PrintWriter
s in place ofSystem.{out,err}
so that, at unit-test time, I can swap outSystem.{out,err}
with a customPrintWriter
that diverts the text it receives to aStringWriter
, so that I can query the text directly. But sadly, this doesn't work becauseRunLast::handleParseResult
doesn't have an overload that accepts aPrintWriter
instead of aPrintStream
.Does picocli have something built-in that would allow me to intercept the auto-generated help message of a command-line application with multiple subcommands?
If not, would providing an overload of
IParseResultHandler::handleParseResult
that accepts aPrintWriter
be an idea that the picocli team would be interested in implementing?The text was updated successfully, but these errors were encountered: