Skip to content

Commit 8a3469a

Browse files
authored
Allowing for customizing error result codes (#1131)
* Allowing for customizing error result codes * Result code renamed to be exit code * Fixing tests to use Option<T>
1 parent a2e5425 commit 8a3469a

13 files changed

+121
-44
lines changed

src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public async Task RootCommand_InvokeAsync_can_set_custom_result_code()
126126

127127
rootCommand.Handler = CommandHandler.Create<InvocationContext>(context =>
128128
{
129-
context.ResultCode = 123;
129+
context.ExitCode = 123;
130130
});
131131

132132
var resultCode = await rootCommand.InvokeAsync("");
@@ -141,7 +141,7 @@ public void RootCommand_Invoke_can_set_custom_result_code()
141141

142142
rootCommand.Handler = CommandHandler.Create<InvocationContext>(context =>
143143
{
144-
context.ResultCode = 123;
144+
context.ExitCode = 123;
145145
});
146146

147147
int resultCode = rootCommand.Invoke("");

src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@ public void Synchronous_invocation_can_be_short_circuited_by_async_middleware_by
256256
handlerWasCalled.Should().BeFalse();
257257
}
258258

259-
260259
[Fact]
261260
public async Task When_no_help_builder_is_specified_it_uses_default_implementation()
262261
{

src/System.CommandLine.Tests/ParseDirectiveTests.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using FluentAssertions;
45
using System.CommandLine.Builder;
5-
using System.CommandLine.Invocation;
66
using System.CommandLine.IO;
77
using System.CommandLine.Parsing;
88
using System.Threading.Tasks;
9-
using FluentAssertions;
109
using Xunit;
1110
using Xunit.Abstractions;
1211

@@ -27,10 +26,7 @@ public async Task Parse_directive_writes_parse_diagram()
2726
var rootCommand = new RootCommand();
2827
var subcommand = new Command("subcommand");
2928
rootCommand.AddCommand(subcommand);
30-
var option = new Option(new[] { "-c", "--count" })
31-
{
32-
Argument = new Argument<int>()
33-
};
29+
var option = new Option<int>(new[] { "-c", "--count" });
3430
subcommand.AddOption(option);
3531

3632
var parser = new CommandLineBuilder(rootCommand)
@@ -56,10 +52,7 @@ public async Task When_there_are_no_errors_then_parse_directive_sets_exit_code_0
5652
{
5753
var command = new RootCommand
5854
{
59-
new Option("-x")
60-
{
61-
Argument = new Argument<int>()
62-
}
55+
new Option<int>("-x")
6356
};
6457

6558
var exitCode = await command.InvokeAsync("[parse] -x 123");
@@ -72,15 +65,29 @@ public async Task When_there_are_errors_then_parse_directive_sets_exit_code_1()
7265
{
7366
var command = new RootCommand
7467
{
75-
new Option("-x")
76-
{
77-
Argument = new Argument<int>()
78-
}
68+
new Option<int>("-x")
7969
};
8070

8171
var exitCode = await command.InvokeAsync("[parse] -x not-an-int");
8272

8373
exitCode.Should().Be(1);
8474
}
75+
76+
[Fact]
77+
public async Task When_there_are_errors_then_parse_directive_sets_exit_code_to_custom_value()
78+
{
79+
var command = new RootCommand
80+
{
81+
new Option<int>("-x")
82+
};
83+
84+
int exitCode = await new CommandLineBuilder()
85+
.AddCommand(command)
86+
.UseParseDirective(errorExitCode: 42)
87+
.Build()
88+
.InvokeAsync("[parse] -x not-an-int");
89+
90+
exitCode.Should().Be(42);
91+
}
8592
}
8693
}

