From 3c646c601008d7fcb0f4f49c25ba7b6d78a71337 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:01:09 +0200 Subject: [PATCH 1/6] Use file-scoped namespaces --- src/Exercism.TestRunner.CSharp/Options.cs | 37 ++- src/Exercism.TestRunner.CSharp/Program.cs | 29 +- .../TestResultParser.cs | 251 +++++++++--------- src/Exercism.TestRunner.CSharp/TestRun.cs | 69 +++-- .../TestRunParser.cs | 103 ++++--- .../TestRunWriter.cs | 31 ++- src/Exercism.TestRunner.CSharp/TestSuite.cs | 147 +++++----- .../TestsRewriter.cs | 29 +- 8 files changed, 344 insertions(+), 352 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/Options.cs b/src/Exercism.TestRunner.CSharp/Options.cs index 62a449f7..59954c4c 100644 --- a/src/Exercism.TestRunner.CSharp/Options.cs +++ b/src/Exercism.TestRunner.CSharp/Options.cs @@ -2,34 +2,33 @@ using Humanizer; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal class Options { - internal class Options - { - [Value(0, Required = true, HelpText = "The solution's exercise")] - public string Slug { get; } + [Value(0, Required = true, HelpText = "The solution's exercise")] + public string Slug { get; } - [Value(1, Required = true, HelpText = "The directory containing the solution")] - public string InputDirectory { get; } + [Value(1, Required = true, HelpText = "The directory containing the solution")] + public string InputDirectory { get; } - [Value(2, Required = true, HelpText = "The directory to which the results will be written")] - public string OutputDirectory { get; } + [Value(2, Required = true, HelpText = "The directory to which the results will be written")] + public string OutputDirectory { get; } - public Options(string slug, string inputDirectory, string outputDirectory) => - (Slug, InputDirectory, OutputDirectory) = (slug, inputDirectory, outputDirectory); + public Options(string slug, string inputDirectory, string outputDirectory) => + (Slug, InputDirectory, OutputDirectory) = (slug, inputDirectory, outputDirectory); - public string TestsFilePath => Path.Combine(InputDirectory, $"{Exercise}Tests.cs"); + public string TestsFilePath => Path.Combine(InputDirectory, $"{Exercise}Tests.cs"); - public string ProjectFilePath => Path.Combine(InputDirectory, $"{Exercise}.csproj"); + public string ProjectFilePath => Path.Combine(InputDirectory, $"{Exercise}.csproj"); - public string AssemblyInfoFilePath => Path.Combine(InputDirectory, "AssemblyInfo.cs"); + public string AssemblyInfoFilePath => Path.Combine(InputDirectory, "AssemblyInfo.cs"); - public string BuildLogFilePath => Path.Combine(InputDirectory, "msbuild.log"); + public string BuildLogFilePath => Path.Combine(InputDirectory, "msbuild.log"); - public string TestResultsFilePath => Path.Combine(InputDirectory, "TestResults", "tests.trx"); + public string TestResultsFilePath => Path.Combine(InputDirectory, "TestResults", "tests.trx"); - public string ResultsJsonFilePath => Path.GetFullPath(Path.Combine(OutputDirectory, "results.json")); + public string ResultsJsonFilePath => Path.GetFullPath(Path.Combine(OutputDirectory, "results.json")); - private string Exercise => Slug.Dehumanize().Pascalize(); - } + private string Exercise => Slug.Dehumanize().Pascalize(); } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/Program.cs b/src/Exercism.TestRunner.CSharp/Program.cs index 6b9bf32c..3b7bd3ec 100644 --- a/src/Exercism.TestRunner.CSharp/Program.cs +++ b/src/Exercism.TestRunner.CSharp/Program.cs @@ -1,23 +1,22 @@ using CommandLine; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +public static class Program { - public static class Program - { - public static void Main(string[] args) => - Parser.Default - .ParseArguments(args) - .WithParsed(CreateTestResults); + public static void Main(string[] args) => + Parser.Default + .ParseArguments(args) + .WithParsed(CreateTestResults); - private static void CreateTestResults(Options options) - { - Console.WriteLine($"[{DateTimeOffset.UtcNow:u}] Running test runner for '{options.Slug}' solution..."); + private static void CreateTestResults(Options options) + { + Console.WriteLine($"[{DateTimeOffset.UtcNow:u}] Running test runner for '{options.Slug}' solution..."); - var testSuite = TestSuite.FromOptions(options); - var testRun = testSuite.Run(); - testRun.WriteToFile(options.ResultsJsonFilePath); + var testSuite = TestSuite.FromOptions(options); + var testRun = testSuite.Run(); + testRun.WriteToFile(options.ResultsJsonFilePath); - Console.WriteLine($"[{DateTimeOffset.UtcNow:u}] Ran test runner for '{options.Slug}' solution"); - } + Console.WriteLine($"[{DateTimeOffset.UtcNow:u}] Ran test runner for '{options.Slug}' solution"); } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestResultParser.cs b/src/Exercism.TestRunner.CSharp/TestResultParser.cs index 794c2fff..727c9e28 100644 --- a/src/Exercism.TestRunner.CSharp/TestResultParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestResultParser.cs @@ -6,148 +6,147 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal static class TestResultParser { - internal static class TestResultParser + internal static TestResult[] FromFile(string logFilePath, SyntaxTree testsSyntaxTree) { - internal static TestResult[] FromFile(string logFilePath, SyntaxTree testsSyntaxTree) - { - using var fileStream = File.OpenRead(logFilePath); - var result = (XmlTestRun)new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream); + using var fileStream = File.OpenRead(logFilePath); + var result = (XmlTestRun)new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream); - if (result.Results == null) - return Array.Empty(); + if (result.Results == null) + return Array.Empty(); - return result.ToTestResults(testsSyntaxTree); - } - - private static TestResult[] ToTestResults(this XmlTestRun result, SyntaxTree testsSyntaxTree) - { - var methodDeclarations = - testsSyntaxTree - .GetRoot() - .DescendantNodes() - .OfType() - .ToArray(); - - var testResults = - from unitTestResult in result.Results.UnitTestResult - let testMethodDeclaration = unitTestResult.TestMethod(methodDeclarations) - orderby testMethodDeclaration.GetLocation().GetLineSpan().StartLinePosition.Line - select ToTestResult(unitTestResult, testMethodDeclaration); - - return testResults.ToArray(); - } - - private static TestResult ToTestResult(XmlUnitTestResult xmlUnitTestResult, MethodDeclarationSyntax testMethodDeclaration) => - new() - { - Name = xmlUnitTestResult.Name(), - Status = xmlUnitTestResult.Status(), - Message = xmlUnitTestResult.Message(), - Output = xmlUnitTestResult.Output(), - TaskId = testMethodDeclaration.TaskId(), - TestCode = testMethodDeclaration.TestCode() - }; - - private static MethodDeclarationSyntax TestMethod(this XmlUnitTestResult xmlUnitTestResult, IEnumerable methodDeclarations) - { - var classAndMethodName = xmlUnitTestResult.TestName.Split("."); - var className = classAndMethodName[0]; - var methodName = classAndMethodName[1].Split('(')[0]; - - return methodDeclarations.Single(method => - method.Identifier.Text == methodName && - method.Parent is ClassDeclarationSyntax classDeclaration && - classDeclaration.Identifier.Text == className); - } - - private static string Name(this XmlUnitTestResult xmlUnitTestResult) => - xmlUnitTestResult.TestName - .Substring(xmlUnitTestResult.TestName.LastIndexOf(".", StringComparison.Ordinal) + 1) - .Humanize(); - - private static TestStatus Status(this XmlUnitTestResult xmlUnitTestResult) => - xmlUnitTestResult.Outcome switch - { - "Passed" => TestStatus.Pass, - "Failed" => TestStatus.Fail, - _ => TestStatus.Error - }; - - private static string Message(this XmlUnitTestResult xmlUnitTestResult) => - xmlUnitTestResult.Output?.ErrorInfo?.Message?.UseUnixNewlines()?.Trim(); - - private static string Output(this XmlUnitTestResult xmlUnitTestResult) => - xmlUnitTestResult.Output?.StdOut?.UseUnixNewlines()?.Trim(); - - private static string TestCode(this MethodDeclarationSyntax testMethod) - { - if (testMethod.Body != null) - return SyntaxFactory.List(testMethod.Body.Statements.Select(statement => statement.WithoutLeadingTrivia())).ToString(); - - return testMethod.ExpressionBody! - .Expression - .WithoutLeadingTrivia() - .ToString(); - } - - private static int? TaskId(this MethodDeclarationSyntax testMethod) => - testMethod.AttributeLists - .SelectMany(attributeList => attributeList.Attributes) - .Where(attribute => - attribute.Name.ToString() == "Task" && - attribute.ArgumentList != null && - attribute.ArgumentList.Arguments.Count == 1 && - attribute.ArgumentList.Arguments[0].Expression.IsKind(SyntaxKind.NumericLiteralExpression)) - .Select(attribute => (LiteralExpressionSyntax)attribute.ArgumentList.Arguments[0].Expression) - .Select(taskNumberExpression => (int?)taskNumberExpression.Token.Value!) - .FirstOrDefault(); + return result.ToTestResults(testsSyntaxTree); } - [XmlRoot(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public class XmlOutput + private static TestResult[] ToTestResults(this XmlTestRun result, SyntaxTree testsSyntaxTree) { - [XmlElement(ElementName = "StdOut", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public string StdOut { get; set; } + var methodDeclarations = + testsSyntaxTree + .GetRoot() + .DescendantNodes() + .OfType() + .ToArray(); + + var testResults = + from unitTestResult in result.Results.UnitTestResult + let testMethodDeclaration = unitTestResult.TestMethod(methodDeclarations) + orderby testMethodDeclaration.GetLocation().GetLineSpan().StartLinePosition.Line + select ToTestResult(unitTestResult, testMethodDeclaration); - [XmlElement(ElementName = "ErrorInfo", - Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlErrorInfo ErrorInfo { get; set; } + return testResults.ToArray(); } - [XmlRoot(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public class XmlUnitTestResult + private static TestResult ToTestResult(XmlUnitTestResult xmlUnitTestResult, MethodDeclarationSyntax testMethodDeclaration) => + new() + { + Name = xmlUnitTestResult.Name(), + Status = xmlUnitTestResult.Status(), + Message = xmlUnitTestResult.Message(), + Output = xmlUnitTestResult.Output(), + TaskId = testMethodDeclaration.TaskId(), + TestCode = testMethodDeclaration.TestCode() + }; + + private static MethodDeclarationSyntax TestMethod(this XmlUnitTestResult xmlUnitTestResult, IEnumerable methodDeclarations) { - [XmlElement(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlOutput Output { get; set; } + var classAndMethodName = xmlUnitTestResult.TestName.Split("."); + var className = classAndMethodName[0]; + var methodName = classAndMethodName[1].Split('(')[0]; + + return methodDeclarations.Single(method => + method.Identifier.Text == methodName && + method.Parent is ClassDeclarationSyntax classDeclaration && + classDeclaration.Identifier.Text == className); + } - [XmlAttribute(AttributeName = "testName")] - public string TestName { get; set; } + private static string Name(this XmlUnitTestResult xmlUnitTestResult) => + xmlUnitTestResult.TestName + .Substring(xmlUnitTestResult.TestName.LastIndexOf(".", StringComparison.Ordinal) + 1) + .Humanize(); - [XmlAttribute(AttributeName = "outcome")] - public string Outcome { get; set; } - } + private static TestStatus Status(this XmlUnitTestResult xmlUnitTestResult) => + xmlUnitTestResult.Outcome switch + { + "Passed" => TestStatus.Pass, + "Failed" => TestStatus.Fail, + _ => TestStatus.Error + }; - [XmlRoot(ElementName = "ErrorInfo", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public class XmlErrorInfo - { - [XmlElement(ElementName = "Message", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public string Message { get; set; } - } + private static string Message(this XmlUnitTestResult xmlUnitTestResult) => + xmlUnitTestResult.Output?.ErrorInfo?.Message?.UseUnixNewlines()?.Trim(); - [XmlRoot(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public class XmlResults - { - [XmlElement(ElementName = "UnitTestResult", - Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public List UnitTestResult { get; set; } - } + private static string Output(this XmlUnitTestResult xmlUnitTestResult) => + xmlUnitTestResult.Output?.StdOut?.UseUnixNewlines()?.Trim(); - [XmlRoot(ElementName = "TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public class XmlTestRun + private static string TestCode(this MethodDeclarationSyntax testMethod) { - [XmlElement(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlResults Results { get; set; } + if (testMethod.Body != null) + return SyntaxFactory.List(testMethod.Body.Statements.Select(statement => statement.WithoutLeadingTrivia())).ToString(); + + return testMethod.ExpressionBody! + .Expression + .WithoutLeadingTrivia() + .ToString(); } + + private static int? TaskId(this MethodDeclarationSyntax testMethod) => + testMethod.AttributeLists + .SelectMany(attributeList => attributeList.Attributes) + .Where(attribute => + attribute.Name.ToString() == "Task" && + attribute.ArgumentList != null && + attribute.ArgumentList.Arguments.Count == 1 && + attribute.ArgumentList.Arguments[0].Expression.IsKind(SyntaxKind.NumericLiteralExpression)) + .Select(attribute => (LiteralExpressionSyntax)attribute.ArgumentList.Arguments[0].Expression) + .Select(taskNumberExpression => (int?)taskNumberExpression.Token.Value!) + .FirstOrDefault(); +} + +[XmlRoot(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class XmlOutput +{ + [XmlElement(ElementName = "StdOut", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public string StdOut { get; set; } + + [XmlElement(ElementName = "ErrorInfo", + Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public XmlErrorInfo ErrorInfo { get; set; } +} + +[XmlRoot(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class XmlUnitTestResult +{ + [XmlElement(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public XmlOutput Output { get; set; } + + [XmlAttribute(AttributeName = "testName")] + public string TestName { get; set; } + + [XmlAttribute(AttributeName = "outcome")] + public string Outcome { get; set; } +} + +[XmlRoot(ElementName = "ErrorInfo", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class XmlErrorInfo +{ + [XmlElement(ElementName = "Message", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public string Message { get; set; } +} + +[XmlRoot(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class XmlResults +{ + [XmlElement(ElementName = "UnitTestResult", + Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public List UnitTestResult { get; set; } +} + +[XmlRoot(ElementName = "TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class XmlTestRun +{ + [XmlElement(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public XmlResults Results { get; set; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestRun.cs b/src/Exercism.TestRunner.CSharp/TestRun.cs index 3a6710a3..74fa120e 100644 --- a/src/Exercism.TestRunner.CSharp/TestRun.cs +++ b/src/Exercism.TestRunner.CSharp/TestRun.cs @@ -1,47 +1,46 @@ using System.Text.Json.Serialization; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal enum TestStatus +{ + Pass, + Fail, + Error +} + +internal class TestResult { - internal enum TestStatus - { - Pass, - Fail, - Error - } - - internal class TestResult - { - [JsonPropertyName("name")] - public string Name { get; set; } - - [JsonPropertyName("status")] - public TestStatus Status { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("status")] + public TestStatus Status { get; set; } - [JsonPropertyName("task_id")] - public int? TaskId { get; set; } + [JsonPropertyName("task_id")] + public int? TaskId { get; set; } - [JsonPropertyName("message")] - public string Message { get; set; } + [JsonPropertyName("message")] + public string Message { get; set; } - [JsonPropertyName("output")] - public string Output { get; set; } + [JsonPropertyName("output")] + public string Output { get; set; } - [JsonPropertyName("test_code")] - public string TestCode { get; set; } - } + [JsonPropertyName("test_code")] + public string TestCode { get; set; } +} - internal class TestRun - { - [JsonPropertyName("version")] - public int Version { get; set; } = 3; +internal class TestRun +{ + [JsonPropertyName("version")] + public int Version { get; set; } = 3; - [JsonPropertyName("status")] - public TestStatus Status { get; set; } + [JsonPropertyName("status")] + public TestStatus Status { get; set; } - [JsonPropertyName("message")] - public string Message { get; set; } + [JsonPropertyName("message")] + public string Message { get; set; } - [JsonPropertyName("tests")] - public TestResult[] Tests { get; set; } - } + [JsonPropertyName("tests")] + public TestResult[] Tests { get; set; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestRunParser.cs b/src/Exercism.TestRunner.CSharp/TestRunParser.cs index bf251fa6..6120b264 100644 --- a/src/Exercism.TestRunner.CSharp/TestRunParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestRunParser.cs @@ -1,72 +1,71 @@ using Microsoft.CodeAnalysis; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal static class TestRunParser { - internal static class TestRunParser + public static TestRun Parse(Options options, SyntaxTree testsSyntaxTree) { - public static TestRun Parse(Options options, SyntaxTree testsSyntaxTree) - { - var logLines = File.ReadLines(options.BuildLogFilePath); - var buildFailed = logLines.Any(); - - if (buildFailed) - { - return TestRunWithError(logLines); - } + var logLines = File.ReadLines(options.BuildLogFilePath); + var buildFailed = logLines.Any(); - return TestRunWithoutError(options, testsSyntaxTree); + if (buildFailed) + { + return TestRunWithError(logLines); } - private static TestRun TestRunWithoutError(Options options, SyntaxTree testsSyntaxTree) - { - var testResults = TestResultParser.FromFile(options.TestResultsFilePath, testsSyntaxTree); + return TestRunWithoutError(options, testsSyntaxTree); + } - return new TestRun - { - Status = testResults.ToTestStatus(), - Tests = testResults - }; - } + private static TestRun TestRunWithoutError(Options options, SyntaxTree testsSyntaxTree) + { + var testResults = TestResultParser.FromFile(options.TestResultsFilePath, testsSyntaxTree); - private static TestStatus ToTestStatus(this TestResult[] tests) + return new TestRun { - if (tests.Any(test => test.Status == TestStatus.Fail)) - return TestStatus.Fail; - - if (tests.All(test => test.Status == TestStatus.Pass) && tests.Any()) - return TestStatus.Pass; + Status = testResults.ToTestStatus(), + Tests = testResults + }; + } - return TestStatus.Error; - } + private static TestStatus ToTestStatus(this TestResult[] tests) + { + if (tests.Any(test => test.Status == TestStatus.Fail)) + return TestStatus.Fail; - private static TestRun TestRunWithError(IEnumerable logLines) => - new TestRun - { - Message = string.Join("\n", logLines.Select(NormalizeLogLine)), - Status = TestStatus.Error, - Tests = Array.Empty() - }; + if (tests.All(test => test.Status == TestStatus.Pass) && tests.Any()) + return TestStatus.Pass; - private static string NormalizeLogLine(this string logLine) => - logLine.RemoveProjectReference().RemovePath().UseUnixNewlines().Trim(); + return TestStatus.Error; + } - private static string RemoveProjectReference(this string logLine) + private static TestRun TestRunWithError(IEnumerable logLines) => + new TestRun { - var bracketIndex = logLine.LastIndexOf('['); - return bracketIndex == -1 ? logLine : logLine[..(bracketIndex - 1)]; - } + Message = string.Join("\n", logLines.Select(NormalizeLogLine)), + Status = TestStatus.Error, + Tests = Array.Empty() + }; - private static string RemovePath(this string logLine) - { - var testFileIndex = logLine.IndexOf(".cs(", StringComparison.Ordinal); - if (testFileIndex == -1) - return logLine; + private static string NormalizeLogLine(this string logLine) => + logLine.RemoveProjectReference().RemovePath().UseUnixNewlines().Trim(); + + private static string RemoveProjectReference(this string logLine) + { + var bracketIndex = logLine.LastIndexOf('['); + return bracketIndex == -1 ? logLine : logLine[..(bracketIndex - 1)]; + } - var lastDirectorySeparatorIndex = logLine.LastIndexOf(Path.DirectorySeparatorChar, testFileIndex); - if (lastDirectorySeparatorIndex == -1) - return logLine; + private static string RemovePath(this string logLine) + { + var testFileIndex = logLine.IndexOf(".cs(", StringComparison.Ordinal); + if (testFileIndex == -1) + return logLine; - return logLine.Substring(lastDirectorySeparatorIndex + 1); - } + var lastDirectorySeparatorIndex = logLine.LastIndexOf(Path.DirectorySeparatorChar, testFileIndex); + if (lastDirectorySeparatorIndex == -1) + return logLine; + + return logLine.Substring(lastDirectorySeparatorIndex + 1); } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestRunWriter.cs b/src/Exercism.TestRunner.CSharp/TestRunWriter.cs index 48ac9a9f..f4a25a37 100644 --- a/src/Exercism.TestRunner.CSharp/TestRunWriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestRunWriter.cs @@ -1,26 +1,25 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal static class TestRunWriter { - internal static class TestRunWriter - { - public static void WriteToFile(this TestRun testRun, string resultsJsonFilePath) => - File.WriteAllText(resultsJsonFilePath, testRun.ToJson()); + public static void WriteToFile(this TestRun testRun, string resultsJsonFilePath) => + File.WriteAllText(resultsJsonFilePath, testRun.ToJson()); - private static string ToJson(this TestRun testRun) => - JsonSerializer.Serialize(testRun, CreateJsonSerializerOptions()).TrimEnd() + Environment.NewLine; + private static string ToJson(this TestRun testRun) => + JsonSerializer.Serialize(testRun, CreateJsonSerializerOptions()).TrimEnd() + Environment.NewLine; - private static JsonSerializerOptions CreateJsonSerializerOptions() + private static JsonSerializerOptions CreateJsonSerializerOptions() + { + var options = new JsonSerializerOptions { - var options = new JsonSerializerOptions - { - IgnoreNullValues = true, - WriteIndented = true, - }; - options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + IgnoreNullValues = true, + WriteIndented = true, + }; + options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); - return options; - } + return options; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestSuite.cs b/src/Exercism.TestRunner.CSharp/TestSuite.cs index 9592892e..5b3003b0 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -3,96 +3,95 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal class TestSuite { - internal class TestSuite - { - private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; + private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; - private readonly SyntaxTree _originalSyntaxTree; - private readonly string _originalProjectFile; - private readonly Options _options; + private readonly SyntaxTree _originalSyntaxTree; + private readonly string _originalProjectFile; + private readonly Options _options; - private TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) - { - _originalSyntaxTree = originalSyntaxTree; - _originalProjectFile = originalProjectFile; - _options = options; - } + private TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) + { + _originalSyntaxTree = originalSyntaxTree; + _originalProjectFile = originalProjectFile; + _options = options; + } - public TestRun Run() - { - BeforeTests(); - RunTests(); - AfterTests(); + public TestRun Run() + { + BeforeTests(); + RunTests(); + AfterTests(); - return TestRunParser.Parse(_options, _originalSyntaxTree); - } + return TestRunParser.Parse(_options, _originalSyntaxTree); + } - private void RunTests() - { - var workingDirectory = Path.GetDirectoryName(_options.TestsFilePath)!; - RunProcess("dotnet", "restore --source /root/.nuget/packages/", workingDirectory); - RunProcess("dotnet", $"test -c release --no-restore --verbosity=quiet --logger \"trx;LogFileName={Path.GetFileName(_options.TestResultsFilePath)}\" /flp:verbosity=quiet;errorsOnly=true", workingDirectory); - } + private void RunTests() + { + var workingDirectory = Path.GetDirectoryName(_options.TestsFilePath)!; + RunProcess("dotnet", "restore --source /root/.nuget/packages/", workingDirectory); + RunProcess("dotnet", $"test -c release --no-restore --verbosity=quiet --logger \"trx;LogFileName={Path.GetFileName(_options.TestResultsFilePath)}\" /flp:verbosity=quiet;errorsOnly=true", workingDirectory); + } - private static void RunProcess(string command, string arguments, string workingDirectory) - { - var processStartInfo = new ProcessStartInfo(command, arguments) - { - WorkingDirectory = workingDirectory, - RedirectStandardInput = true, - RedirectStandardError = true, - RedirectStandardOutput = true - }; - Process.Start(processStartInfo)?.WaitForExit(); - } - - private void BeforeTests() + private static void RunProcess(string command, string arguments, string workingDirectory) + { + var processStartInfo = new ProcessStartInfo(command, arguments) { - RewriteProjectFile(); - RewriteTestsFile(); + WorkingDirectory = workingDirectory, + RedirectStandardInput = true, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + Process.Start(processStartInfo)?.WaitForExit(); + } + + private void BeforeTests() + { + RewriteProjectFile(); + RewriteTestsFile(); - if (CaptureOutput) - AddCaptureOuputAssemblyAttributes(); - } - - private void RewriteProjectFile() => - File.WriteAllText(_options.ProjectFilePath, - _originalProjectFile - .Replace("net5.0", "net9.0") - .Replace("net6.0", "net9.0") - .Replace("net7.0", "net9.0") - .Replace("net8.0", "net9.0")); - - private void RewriteTestsFile() => - File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.Rewrite().ToString()); + if (CaptureOutput) + AddCaptureOuputAssemblyAttributes(); + } + + private void RewriteProjectFile() => + File.WriteAllText(_options.ProjectFilePath, + _originalProjectFile + .Replace("net5.0", "net9.0") + .Replace("net6.0", "net9.0") + .Replace("net7.0", "net9.0") + .Replace("net8.0", "net9.0")); + + private void RewriteTestsFile() => + File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.Rewrite().ToString()); - private void AddCaptureOuputAssemblyAttributes() => - File.WriteAllText(_options.AssemblyInfoFilePath, AssemblyInfo); + private void AddCaptureOuputAssemblyAttributes() => + File.WriteAllText(_options.AssemblyInfoFilePath, AssemblyInfo); - private void AfterTests() - { - UndoRewriteProjectFile(); - UndoRewriteTestsFile(); + private void AfterTests() + { + UndoRewriteProjectFile(); + UndoRewriteTestsFile(); - if (CaptureOutput) - UndoAddCaptureOuputAssemblyAttributes(); - } + if (CaptureOutput) + UndoAddCaptureOuputAssemblyAttributes(); + } - private void UndoRewriteProjectFile() => File.WriteAllText(_options.ProjectFilePath, _originalProjectFile); + private void UndoRewriteProjectFile() => File.WriteAllText(_options.ProjectFilePath, _originalProjectFile); - private void UndoRewriteTestsFile() => File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.ToString()); + private void UndoRewriteTestsFile() => File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.ToString()); - private void UndoAddCaptureOuputAssemblyAttributes() => File.Delete(_options.AssemblyInfoFilePath); + private void UndoAddCaptureOuputAssemblyAttributes() => File.Delete(_options.AssemblyInfoFilePath); - private bool CaptureOutput => _originalProjectFile.Contains("xunit.v3"); + private bool CaptureOutput => _originalProjectFile.Contains("xunit.v3"); - public static TestSuite FromOptions(Options options) - { - var originalSyntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(options.TestsFilePath)); - var originalProjectFile = File.ReadAllText(options.ProjectFilePath); - return new TestSuite(originalSyntaxTree, originalProjectFile, options); - } + public static TestSuite FromOptions(Options options) + { + var originalSyntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(options.TestsFilePath)); + var originalProjectFile = File.ReadAllText(options.ProjectFilePath); + return new TestSuite(originalSyntaxTree, originalProjectFile, options); } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs index 1783a6db..181d2c72 100644 --- a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs @@ -2,23 +2,22 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal static class TestsRewriter { - internal static class TestsRewriter - { - public static SyntaxTree Rewrite(this SyntaxTree tree) => - tree.WithRootAndOptions(tree.GetRoot().Rewrite(), tree.Options); + public static SyntaxTree Rewrite(this SyntaxTree tree) => + tree.WithRootAndOptions(tree.GetRoot().Rewrite(), tree.Options); - private static SyntaxNode Rewrite(this SyntaxNode node) => - node.UnskipTests().NormalizeWhitespace(); + private static SyntaxNode Rewrite(this SyntaxNode node) => + node.UnskipTests().NormalizeWhitespace(); - private static SyntaxNode UnskipTests(this SyntaxNode testsRoot) => - new UnskipTestsRewriter().Visit(testsRoot); + private static SyntaxNode UnskipTests(this SyntaxNode testsRoot) => + new UnskipTestsRewriter().Visit(testsRoot); - private class UnskipTestsRewriter : CSharpSyntaxRewriter - { - public override SyntaxNode? VisitAttributeArgument(AttributeArgumentSyntax node) => - node.NameEquals?.Name.Identifier.Text == "Skip" ? null : base.VisitAttributeArgument(node); - } + private class UnskipTestsRewriter : CSharpSyntaxRewriter + { + public override SyntaxNode? VisitAttributeArgument(AttributeArgumentSyntax node) => + node.NameEquals?.Name.Identifier.Text == "Skip" ? null : base.VisitAttributeArgument(node); } -} +} \ No newline at end of file From 78160b22aadae2882747a0c67cf7d57e6b41668f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:02:33 +0200 Subject: [PATCH 2/6] Use sealed class --- src/Exercism.TestRunner.CSharp/Options.cs | 2 +- src/Exercism.TestRunner.CSharp/TestResultParser.cs | 10 +++++----- src/Exercism.TestRunner.CSharp/TestRun.cs | 4 ++-- src/Exercism.TestRunner.CSharp/TestSuite.cs | 2 +- src/Exercism.TestRunner.CSharp/TestsRewriter.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/Options.cs b/src/Exercism.TestRunner.CSharp/Options.cs index 59954c4c..5e490eae 100644 --- a/src/Exercism.TestRunner.CSharp/Options.cs +++ b/src/Exercism.TestRunner.CSharp/Options.cs @@ -4,7 +4,7 @@ namespace Exercism.TestRunner.CSharp; -internal class Options +internal sealed class Options { [Value(0, Required = true, HelpText = "The solution's exercise")] public string Slug { get; } diff --git a/src/Exercism.TestRunner.CSharp/TestResultParser.cs b/src/Exercism.TestRunner.CSharp/TestResultParser.cs index 727c9e28..c4d6055b 100644 --- a/src/Exercism.TestRunner.CSharp/TestResultParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestResultParser.cs @@ -106,7 +106,7 @@ private static string TestCode(this MethodDeclarationSyntax testMethod) } [XmlRoot(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] -public class XmlOutput +public sealed class XmlOutput { [XmlElement(ElementName = "StdOut", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public string StdOut { get; set; } @@ -117,7 +117,7 @@ public class XmlOutput } [XmlRoot(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] -public class XmlUnitTestResult +public sealed class XmlUnitTestResult { [XmlElement(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public XmlOutput Output { get; set; } @@ -130,14 +130,14 @@ public class XmlUnitTestResult } [XmlRoot(ElementName = "ErrorInfo", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] -public class XmlErrorInfo +public sealed class XmlErrorInfo { [XmlElement(ElementName = "Message", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public string Message { get; set; } } [XmlRoot(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] -public class XmlResults +public sealed class XmlResults { [XmlElement(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] @@ -145,7 +145,7 @@ public class XmlResults } [XmlRoot(ElementName = "TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] -public class XmlTestRun +public sealed class XmlTestRun { [XmlElement(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public XmlResults Results { get; set; } diff --git a/src/Exercism.TestRunner.CSharp/TestRun.cs b/src/Exercism.TestRunner.CSharp/TestRun.cs index 74fa120e..d45253d5 100644 --- a/src/Exercism.TestRunner.CSharp/TestRun.cs +++ b/src/Exercism.TestRunner.CSharp/TestRun.cs @@ -9,7 +9,7 @@ internal enum TestStatus Error } -internal class TestResult +internal sealed class TestResult { [JsonPropertyName("name")] public string Name { get; set; } @@ -30,7 +30,7 @@ internal class TestResult public string TestCode { get; set; } } -internal class TestRun +internal sealed class TestRun { [JsonPropertyName("version")] public int Version { get; set; } = 3; diff --git a/src/Exercism.TestRunner.CSharp/TestSuite.cs b/src/Exercism.TestRunner.CSharp/TestSuite.cs index 5b3003b0..933fcd79 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -5,7 +5,7 @@ namespace Exercism.TestRunner.CSharp; -internal class TestSuite +internal sealed class TestSuite { private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; diff --git a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs index 181d2c72..6e999e24 100644 --- a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs @@ -15,7 +15,7 @@ private static SyntaxNode Rewrite(this SyntaxNode node) => private static SyntaxNode UnskipTests(this SyntaxNode testsRoot) => new UnskipTestsRewriter().Visit(testsRoot); - private class UnskipTestsRewriter : CSharpSyntaxRewriter + private sealed class UnskipTestsRewriter : CSharpSyntaxRewriter { public override SyntaxNode? VisitAttributeArgument(AttributeArgumentSyntax node) => node.NameEquals?.Name.Identifier.Text == "Skip" ? null : base.VisitAttributeArgument(node); From 0ca27450dd42cfd813478171476152e64c028d69 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:09:37 +0200 Subject: [PATCH 3/6] Fix warnings --- .../TestResultParser.cs | 28 +++++++++---------- src/Exercism.TestRunner.CSharp/TestRun.cs | 16 +++++------ .../TestRunWriter.cs | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/TestResultParser.cs b/src/Exercism.TestRunner.CSharp/TestResultParser.cs index c4d6055b..29c0c6fd 100644 --- a/src/Exercism.TestRunner.CSharp/TestResultParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestResultParser.cs @@ -13,10 +13,10 @@ internal static class TestResultParser internal static TestResult[] FromFile(string logFilePath, SyntaxTree testsSyntaxTree) { using var fileStream = File.OpenRead(logFilePath); - var result = (XmlTestRun)new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream); + var result = new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream) as XmlTestRun; - if (result.Results == null) - return Array.Empty(); + if (result?.Results == null) + return []; return result.ToTestResults(testsSyntaxTree); } @@ -75,10 +75,10 @@ private static TestStatus Status(this XmlUnitTestResult xmlUnitTestResult) => _ => TestStatus.Error }; - private static string Message(this XmlUnitTestResult xmlUnitTestResult) => + private static string? Message(this XmlUnitTestResult xmlUnitTestResult) => xmlUnitTestResult.Output?.ErrorInfo?.Message?.UseUnixNewlines()?.Trim(); - private static string Output(this XmlUnitTestResult xmlUnitTestResult) => + private static string? Output(this XmlUnitTestResult xmlUnitTestResult) => xmlUnitTestResult.Output?.StdOut?.UseUnixNewlines()?.Trim(); private static string TestCode(this MethodDeclarationSyntax testMethod) @@ -100,7 +100,7 @@ private static string TestCode(this MethodDeclarationSyntax testMethod) attribute.ArgumentList != null && attribute.ArgumentList.Arguments.Count == 1 && attribute.ArgumentList.Arguments[0].Expression.IsKind(SyntaxKind.NumericLiteralExpression)) - .Select(attribute => (LiteralExpressionSyntax)attribute.ArgumentList.Arguments[0].Expression) + .Select(attribute => (LiteralExpressionSyntax)attribute.ArgumentList!.Arguments[0].Expression) .Select(taskNumberExpression => (int?)taskNumberExpression.Token.Value!) .FirstOrDefault(); } @@ -109,31 +109,31 @@ private static string TestCode(this MethodDeclarationSyntax testMethod) public sealed class XmlOutput { [XmlElement(ElementName = "StdOut", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public string StdOut { get; set; } + public string? StdOut { get; set; } [XmlElement(ElementName = "ErrorInfo", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlErrorInfo ErrorInfo { get; set; } + public XmlErrorInfo? ErrorInfo { get; set; } } [XmlRoot(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public sealed class XmlUnitTestResult { [XmlElement(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlOutput Output { get; set; } + public XmlOutput? Output { get; set; } [XmlAttribute(AttributeName = "testName")] - public string TestName { get; set; } + public required string TestName { get; set; } [XmlAttribute(AttributeName = "outcome")] - public string Outcome { get; set; } + public required string Outcome { get; set; } } [XmlRoot(ElementName = "ErrorInfo", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public sealed class XmlErrorInfo { [XmlElement(ElementName = "Message", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public string Message { get; set; } + public string? Message { get; set; } } [XmlRoot(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] @@ -141,12 +141,12 @@ public sealed class XmlResults { [XmlElement(ElementName = "UnitTestResult", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public List UnitTestResult { get; set; } + public List? UnitTestResult { get; set; } } [XmlRoot(ElementName = "TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] public sealed class XmlTestRun { [XmlElement(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] - public XmlResults Results { get; set; } + public required XmlResults Results { get; set; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestRun.cs b/src/Exercism.TestRunner.CSharp/TestRun.cs index d45253d5..9ccca00d 100644 --- a/src/Exercism.TestRunner.CSharp/TestRun.cs +++ b/src/Exercism.TestRunner.CSharp/TestRun.cs @@ -12,22 +12,22 @@ internal enum TestStatus internal sealed class TestResult { [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; set; } [JsonPropertyName("status")] - public TestStatus Status { get; set; } + public required TestStatus Status { get; set; } [JsonPropertyName("task_id")] public int? TaskId { get; set; } [JsonPropertyName("message")] - public string Message { get; set; } + public string? Message { get; set; } [JsonPropertyName("output")] - public string Output { get; set; } + public string? Output { get; set; } [JsonPropertyName("test_code")] - public string TestCode { get; set; } + public required string TestCode { get; set; } } internal sealed class TestRun @@ -36,11 +36,11 @@ internal sealed class TestRun public int Version { get; set; } = 3; [JsonPropertyName("status")] - public TestStatus Status { get; set; } + public required TestStatus Status { get; set; } [JsonPropertyName("message")] - public string Message { get; set; } + public string? Message { get; set; } [JsonPropertyName("tests")] - public TestResult[] Tests { get; set; } + public required TestResult[] Tests { get; set; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestRunWriter.cs b/src/Exercism.TestRunner.CSharp/TestRunWriter.cs index f4a25a37..866affcd 100644 --- a/src/Exercism.TestRunner.CSharp/TestRunWriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestRunWriter.cs @@ -15,7 +15,7 @@ private static JsonSerializerOptions CreateJsonSerializerOptions() { var options = new JsonSerializerOptions { - IgnoreNullValues = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true, }; options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); From 6479533bb20a7774bbef29793cbac8273e3f359e Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:10:31 +0200 Subject: [PATCH 4/6] Use null pattern matching --- src/Exercism.TestRunner.CSharp/TestResultParser.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/TestResultParser.cs b/src/Exercism.TestRunner.CSharp/TestResultParser.cs index 29c0c6fd..54116677 100644 --- a/src/Exercism.TestRunner.CSharp/TestResultParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestResultParser.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Win32.SafeHandles; namespace Exercism.TestRunner.CSharp; @@ -15,10 +16,7 @@ internal static TestResult[] FromFile(string logFilePath, SyntaxTree testsSyntax using var fileStream = File.OpenRead(logFilePath); var result = new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream) as XmlTestRun; - if (result?.Results == null) - return []; - - return result.ToTestResults(testsSyntaxTree); + return result?.Results is null ? [] : result.ToTestResults(testsSyntaxTree); } private static TestResult[] ToTestResults(this XmlTestRun result, SyntaxTree testsSyntaxTree) @@ -83,7 +81,7 @@ private static TestStatus Status(this XmlUnitTestResult xmlUnitTestResult) => private static string TestCode(this MethodDeclarationSyntax testMethod) { - if (testMethod.Body != null) + if (testMethod.Body is not null) return SyntaxFactory.List(testMethod.Body.Statements.Select(statement => statement.WithoutLeadingTrivia())).ToString(); return testMethod.ExpressionBody! From 73c004e729ae26bf43148e2b871dfa8226169bd3 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:11:12 +0200 Subject: [PATCH 5/6] Use namespace --- src/Exercism.TestRunner.CSharp/StringExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Exercism.TestRunner.CSharp/StringExtensions.cs b/src/Exercism.TestRunner.CSharp/StringExtensions.cs index a8eb2f87..3081f767 100644 --- a/src/Exercism.TestRunner.CSharp/StringExtensions.cs +++ b/src/Exercism.TestRunner.CSharp/StringExtensions.cs @@ -1,4 +1,6 @@ -public static class StringExtensions +namespace Exercism.TestRunner.CSharp; + +public static class StringExtensions { public static string UseUnixNewlines(this string str) => str.Replace("\r\n", "\n"); From b94599acf165771efcc852ee1d8b4bc965f8294f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 19:15:53 +0200 Subject: [PATCH 6/6] Various refactorings --- .../TestRunParser.cs | 19 ++++----- src/Exercism.TestRunner.CSharp/TestSuite.cs | 41 +++++++------------ 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/TestRunParser.cs b/src/Exercism.TestRunner.CSharp/TestRunParser.cs index 6120b264..1b87b4ac 100644 --- a/src/Exercism.TestRunner.CSharp/TestRunParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestRunParser.cs @@ -6,15 +6,12 @@ internal static class TestRunParser { public static TestRun Parse(Options options, SyntaxTree testsSyntaxTree) { - var logLines = File.ReadLines(options.BuildLogFilePath); - var buildFailed = logLines.Any(); + var logLines = File.ReadAllLines(options.BuildLogFilePath); + var buildFailed = logLines.Length > 0; - if (buildFailed) - { - return TestRunWithError(logLines); - } - - return TestRunWithoutError(options, testsSyntaxTree); + return buildFailed + ? TestRunWithError(logLines) + : TestRunWithoutError(options, testsSyntaxTree); } private static TestRun TestRunWithoutError(Options options, SyntaxTree testsSyntaxTree) @@ -40,11 +37,11 @@ private static TestStatus ToTestStatus(this TestResult[] tests) } private static TestRun TestRunWithError(IEnumerable logLines) => - new TestRun + new() { Message = string.Join("\n", logLines.Select(NormalizeLogLine)), Status = TestStatus.Error, - Tests = Array.Empty() + Tests = [] }; private static string NormalizeLogLine(this string logLine) => @@ -66,6 +63,6 @@ private static string RemovePath(this string logLine) if (lastDirectorySeparatorIndex == -1) return logLine; - return logLine.Substring(lastDirectorySeparatorIndex + 1); + return logLine[(lastDirectorySeparatorIndex + 1)..]; } } \ No newline at end of file diff --git a/src/Exercism.TestRunner.CSharp/TestSuite.cs b/src/Exercism.TestRunner.CSharp/TestSuite.cs index 933fcd79..b0268fa9 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -5,35 +5,24 @@ namespace Exercism.TestRunner.CSharp; -internal sealed class TestSuite +internal sealed class TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) { private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; - private readonly SyntaxTree _originalSyntaxTree; - private readonly string _originalProjectFile; - private readonly Options _options; - - private TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) - { - _originalSyntaxTree = originalSyntaxTree; - _originalProjectFile = originalProjectFile; - _options = options; - } - public TestRun Run() { BeforeTests(); RunTests(); AfterTests(); - return TestRunParser.Parse(_options, _originalSyntaxTree); + return TestRunParser.Parse(options, originalSyntaxTree); } private void RunTests() { - var workingDirectory = Path.GetDirectoryName(_options.TestsFilePath)!; + var workingDirectory = Path.GetDirectoryName(options.TestsFilePath)!; RunProcess("dotnet", "restore --source /root/.nuget/packages/", workingDirectory); - RunProcess("dotnet", $"test -c release --no-restore --verbosity=quiet --logger \"trx;LogFileName={Path.GetFileName(_options.TestResultsFilePath)}\" /flp:verbosity=quiet;errorsOnly=true", workingDirectory); + RunProcess("dotnet", $"test -c release --no-restore --verbosity=quiet --logger \"trx;LogFileName={Path.GetFileName(options.TestResultsFilePath)}\" /flp:verbosity=quiet;errorsOnly=true", workingDirectory); } private static void RunProcess(string command, string arguments, string workingDirectory) @@ -54,22 +43,22 @@ private void BeforeTests() RewriteTestsFile(); if (CaptureOutput) - AddCaptureOuputAssemblyAttributes(); + AddCaptureOutputAssemblyAttributes(); } private void RewriteProjectFile() => - File.WriteAllText(_options.ProjectFilePath, - _originalProjectFile + File.WriteAllText(options.ProjectFilePath, + originalProjectFile .Replace("net5.0", "net9.0") .Replace("net6.0", "net9.0") .Replace("net7.0", "net9.0") .Replace("net8.0", "net9.0")); private void RewriteTestsFile() => - File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.Rewrite().ToString()); + File.WriteAllText(options.TestsFilePath, originalSyntaxTree.Rewrite().ToString()); - private void AddCaptureOuputAssemblyAttributes() => - File.WriteAllText(_options.AssemblyInfoFilePath, AssemblyInfo); + private void AddCaptureOutputAssemblyAttributes() => + File.WriteAllText(options.AssemblyInfoFilePath, AssemblyInfo); private void AfterTests() { @@ -77,16 +66,16 @@ private void AfterTests() UndoRewriteTestsFile(); if (CaptureOutput) - UndoAddCaptureOuputAssemblyAttributes(); + UndoAddCaptureOutputAssemblyAttributes(); } - private void UndoRewriteProjectFile() => File.WriteAllText(_options.ProjectFilePath, _originalProjectFile); + private void UndoRewriteProjectFile() => File.WriteAllText(options.ProjectFilePath, originalProjectFile); - private void UndoRewriteTestsFile() => File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.ToString()); + private void UndoRewriteTestsFile() => File.WriteAllText(options.TestsFilePath, originalSyntaxTree.ToString()); - private void UndoAddCaptureOuputAssemblyAttributes() => File.Delete(_options.AssemblyInfoFilePath); + private void UndoAddCaptureOutputAssemblyAttributes() => File.Delete(options.AssemblyInfoFilePath); - private bool CaptureOutput => _originalProjectFile.Contains("xunit.v3"); + private bool CaptureOutput => originalProjectFile.Contains("xunit.v3"); public static TestSuite FromOptions(Options options) {