Skip to content
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

Add status checks to the test runner #1179

Merged
merged 4 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/renumber-sections.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/test-examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ on:
jobs:
test-extraction-and-runner:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}

steps:
- name: Check out our repo
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/word-converter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
env:
DOTNET_NOLOGO: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,6 @@ test-grammar/

# don't checkin jar files:
*.jar

# don't checkin launchSettings:
**/launchSettings.json
1 change: 1 addition & 0 deletions tools/ExampleTester/ExampleTester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<ItemGroup>
<ProjectReference Include="..\ExampleExtractor\ExampleExtractor.csproj" />
<ProjectReference Include="..\Utilities\Utilities.csproj" />
</ItemGroup>

</Project>
35 changes: 17 additions & 18 deletions tools/ExampleTester/GeneratedExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Newtonsoft.Json;
using System.Reflection;
using System.Text;
using Utilities;

namespace ExampleTester;

Expand Down Expand Up @@ -33,9 +34,9 @@ private static GeneratedExample Load(string directory)
return new GeneratedExample(directory);
}

internal async Task<bool> Test(TesterConfiguration configuration)
internal async Task<bool> Test(TesterConfiguration configuration, StatusCheckLogger logger)
{
var outputLines = new List<string> { $"Testing {Metadata.Name} from {Metadata.Source}" };
logger.ConsoleOnlyLog(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $"Testing {Metadata.Name} from {Metadata.Source}", "ExampleTester"));

// Explicitly do a release build, to avoid implicitly defining DEBUG.
var properties = new Dictionary<string, string> { { "Configuration", "Release" } };
Expand All @@ -52,21 +53,16 @@ internal async Task<bool> Test(TesterConfiguration configuration)
}

bool ret = true;
ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors);
ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, Metadata.IgnoredWarnings);
ret &= ValidateDiagnostics("errors", DiagnosticSeverity.Error, Metadata.ExpectedErrors, logger);
ret &= ValidateDiagnostics("warnings", DiagnosticSeverity.Warning, Metadata.ExpectedWarnings, logger, Metadata.IgnoredWarnings);
// Don't try to validate output if we've already failed in terms of errors and warnings, or if we expect errors.
if (ret && Metadata.ExpectedErrors is null)
{
ret &= ValidateOutput();
}

if (!ret || !configuration.Quiet)
{
outputLines.ForEach(Console.WriteLine);
}
return ret;

bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string> expected, List<string>? ignored = null)
bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string> expected, StatusCheckLogger logger, List<string>? ignored = null)
{
expected ??= new List<string>();
ignored ??= new List<string>();
Expand All @@ -81,10 +77,12 @@ bool ValidateDiagnostics(string type, DiagnosticSeverity severity, List<string>
bool ret = ValidateExpectedAgainstActual(type, expected, actualIds);
if (!ret)
{
outputLines.Add($" Details of actual {type}:");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Details of actual {type}:", "ExampleTester"));
foreach (var diagnostic in actualDiagnostics)
{
outputLines.Add($" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine,
$" Line {diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1}: {diagnostic.Id}: {diagnostic.GetMessage()}",
"ExampleTester"));
}
}
return ret;
Expand All @@ -97,7 +95,7 @@ bool ValidateOutput()
{
if (Metadata.ExpectedOutput != null)
{
outputLines.Add(" Output expected, but project has no entry point.");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Output expected, but project has no entry point.", "ExampleTester"));
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
return true;
Expand All @@ -114,21 +112,21 @@ bool ValidateOutput()
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
outputLines.Add(" Failed to emit assembly");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, " Failed to emit assembly", "ExampleTester"));
return false;
}