src/System.CommandLine.Tests/UseExceptionHandlerTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,32 @@ public async Task When_thrown_exception_is_from_cancelation_no_output_is_generat
126126
[Fact]
127127
public async Task UseExceptionHandler_output_can_be_customized()
128128
{
129-
await new CommandLineBuilder()
129+
int resultCode = await new CommandLineBuilder()
130130
.AddCommand(new Command("the-command"))
131131
.UseExceptionHandler((exception, context) =>
132132
{
133133
context.Console.Out.Write("Well that's awkward.");
134+
context.ExitCode = 22;
134135
})
135136
.UseMiddleware(_ => throw new Exception("oops!"))
136137
.Build()
137138
.InvokeAsync("the-command", _console);
138139

139140
_console.Out.ToString().Should().Be("Well that's awkward.");
141+
resultCode.Should().Be(22);
142+
}
143+
144+
[Fact]
145+
public async Task UseExceptionHandler_set_custom_result_code()
146+
{
147+
int resultCode = await new CommandLineBuilder()
148+
.AddCommand(new Command("the-command"))
149+
.UseExceptionHandler(errorExitCode: 42)
150+
.UseMiddleware(_ => throw new Exception("oops!"))
151+
.Build()
152+
.InvokeAsync("the-command", _console);
153+
154+
resultCode.Should().Be(42);
140155
}
141156
}
142157
}

src/System.CommandLine.Tests/UseParseErrorReportingTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,22 @@ public void Parse_error_reporting_reports_error_when_help_is_used_and_required_s
3434

3535
result.Should().Be(1);
3636
}
37+
38+
[Fact]
39+
public void Parse_error_uses_custom_error_result_code()
40+
{
41+
var root = new RootCommand
42+
{
43+
new Command("inner")
44+
};
45+
46+
var parser = new CommandLineBuilder(root)
47+
.UseParseErrorReporting(errorExitCode: 42)
48+
.Build();
49+
50+
int result = parser.Invoke("");
51+
52+
result.Should().Be(42);
53+
}
3754
}
3855
}

src/System.CommandLine.Tests/VersionOptionTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,27 @@ public void Version_is_not_valid_with_other_tokens(string commandLine)
106106
result.Should().NotBe(0);
107107
}
108108

109+
[Fact]
110+
public void When_the_version_is_not_valid_it_returns_a_custom_result_code()
111+
{
112+
var rootCommand = new RootCommand
113+
{
114+
new Command("subcommand")
115+
{
116+
Handler = CommandHandler.Create(() => { })
117+
},
118+
new Option("-x")
119+
};
120+
121+
var parser = new CommandLineBuilder(rootCommand)
122+
.UseVersionOption(errorExitCode: 42)
123+
.Build();
124+
125+
int result = parser.Invoke("--version -x");
126+
127+
result.Should().Be(42);
128+
}
129+
109130
[Fact]
110131
public void Version_option_is_not_added_to_subcommands()
111132
{

src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static CommandLineBuilder CancelOnProcessTermination(this CommandLineBuil
103103
// because Main will not finish executing.
104104
// Wait for the invocation to finish.
105105
blockProcessExit.Wait();
106-
Environment.ExitCode = context.ResultCode;
106+
Environment.ExitCode = context.ExitCode;
107107
};
108108
Console.CancelKeyPress += consoleHandler;
109109
AppDomain.CurrentDomain.ProcessExit += processExitHandler;
@@ -272,7 +272,8 @@ public static CommandLineBuilder UseDefaults(this CommandLineBuilder builder)
272272

273273
public static CommandLineBuilder UseExceptionHandler(
274274
this CommandLineBuilder builder,
275-
Action<Exception, InvocationContext>? onException = null)
275+
Action<Exception, InvocationContext>? onException = null,
276+
int? errorExitCode = null)
276277
{
277278
builder.AddMiddleware(async (context, next) =>
278279
{
@@ -300,7 +301,7 @@ void Default(Exception exception, InvocationContext context)
300301

301302
context.Console.ResetTerminalForegroundColor();
302303
}
303-
context.ResultCode = 1;
304+
context.ExitCode = errorExitCode ?? 1;
304305
}
305306
}
306307

@@ -368,13 +369,14 @@ public static CommandLineBuilder UseMiddleware(
368369
}
369370

