Fix exit code overflow for unhandled exceptions on Unix systems (#62)

This commit is contained in:
Ihor Nechyporuk
2020-07-19 16:50:37 +03:00
committed by GitHub
parent ac83233dc2
commit 004f906148
4 changed files with 32 additions and 7 deletions

View File

@@ -20,7 +20,7 @@ namespace CliFx.Tests
private class CommandExceptionCommand : ICommand
{
[CommandOption("code", 'c')]
public int ExitCode { get; set; } = 1337;
public int ExitCode { get; set; } = 133;
[CommandOption("msg", 'm')]
public string? Message { get; set; }

View File

@@ -263,7 +263,7 @@ namespace CliFx
public static int FromException(Exception ex) =>
ex is CommandException cmdEx
? cmdEx.ExitCode
: ex.HResult;
: 1;
}
[Command]

View File

@@ -9,12 +9,14 @@ namespace CliFx.Exceptions
/// </summary>
public class CommandException : Exception
{
private const int DefaultExitCode = -1;
private const int DefaultExitCode = 1;
private readonly bool _isMessageSet;
/// <summary>
/// Returns an exit code associated with this exception.
/// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255
/// otherwise it may overflow and yield unexpected results.
/// </summary>
public int ExitCode { get; }
@@ -26,6 +28,14 @@ namespace CliFx.Exceptions
/// <summary>
/// Initializes an instance of <see cref="CommandException"/>.
/// </summary>
/// <param name="message">The exception message.</param>
/// <param name="innerException">The inner exception.</param>
/// <param name="exitCode">
/// The exit code associated with this exception.
/// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255
/// otherwise it may overflow and yield unexpected results.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
public CommandException(string? message, Exception? innerException, int exitCode = DefaultExitCode, bool showHelp = false)
: base(message, innerException)
{
@@ -39,6 +49,13 @@ namespace CliFx.Exceptions
/// <summary>
/// Initializes an instance of <see cref="CommandException"/>.
/// </summary>
/// <param name="message">The exception message.</param>
/// <param name="exitCode">
/// The exit code associated with this exception.
/// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255
/// otherwise it may overflow and yield unexpected results.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
public CommandException(string? message, int exitCode = DefaultExitCode, bool showHelp = false)
: this(message, null, exitCode, showHelp)
{
@@ -47,6 +64,12 @@ namespace CliFx.Exceptions
/// <summary>
/// Initializes an instance of <see cref="CommandException"/>.
/// </summary>
/// <param name="exitCode">
/// The exit code associated with this exception.
/// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255
/// otherwise it may overflow and yield unexpected results.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
public CommandException(int exitCode = DefaultExitCode, bool showHelp = false)
: this(null, exitCode, showHelp)
{

View File

@@ -396,10 +396,12 @@ You can run `myapp.exe cmd1 [command] --help` to show help on a specific command
You may have noticed that commands in CliFx don't return exit codes. This is by design as exit codes are considered a higher-level concern and thus handled by `CliApplication`, not by individual commands.
Commands can report execution failure simply by throwing exceptions just like any other C# code. When an exception is thrown, `CliApplication` will catch it, print the error, and return an appropriate exit code to the calling process.
Commands can report execution failure simply by throwing exceptions just like any other C# code. When an exception is thrown, `CliApplication` will catch it, print the error, and return an exit code `1` to the calling process.
If you want to communicate a specific error through exit code, you can instead throw an instance of `CommandException` which takes an exit code as a parameter. When a command throws an exception of type `CommandException`, it is assumed that this was a result of a handled error and, as such, only the exception message will be printed to the error stream. If a command throws an exception of any other type, the full stack trace will be printed as well.
> Please note that on Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between `1` and `255` otherwise it may overflow and yield unexpected results.
```c#
[Command]
public class DivideCommand : ICommand
@@ -414,8 +416,8 @@ public class DivideCommand : ICommand
{
if (Math.Abs(Divisor) < double.Epsilon)
{
// This will print the error and set exit code to 1337
throw new CommandException("Division by zero is not supported.", 1337);
// This will print the error and set exit code to 133
throw new CommandException("Division by zero is not supported.", 133);
}
var result = Dividend / Divisor;
@@ -434,7 +436,7 @@ Division by zero is not supported.
> $LastExitCode
1337
133
```
You can also specify the `showHelp` parameter to instruct whether to show the help text for the current command after printing the error: