Skip to content

Commit beb677f

Browse files
committed
Fix concurrency issues over Console.Out
1 parent 2015aa4 commit beb677f

File tree

6 files changed

+49
-40
lines changed

6 files changed

+49
-40
lines changed

tools/ExampleTester/ExampleTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ namespace ExampleTester;
88
public static class ExampleTests
99
{
1010
private static TesterConfiguration TesterConfiguration { get; } = new(FindTmpDirectory());
11-
private static StatusCheckLogger StatusCheckLogger { get; } = new("..", "Example tester");
1211

1312
public static IEnumerable<object[]> LoadExamples() =>
1413
from example in GeneratedExample.LoadAllExamples(TesterConfiguration.ExtractedOutputDirectory)
@@ -17,7 +16,9 @@ from example in GeneratedExample.LoadAllExamples(TesterConfiguration.ExtractedOu
1716
[TestCaseSource(nameof(LoadExamples))]
1817
public static async Task ExamplePasses(GeneratedExample example)
1918
{
20-
if (!await example.Test(TesterConfiguration, StatusCheckLogger))
19+
var logger = new StatusCheckLogger(TestContext.Out, "..", "Example tester");
20+
21+
if (!await example.Test(TesterConfiguration, logger))
2122
Assert.Fail("There were one or more failures. See the logged output for details.");
2223
}
2324

tools/ExampleTester/GeneratedExample.cs

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
namespace ExampleTester;
1111

12-
internal class GeneratedExample
12+
public class GeneratedExample
1313
{
14+
private static readonly object ConsoleAccessLock = new();
15+
1416
static GeneratedExample()
1517
{
1618
MSBuildLocator.RegisterDefaults();
@@ -135,46 +137,52 @@ bool ValidateOutput()
135137
? new object[] { Metadata.ExecutionArgs ?? new string[0] }
136138
: new object[0];
137139

138-
var oldOut = Console.Out;
139140
List<string> actualLines;
140141
Exception? actualException = null;
141-
try
142+
lock (ConsoleAccessLock)
142143
{
143-
var builder = new StringBuilder();
144-
Console.SetOut(new StringWriter(builder));
144+
var oldOut = Console.Out;
145145
try
146146
{
147-
var result = method.Invoke(null, arguments);
148-
// For async Main methods, the compilation's entry point is still the Main
149-
// method, so we explicitly wait for the returned task just like the synthesized
150-
// entry point would.
151-
if (result is Task task)
147+
var builder = new StringBuilder();
148+
Console.SetOut(new StringWriter(builder));
149+
try
150+
{
151+
var result = method.Invoke(null, arguments);
152+
// For async Main methods, the compilation's entry point is still the Main
153+
// method, so we explicitly wait for the returned task just like the synthesized
154+
// entry point would.
155+
if (result is Task task)
156+
{
157+
task.GetAwaiter().GetResult();
158+
}
159+
160+
// For some reason, we don't *actually* get the result of all finalizers
161+
// without this. We shouldn't need it (as relevant examples already have it) but
162+
// code that works outside the test harness doesn't work inside it.
163+
GC.Collect();
164+
GC.WaitForPendingFinalizers();
165+
}
166+
catch (TargetInvocationException outer)
152167
{
153-
task.GetAwaiter().GetResult();
168+
actualException = outer.InnerException ?? throw new InvalidOperationException("TargetInvocationException had no nested exception");
154169
}
155-
// For some reason, we don't *actually* get the result of all finalizers
156-
// without this. We shouldn't need it (as relevant examples already have it) but
157-
// code that works outside the test harness doesn't work inside it.
158-
GC.Collect();
159-
GC.WaitForPendingFinalizers();
170+
171+
// Skip blank lines, to avoid unnecessary trailing empties.
172+
// Also trim the end of each actual line, to avoid trailing spaces being necessary in the metadata
173+
// or listed console output.
174+
actualLines = builder.ToString()
175+
.Replace("\r\n", "\n")
176+
.Split('\n')
177+
.Select(line => line.TrimEnd())
178+
.Where(line => line != "").ToList();
160179
}
161-
catch (TargetInvocationException outer)
180+
finally
162181
{
163-
actualException = outer.InnerException ?? throw new InvalidOperationException("TargetInvocationException had no nested exception");
182+
Console.SetOut(oldOut);
164183
}
165-
// Skip blank lines, to avoid unnecessary trailing empties.
166-
// Also trim the end of each actual line, to avoid trailing spaces being necessary in the metadata
167-
// or listed console output.
168-
actualLines = builder.ToString()
169-
.Replace("\r\n", "\n")
170-
.Split('\n')
171-
.Select(line => line.TrimEnd())
172-
.Where(line => line != "").ToList();
173-
}
174-
finally
175-
{
176-
Console.SetOut(oldOut);
177184
}
185+
178186
var expectedLines = Metadata.ExpectedOutput ?? new List<string>();
179187
return ValidateException(actualException, Metadata.ExpectedException) &&
180188
(Metadata.IgnoreOutput || ValidateExpectedAgainstActual("output", expectedLines, actualLines));

tools/ExampleTester/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.CommandLine;
33
using Utilities;
44

5-
var logger = new StatusCheckLogger("..", "Example tester");
5+
var logger = new StatusCheckLogger(Console.Out, "..", "Example tester");
66
var headSha = Environment.GetEnvironmentVariable("HEAD_SHA");
77
var token = Environment.GetEnvironmentVariable("GH_TOKEN");
88

tools/MarkdownConverter/Spec/Reporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public Reporter() : this(null, null) { }
2828
public Reporter(Reporter? parent, string? filename)
2929
{
3030
// This is needed so that all Reporters share the same GitHub logger.
31-
this.githubLogger = parent?.githubLogger ?? new StatusCheckLogger("..", "Markdown to Word Converter");
31+
this.githubLogger = parent?.githubLogger ?? new StatusCheckLogger(Console.Out, "..", "Markdown to Word Converter");
3232
this.parent = parent;
3333
Location = new SourceLocation(filename, null, null, null);
3434
}

tools/StandardAnchorTags/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class Program
2525
/// <returns>0 on success, non-zero on failure</returns>
2626
static async Task<int> Main(string owner, string repo, bool dryrun =false)
2727
{
28-
var logger = new StatusCheckLogger("..", "TOC and Anchor updater");
28+
var logger = new StatusCheckLogger(Console.Out, "..", "TOC and Anchor updater");
2929
var headSha = Environment.GetEnvironmentVariable("HEAD_SHA");
3030
var token = Environment.GetEnvironmentVariable("GH_TOKEN");
3131
using FileStream openStream = File.OpenRead(FilesPath);

tools/Utilities/StatusCheckLogger.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ public record StatusCheckMessage(string file, int StartLine, int EndLine, string
2222
/// </remarks>
2323
/// <param name="pathToRoot">The path to the root of the repository</param>
2424
/// <param name="toolName">The name of the tool that is running the check</param>
25-
public class StatusCheckLogger(string pathToRoot, string toolName)
25+
public class StatusCheckLogger(TextWriter writer, string pathToRoot, string toolName)
2626
{
2727
private List<NewCheckRunAnnotation> annotations = [];
2828
public bool Success { get; private set; } = true;
2929

3030
// Utility method to format the path to unix style, from the root of the repository.
3131
private string FormatPath(string path) => Path.GetRelativePath(pathToRoot, path).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
3232

33-
private void WriteMessageToConsole(string prefix, StatusCheckMessage d) => Console.WriteLine($"{prefix}{toolName}-{d.Id}::file={FormatPath(d.file)},line={d.StartLine}::{d.Message}");
33+
private void WriteMessageToConsole(string prefix, StatusCheckMessage d) => writer.WriteLine($"{prefix}{toolName}-{d.Id}::file={FormatPath(d.file)},line={d.StartLine}::{d.Message}");
3434

3535
/// <summary>
3636
/// Log a notice from the status check to the console only
@@ -178,9 +178,9 @@ public async Task BuildCheckRunResult(string token, string owner, string repo, s
178178
// Once running on a branch on the dotnet org, this should work correctly.
179179
catch (ForbiddenException e)
180180
{
181-
Console.WriteLine("===== WARNING: Could not create a check run.=====");
182-
Console.WriteLine("Exception details:");
183-
Console.WriteLine(e);
181+
writer.WriteLine("===== WARNING: Could not create a check run.=====");
182+
writer.WriteLine("Exception details:");
183+
writer.WriteLine(e);
184184
}
185185
}
186186
}

0 commit comments

Comments
 (0)