370371
public static CommandLineBuilder UseParseDirective(
371-
this CommandLineBuilder builder)
372+
this CommandLineBuilder builder,
373+
int? errorExitCode = null)
372374
{
373375
builder.AddMiddleware(async (context, next) =>
374376
{
375377
if (context.ParseResult.Directives.Contains("parse"))
376378
{
377-
context.InvocationResult = new ParseDirectiveResult();
379+
context.InvocationResult = new ParseDirectiveResult(errorExitCode);
378380
}
379381
else
380382
{
@@ -386,13 +388,14 @@ public static CommandLineBuilder UseParseDirective(
386388
}
387389

388390
public static CommandLineBuilder UseParseErrorReporting(
389-
this CommandLineBuilder builder)
391+
this CommandLineBuilder builder,
392+
int? errorExitCode = null)
390393
{
391394
builder.AddMiddleware(async (context, next) =>
392395
{
393396
if (context.ParseResult.Errors.Count > 0)
394397
{
395-
context.InvocationResult = new ParseErrorResult();
398+
context.InvocationResult = new ParseErrorResult(errorExitCode);
396399
}
397400
else
398401
{
@@ -458,7 +461,8 @@ public static CommandLineBuilder UseValidationMessages(
458461
}
459462

460463
public static CommandLineBuilder UseVersionOption(
461-
this CommandLineBuilder builder)
464+
this CommandLineBuilder builder,
465+
int? errorExitCode = null)
462466
{
463467
var command = builder.Command;
464468

@@ -489,7 +493,7 @@ public static CommandLineBuilder UseVersionOption(
489493
{
490494
if (result.ArgumentConversionResult.ErrorMessage is { })
491495
{
492-
context.InvocationResult = new ParseErrorResult();
496+
context.InvocationResult = new ParseErrorResult(errorExitCode);
493497
}
494498
else
495499
{

src/System.CommandLine/Invocation/CommandHandler.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,19 @@ public static ICommandHandler Create<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T1
203203
Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, Task<int>> action) =>
204204
HandlerDescriptor.FromDelegate(action).GetCommandHandler();
205205

206-
internal static async Task<int> GetResultCodeAsync(object value, InvocationContext context)
206+
internal static async Task<int> GetExitCodeAsync(object value, InvocationContext context)
207207
{
208208
switch (value)
209209
{
210-
case Task<int> resultCodeTask:
211-
return await resultCodeTask;
210+
case Task<int> exitCodeTask:
211+
return await exitCodeTask;
212212
case Task task:
213213
await task;
214-
return context.ResultCode;
215-
case int resultCode:
216-
return resultCode;
214+
return context.ExitCode;
215+
case int exitCode:
216+
return exitCode;
217217
case null:
218-
return context.ResultCode;
218+
return context.ExitCode;
219219
default:
220220
throw new NotSupportedException();
221221
}

src/System.CommandLine/Invocation/InvocationContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public ParseResult ParseResult
3333
set => BindingContext.ParseResult = value;
3434
}
3535

36-
public int ResultCode { get; set; }
36+
public int ExitCode { get; set; }
3737

3838
public IInvocationResult? InvocationResult { get; set; }
3939

src/System.CommandLine/Invocation/InvocationPipeline.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public async Task<int> InvokeAsync(IConsole? console = null)
2525

2626
await invocationChain(context, invocationContext => Task.CompletedTask);
2727

28-
return GetResultCode(context);
28+
return GetExitCode(context);
2929
}
3030

3131
public int Invoke(IConsole? console = null)
@@ -36,7 +36,7 @@ public int Invoke(IConsole? console = null)
3636

3737
Task.Run(() => invocationChain(context, invocationContext => Task.CompletedTask)).GetAwaiter().GetResult();
3838

39-
return GetResultCode(context);
39+
return GetExitCode(context);
4040
}
4141

4242
private static InvocationMiddleware BuildInvocationChain(InvocationContext context)
@@ -54,7 +54,7 @@ private static InvocationMiddleware BuildInvocationChain(InvocationContext conte
5454

5555
if (handler != null)
5656
{
57-
context.ResultCode = await handler.InvokeAsync(invocationContext);
57+
context.ExitCode = await handler.InvokeAsync(invocationContext);
5858
}
5959
}
6060
});
@@ -66,11 +66,11 @@ private static InvocationMiddleware BuildInvocationChain(InvocationContext conte
6666
c => second(c, next)));
6767
}
6868

69-
private static int GetResultCode(InvocationContext context)
69+
private static int GetExitCode(InvocationContext context)
7070
{
7171
context.InvocationResult?.Apply(context);
7272

73-
return context.ResultCode;
73+
return context.ExitCode;
7474
}
7575
}
7676
}

0 commit comments

Comments
 (0)