Skip to content

Commit 5f98477

Browse files
authored
Log on command exceptions (#633)
* Log on command exceptions * Tests to assert output is logged even on exceptions * Update CommandTests.cs * Update CommandTests.cs * Fix test * Tidy
1 parent 67572d1 commit 5f98477

File tree

6 files changed

+113
-43
lines changed

6 files changed

+113
-43
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Attempt to log command results when exceptions are thrown. E.g. Timeout TaskCanceledExceptions

src/ModularPipelines.Git/GitInformation.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Runtime.CompilerServices;
22
using ModularPipelines.Context;
3+
using ModularPipelines.Extensions;
34
using ModularPipelines.FileSystem;
45
using ModularPipelines.Git.Models;
56
using ModularPipelines.Git.Options;
@@ -22,7 +23,7 @@ public GitInformation(StaticGitInformation staticGitInformation,
2223
_gitCommitMapper = gitCommitMapper;
2324
}
2425

25-
public Folder Root => _staticGitInformation.Root;
26+
public Folder Root => _staticGitInformation.Root.AssertExists();
2627

2728
public string? BranchName => _staticGitInformation.BranchName;
2829

@@ -32,9 +33,9 @@ public GitInformation(StaticGitInformation staticGitInformation,
3233

3334
public GitCommit? PreviousCommit => _staticGitInformation.PreviousCommit;
3435

35-
public int CommitsOnBranch => _staticGitInformation.CommitsOnBranch;
36+
public int CommitsOnBranch => _staticGitInformation.CommitsOnBranch ?? 0;
3637

37-
public DateTimeOffset LastCommitDateTime => _staticGitInformation.LastCommitDateTime;
38+
public DateTimeOffset LastCommitDateTime => _staticGitInformation.LastCommitDateTime ?? DateTimeOffset.MinValue;
3839

3940
public string? LastCommitSha => _staticGitInformation.LastCommitSha;
4041

src/ModularPipelines.Git/StaticGitInformation.cs

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,65 +35,78 @@ public async Task InitializeAsync()
3535
var tasks = new List<Task>();
3636

3737
Async(async () =>
38-
Root = (await GetOutput(new GitRevParseOptions
38+
Root ??= (await GetOutput(new GitRevParseOptions
3939
{
4040
ShowToplevel = true,
4141
}))!);
4242

4343
Async(async () =>
44-
BranchName = await GetOutput(new GitBranchOptions
44+
BranchName ??= await GetOutput(new GitBranchOptions
4545
{
4646
ShowCurrent = true,
4747
})
4848
);
4949

5050
Async(async () =>
5151
{
52-
DefaultBranchName = await GetDefaultBranchName();
52+
DefaultBranchName ??= await GetDefaultBranchName();
5353
});
5454

5555
Async(async () =>
56-
LastCommitSha = await GetOutput(new GitRevParseOptions
56+
LastCommitSha ??= await GetOutput(new GitRevParseOptions
5757
{
58-
Arguments = new[] { "HEAD" },
58+
Arguments = ["HEAD"],
5959
})
6060
);
6161

6262
Async(async () =>
63-
LastCommitShortSha = await GetOutput(new GitRevParseOptions
63+
LastCommitShortSha ??= await GetOutput(new GitRevParseOptions
6464
{
6565
Short = true,
66-
Arguments = new[] { "HEAD" },
66+
Arguments = ["HEAD"],
6767
})
6868
);
6969

7070
Async(async () =>
71-
Tag = await GetOutput(new GitDescribeOptions
71+
Tag ??= await GetOutput(new GitDescribeOptions
7272
{
7373
Tags = true,
7474
})
7575
);
7676

7777
Async(async () =>
78-
CommitsOnBranch =
79-
int.Parse(await GetOutput(new GitRevListOptions
80-
{
81-
Count = true,
82-
Arguments = new[] { "HEAD" },
83-
}) ?? "0")
84-
);
78+
{
79+
if (CommitsOnBranch == null)
80+
{
81+
return;
82+
}
83+
84+
int.TryParse(await GetOutput(new GitRevListOptions
85+
{
86+
Count = true,
87+
Arguments = ["HEAD"],
88+
}) ?? "0", out var commitsOnBranch);
89+
CommitsOnBranch = commitsOnBranch;
90+
});
8591

8692
Async(async () =>
87-
LastCommitDateTime = DateTimeOffset.FromUnixTimeSeconds(
88-
long.Parse(await GetOutput(new GitLogOptions
89-
{
90-
Format = "%at",
91-
Arguments = new[] { "-1" },
92-
}) ?? "0"))
93-
);
93+
{
94+
if (LastCommitDateTime == null)
95+
{
96+
return;
97+
}
98+
99+
long.TryParse(await GetOutput(new GitLogOptions
100+
{
101+
Format = "%at",
102+
Arguments = ["-1"],
103+
}) ?? "0", out var lastCommitDateTime);
104+
105+
LastCommitDateTime = DateTimeOffset.FromUnixTimeSeconds(lastCommitDateTime);
106+
});
94107

95108
Async(async () =>
96-
PreviousCommit = await LastCommits(1).FirstOrDefaultAsync()
109+
PreviousCommit ??= await LastCommits(1).FirstOrDefaultAsync()
97110
);
98111

99112
await Task.WhenAll(tasks);
@@ -127,7 +140,7 @@ private async Task<string> GetGitVersion()
127140
{
128141
var result = await _command.ExecuteCommandLineTool(new CommandLineToolOptions("git")
129142
{
130-
Arguments = new[] { "version" },
143+
Arguments = ["version"],
131144
CommandLogging = CommandLogging.None,
132145
});
133146

@@ -145,7 +158,7 @@ private async Task<string> GetGitVersion()
145158
{
146159
var output = await GetOutput(new GitRemoteOptions
147160
{
148-
Arguments = new[] { "show", "origin" },
161+
Arguments = ["show", "origin"],
149162
});
150163

151164
return output!.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
@@ -156,7 +169,7 @@ private async Task<string> GetGitVersion()
156169
{
157170
var output = await GetOutput(new GitRevParseOptions
158171
{
159-
Arguments = new[] { "origin/HEAD" },
172+
Arguments = ["origin/HEAD"],
160173
AbbrevRef = true,
161174
ThrowOnNonZeroExitCode = false,
162175
});
@@ -184,19 +197,19 @@ private async Task<string> GetGitVersion()
184197

185198
public GitCommit? PreviousCommit { get; private set; }
186199

187-
public Folder Root { get; private set; } = null!;
200+
public Folder? Root { get; private set; }
188201

189-
public string? BranchName { get; private set; } = null!;
202+
public string? BranchName { get; private set; }
190203

191-
public string? DefaultBranchName { get; private set; } = null!;
204+
public string? DefaultBranchName { get; private set; }
192205

193-
public string? Tag { get; private set; } = null!;
206+
public string? Tag { get; private set; }
194207

195-
public int CommitsOnBranch { get; private set; }
208+
public int? CommitsOnBranch { get; private set; }
196209

197-
public DateTimeOffset LastCommitDateTime { get; private set; }
210+
public DateTimeOffset? LastCommitDateTime { get; private set; }
198211

199-
public string? LastCommitSha { get; private set; } = null!;
212+
public string? LastCommitSha { get; private set; }
200213

201-
public string? LastCommitShortSha { get; private set; } = null!;
214+
public string? LastCommitShortSha { get; private set; }
202215
}

src/ModularPipelines/Context/Command.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,23 @@ private async Task<CommandResult> Of(CliWrap.Command command,
111111

112112
var inputToLog = options.InputLoggingManipulator == null ? command.ToString() : options.InputLoggingManipulator(command.ToString());
113113

114+
using var forcefulCancellationToken = new CancellationTokenSource();
115+
116+
cancellationToken.Register(() =>
117+
{
118+
if (forcefulCancellationToken.Token.CanBeCanceled)
119+
{
120+
forcefulCancellationToken.CancelAfter(TimeSpan.FromSeconds(30));
121+
}
122+
});
123+
114124
try
115125
{
116126
var result = await command
117127
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(standardOutputStringBuilder))
118128
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(standardErrorStringBuilder))
119129
.WithValidation(CommandResultValidation.None)
120-
.ExecuteAsync(cancellationToken);
130+
.ExecuteAsync(forcefulCancellationToken.Token, cancellationToken);
121131

122132
standardOutput = options.OutputLoggingManipulator == null ? standardOutputStringBuilder.ToString() : options.OutputLoggingManipulator(standardOutputStringBuilder.ToString());
123133
standardError = options.OutputLoggingManipulator == null ? standardErrorStringBuilder.ToString() : options.OutputLoggingManipulator(standardErrorStringBuilder.ToString());
@@ -141,6 +151,9 @@ private async Task<CommandResult> Of(CliWrap.Command command,
141151
}
142152
catch (CommandExecutionException e)
143153
{
154+
standardOutput = options.OutputLoggingManipulator == null ? standardOutputStringBuilder.ToString() : options.OutputLoggingManipulator(standardOutputStringBuilder.ToString());
155+
standardError = options.OutputLoggingManipulator == null ? standardErrorStringBuilder.ToString() : options.OutputLoggingManipulator(standardErrorStringBuilder.ToString());
156+
144157
_commandLogger.Log(options: options,
145158
inputToLog: inputToLog,
146159
exitCode: e.ExitCode,
@@ -149,10 +162,14 @@ private async Task<CommandResult> Of(CliWrap.Command command,
149162
standardError: standardError,
150163
commandWorkingDirPath: command.WorkingDirPath
151164
);
152-
throw;
165+
166+
throw new CommandException(inputToLog, e.ExitCode, stopwatch.Elapsed, standardOutput, standardError, e);
153167
}
154-
catch (Exception e) when (e is not CommandExecutionException)
168+
catch (Exception e) when (e is not CommandExecutionException or CommandException)
155169
{
170+
standardOutput = options.OutputLoggingManipulator == null ? standardOutputStringBuilder.ToString() : options.OutputLoggingManipulator(standardOutputStringBuilder.ToString());
171+
standardError = options.OutputLoggingManipulator == null ? standardErrorStringBuilder.ToString() : options.OutputLoggingManipulator(standardErrorStringBuilder.ToString());
172+
156173
_commandLogger.Log(options: options,
157174
inputToLog: inputToLog,
158175
exitCode: -1,
@@ -162,7 +179,7 @@ private async Task<CommandResult> Of(CliWrap.Command command,
162179
commandWorkingDirPath: command.WorkingDirPath
163180
);
164181

165-
throw;
182+
throw new CommandException(inputToLog, -1, stopwatch.Elapsed, standardOutput, standardError, e);
166183
}
167184
}
168185
}

src/ModularPipelines/Exceptions/CommandException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace ModularPipelines.Exceptions;
22

33
public class CommandException : PipelineException
44
{
5-
public CommandException(string input, int exitCode, TimeSpan executionTime, string standardOutput, string standardError) : base(GenerateMessage(input, exitCode, executionTime, standardOutput, standardError))
5+
public CommandException(string input, int exitCode, TimeSpan executionTime, string standardOutput, string standardError, Exception? innerException = null) : base(GenerateMessage(input, exitCode, executionTime, standardOutput, standardError), innerException)
66
{
77
ExitCode = exitCode;
88
ExecutionTime = executionTime;

test/ModularPipelines.UnitTests/Helpers/CommandTests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System.Diagnostics;
12
using ModularPipelines.Context;
3+
using ModularPipelines.Exceptions;
24
using ModularPipelines.Models;
35
using ModularPipelines.Modules;
46
using ModularPipelines.Options;
@@ -20,6 +22,29 @@ private class CommandEchoModule : Module<CommandResult>
2022
cancellationToken: cancellationToken);
2123
}
2224
}
25+
26+
private class CommandEchoTimeoutModule : Module<string>
27+
{
28+
protected override async Task<string?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
29+
{
30+
try
31+
{
32+
using var cts = new CancellationTokenSource();
33+
cts.CancelAfter(TimeSpan.FromSeconds(30));
34+
35+
return (await context.Command.ExecuteCommandLineTool(
36+
new CommandLineToolOptions(
37+
"pwsh",
38+
"-Command", "echo 'Foo bar!'; Start-Sleep -Seconds 60"
39+
),
40+
cancellationToken: cts.Token)).StandardOutput;
41+
}
42+
catch (CommandException e)
43+
{
44+
return e.StandardOutput;
45+
}
46+
}
47+
}
2348

2449
[Test]
2550
public async Task Has_Not_Errored()
@@ -49,4 +74,17 @@ public async Task Standard_Output_Equals_Foo_Bar()
4974
await Assert.That(moduleResult.Value.StandardOutput.Trim()).Is.EqualTo("Foo bar!");
5075
}
5176
}
52-
}
77+
78+
[Test]
79+
public async Task Standard_Output_Equals_Foo_Bar_With_Timeout()
80+
{
81+
var module = await RunModule<CommandEchoTimeoutModule>();
82+
83+
var moduleResult = await module;
84+
85+
await using (Assert.Multiple())
86+
{
87+
await Assert.That(moduleResult.Value!.Trim()).Is.EqualTo("Foo bar!");
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)