var generatedAssembly = Assembly.Load(ms.ToArray());
var type = generatedAssembly.GetType(typeName);
if (type is null)
{
outputLines.Add($" Failed to find entry point type {typeName}");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point type {typeName}", "ExampleTester"));
return false;
}
var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (method is null)
{
outputLines.Add($" Failed to find entry point method {typeName}.{methodName}");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, $" Failed to find entry point method {typeName}.{methodName}", "ExampleTester"));
return false;
}
var arguments = method.GetParameters().Any()
Expand Down Expand Up @@ -197,7 +195,7 @@ bool MaybeReportError(bool result, string message)
{
if (!result)
{
outputLines.Add(message);
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine, message, "ExampleTester"));
}
return result;
}
Expand All @@ -207,7 +205,8 @@ bool ValidateExpectedAgainstActual(string type, List<string> expected, List<stri
{
if (!expected.SequenceEqual(actual))
{
outputLines.Add($" Mismatched {type}: Expected {string.Join(", ", expected)}; Was {string.Join(", ", actual)}");
logger.LogFailure(new StatusCheckMessage(Metadata.Source, Metadata.StartLine, Metadata.EndLine,
$" Mismatched {type}: Expected {string.Join(", ", expected)}; Was {string.Join(", ", actual)}", "ExampleTester"));
return false;
}
return true;
Expand Down
17 changes: 14 additions & 3 deletions tools/ExampleTester/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
using ExampleTester;
using System.CommandLine;
using Utilities;

var logger = new StatusCheckLogger("..", "Example tester");
var headSha = Environment.GetEnvironmentVariable("HEAD_SHA");
var token = Environment.GetEnvironmentVariable("GH_TOKEN");

var rootCommand = new RootCommand();
new TesterConfigurationBinder().ConfigureCommand(rootCommand, ExecuteAsync);
return rootCommand.Invoke(args);
int exitCode = rootCommand.Invoke(args);

if ((token is not null) && (headSha is not null))
{
await logger.BuildCheckRunResult(token, "dotnet", "csharpstandard", headSha);
}
return exitCode;

async Task<int> ExecuteAsync(TesterConfiguration configuration)
{
Expand Down Expand Up @@ -31,7 +42,7 @@ async Task<int> ExecuteAsync(TesterConfiguration configuration)
foreach (var example in examples)
{
// The Run method explains any failures, we just need to count them.
if (!await example.Test(configuration))
if (!await example.Test(configuration, logger))
{
failures++;
}
Expand All @@ -42,4 +53,4 @@ async Task<int> ExecuteAsync(TesterConfiguration configuration)
Console.WriteLine($"Failures: {failures}");

return failures;
}
}
4 changes: 2 additions & 2 deletions tools/MarkdownConverter/Spec/Reporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ public void Error(string code, string msg, SourceLocation? loc = null)
{
loc = loc ?? Location;
IncrementErrors();
githubLogger.LogFailure(new Diagnostic(loc.File ?? "mdspec2docx", loc.StartLine, loc.EndLine, msg, code));
githubLogger.LogFailure(new StatusCheckMessage(loc.File ?? "mdspec2docx", loc.StartLine, loc.EndLine, msg, code));
}

public void Warning(string code, string msg, SourceLocation? loc = null, int lineOffset = 0)
{
loc = loc ?? Location;
IncrementWarnings();
githubLogger.LogWarning(new Diagnostic(loc.File ?? "mdspec2docx", loc.StartLine+lineOffset, loc.EndLine+lineOffset, msg, code));
githubLogger.LogWarning(new StatusCheckMessage(loc.File ?? "mdspec2docx", loc.StartLine+lineOffset, loc.EndLine+lineOffset, msg, code));
}

public void Log(string code, string msg, SourceLocation? loc = null)
Expand Down
2 changes: 1 addition & 1 deletion tools/StandardAnchorTags/ReferenceUpdateProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private string ProcessSectionLinks(string line, int lineNumber, string path)
if ((referenceText.Length > 1) &&
(!linkMap.ContainsKey(referenceText)))
{
var diagnostic = new Diagnostic(path, lineNumber, lineNumber, $"`{referenceText}` not found", DiagnosticIDs.TOC002);
var diagnostic = new StatusCheckMessage(path, lineNumber, lineNumber, $"`{referenceText}` not found", DiagnosticIDs.TOC002);
logger.LogFailure(diagnostic);
} else
{
Expand Down
2 changes: 1 addition & 1 deletion tools/StandardAnchorTags/TocSectionNumberBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public async Task AddFrontMatterTocEntries(string fileName)
return;
}
// Getting here means this file doesn't have an H1. That's an error:
var diagnostic = new Diagnostic(path, 1, 1, "File doesn't have an H1 tag as its first line.", DiagnosticIDs.TOC001);
var diagnostic = new StatusCheckMessage(path, 1, 1, "File doesn't have an H1 tag as its first line.", DiagnosticIDs.TOC001);
logger.LogFailure(diagnostic);
}

Expand Down
31 changes: 22 additions & 9 deletions tools/Utilities/StatusCheckLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Utilities;
/// <param name="Id">The error message ID</param>
/// <param name="StartLine">The start line (index from 1)</param>
/// <param name="EndLine">The end line (index from 1)</param>
public record Diagnostic(string file, int StartLine, int EndLine, string Message, string Id);
public record StatusCheckMessage(string file, int StartLine, int EndLine, string Message, string Id);
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// This class writes the status of the check to the console in the format GitHub supports
Expand All @@ -30,7 +30,18 @@ public class StatusCheckLogger(string pathToRoot, string toolName)
// Utility method to format the path to unix style, from the root of the repository.
private string FormatPath(string path) => Path.GetRelativePath(pathToRoot, path).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

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

/// <summary>
/// Log a notice from the status check to the console only
/// </summary>
/// <param name="d">The diagnostic</param>
/// <remarks>
/// Log the diagnostic information to console. These will only appear in the console window, not
/// as annotations on the changes in the PR.
/// </remarks>
public void ConsoleOnlyLog(StatusCheckMessage d) =>
WriteMessageToConsole("", d);

/// <summary>
/// Log a notice from the status check
Expand All @@ -40,7 +51,7 @@ public class StatusCheckLogger(string pathToRoot, string toolName)
/// Add the diagnostic to the annotation list and
/// log the diagnostic information to console.
/// </remarks>
public void LogNotice(Diagnostic d)
public void LogNotice(StatusCheckMessage d)
{
WriteMessageToConsole("", d);
annotations.Add(
Expand All @@ -59,7 +70,7 @@ public void LogNotice(Diagnostic d)
/// log the warning notice to the console.
/// Warnings are logged, but the process reports "success" to GitHub.
/// </remarks>
public void LogWarning(Diagnostic d)
public void LogWarning(StatusCheckMessage d)
{
WriteMessageToConsole("⚠️", d);
annotations.Add(
Expand All @@ -76,11 +87,11 @@ public void LogWarning(Diagnostic d)
/// <remarks>
/// Add the diagnostic to the annotation list and
/// log the failure notice to the console.
/// This method is distinct from <see cref="ExitOnFailure(Diagnostic)"/> in
/// This method is distinct from <see cref="ExitOnFailure(StatusCheckMessage)"/> in
/// that this method does not throw an exception. Its purpose is to log
/// the failure but allow the tool to continue running further checks.
/// </remarks>
public void LogFailure(Diagnostic d)
public void LogFailure(StatusCheckMessage d)
{
WriteMessageToConsole("❌", d);
annotations.Add(
Expand All @@ -98,11 +109,11 @@ public void LogFailure(Diagnostic d)
/// <remarks>
/// Add the diagnostic to the annotation list and
/// log the failure notice to the console.
/// This method is distinct from <see cref="LogFailure(Diagnostic)"/> in
/// This method is distinct from <see cref="LogFailure(StatusCheckMessage)"/> in
/// that this method throws an exception. Its purpose is to log
/// the failure and immediately exit, foregoing any further checks.
/// </remarks>
public void ExitOnFailure(Diagnostic d)
public void ExitOnFailure(StatusCheckMessage d)
{
LogFailure(d);
throw new InvalidOperationException(d.Message);
Expand Down Expand Up @@ -138,9 +149,11 @@ public async Task BuildCheckRunResult(string token, string owner, string repo, s
}
// If the token does not have the correct permissions, we will get a 403
// Once running on a branch on the dotnet org, this should work correctly.
catch (ForbiddenException)
catch (ForbiddenException e)
{
Console.WriteLine("===== WARNING: Could not create a check run.=====");
Console.WriteLine("Exception details:");
Console.WriteLine(e);
}
}
}
3 changes: 3 additions & 0 deletions tools/tools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleExtractor", "ExampleExtractor\ExampleExtractor.csproj", "{571E69B9-07A3-4682-B692-876B016315CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleTester", "ExampleTester\ExampleTester.csproj", "{829FE7D6-B7E7-48DF-923A-73A79921E997}"
ProjectSection(ProjectDependencies) = postProject
{835C6333-BDB5-4DEC-B3BE-4300E3F948AF} = {835C6333-BDB5-4DEC-B3BE-4300E3F948AF}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleFormatter", "ExampleFormatter\ExampleFormatter.csproj", "{82D1A159-5637-48C4-845D-CC1390995CC2}"
EndProject
Expand Down
Loading