diff --git a/src/Exercism.TestRunner.CSharp/Options.cs b/src/Exercism.TestRunner.CSharp/Options.cs index 62a449f..5e490ea 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 sealed 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 6b9bf32..3b7bd3e 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/StringExtensions.cs b/src/Exercism.TestRunner.CSharp/StringExtensions.cs index a8eb2f8..3081f76 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"); diff --git a/src/Exercism.TestRunner.CSharp/TestResultParser.cs b/src/Exercism.TestRunner.CSharp/TestResultParser.cs index 794c2ff..5411667 100644 --- a/src/Exercism.TestRunner.CSharp/TestResultParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestResultParser.cs @@ -5,149 +5,146 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Win32.SafeHandles; -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); - - if (result.Results == null) - return Array.Empty(); - - return result.ToTestResults(testsSyntaxTree); - } + using var fileStream = File.OpenRead(logFilePath); + var result = new XmlSerializer(typeof(XmlTestRun)).Deserialize(fileStream) as XmlTestRun; - 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?.Results is null ? [] : 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 is not 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 sealed 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 sealed class XmlUnitTestResult +{ + [XmlElement(ElementName = "Output", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + public XmlOutput? Output { get; set; } + + [XmlAttribute(AttributeName = "testName")] + public required string TestName { get; set; } + + [XmlAttribute(AttributeName = "outcome")] + 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; } +} + +[XmlRoot(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public sealed 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 sealed class XmlTestRun +{ + [XmlElement(ElementName = "Results", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] + 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 3a6710a..9ccca00 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 sealed 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 required string Name { get; set; } + + [JsonPropertyName("status")] + public required 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 required string TestCode { get; set; } +} - internal class TestRun - { - [JsonPropertyName("version")] - public int Version { get; set; } = 3; +internal sealed class TestRun +{ + [JsonPropertyName("version")] + public int Version { get; set; } = 3; - [JsonPropertyName("status")] - public TestStatus Status { get; set; } + [JsonPropertyName("status")] + public required 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 required 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 bf251fa..1b87b4a 100644 --- a/src/Exercism.TestRunner.CSharp/TestRunParser.cs +++ b/src/Exercism.TestRunner.CSharp/TestRunParser.cs @@ -1,72 +1,68 @@ 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(); + var logLines = File.ReadAllLines(options.BuildLogFilePath); + var buildFailed = logLines.Length > 0; - if (buildFailed) - { - return TestRunWithError(logLines); - } + return buildFailed + ? TestRunWithError(logLines) + : TestRunWithoutError(options, testsSyntaxTree); + } - return TestRunWithoutError(options, testsSyntaxTree); - } + private static TestRun TestRunWithoutError(Options options, SyntaxTree testsSyntaxTree) + { + var testResults = TestResultParser.FromFile(options.TestResultsFilePath, testsSyntaxTree); - private static TestRun TestRunWithoutError(Options options, SyntaxTree testsSyntaxTree) + return new TestRun { - var testResults = TestResultParser.FromFile(options.TestResultsFilePath, testsSyntaxTree); - - return new TestRun - { - Status = testResults.ToTestStatus(), - Tests = testResults - }; - } + Status = testResults.ToTestStatus(), + Tests = testResults + }; + } - private static TestStatus ToTestStatus(this TestResult[] tests) - { - if (tests.Any(test => test.Status == TestStatus.Fail)) - return TestStatus.Fail; + private static TestStatus ToTestStatus(this TestResult[] tests) + { + if (tests.Any(test => test.Status == TestStatus.Fail)) + return TestStatus.Fail; - if (tests.All(test => test.Status == TestStatus.Pass) && tests.Any()) - return TestStatus.Pass; + if (tests.All(test => test.Status == TestStatus.Pass) && tests.Any()) + return TestStatus.Pass; - return TestStatus.Error; - } + return TestStatus.Error; + } - private static TestRun TestRunWithError(IEnumerable logLines) => - new TestRun - { - Message = string.Join("\n", logLines.Select(NormalizeLogLine)), - Status = TestStatus.Error, - Tests = Array.Empty() - }; + private static TestRun TestRunWithError(IEnumerable logLines) => + new() + { + Message = string.Join("\n", logLines.Select(NormalizeLogLine)), + Status = TestStatus.Error, + Tests = [] + }; - private static string NormalizeLogLine(this string logLine) => - logLine.RemoveProjectReference().RemovePath().UseUnixNewlines().Trim(); + 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)]; - } + private static string RemoveProjectReference(this string logLine) + { + var bracketIndex = logLine.LastIndexOf('['); + return bracketIndex == -1 ? logLine : logLine[..(bracketIndex - 1)]; + } - private static string RemovePath(this string logLine) - { - var testFileIndex = logLine.IndexOf(".cs(", StringComparison.Ordinal); - if (testFileIndex == -1) - return logLine; + private static string RemovePath(this string logLine) + { + var testFileIndex = logLine.IndexOf(".cs(", StringComparison.Ordinal); + if (testFileIndex == -1) + return logLine; - var lastDirectorySeparatorIndex = logLine.LastIndexOf(Path.DirectorySeparatorChar, testFileIndex); - if (lastDirectorySeparatorIndex == -1) - return logLine; + var lastDirectorySeparatorIndex = logLine.LastIndexOf(Path.DirectorySeparatorChar, testFileIndex); + 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/TestRunWriter.cs b/src/Exercism.TestRunner.CSharp/TestRunWriter.cs index 48ac9a9..866affc 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)); + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + 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 9592892..b0268fa 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -3,96 +3,84 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace Exercism.TestRunner.CSharp +namespace Exercism.TestRunner.CSharp; + +internal sealed class TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) { - internal class TestSuite + private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; + + public TestRun Run() { - private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; + BeforeTests(); + RunTests(); + AfterTests(); - private readonly SyntaxTree _originalSyntaxTree; - private readonly string _originalProjectFile; - private readonly Options _options; + return TestRunParser.Parse(options, originalSyntaxTree); + } - private TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Options options) - { - _originalSyntaxTree = originalSyntaxTree; - _originalProjectFile = originalProjectFile; - _options = options; - } + 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); + } - public TestRun Run() + private static void RunProcess(string command, string arguments, string workingDirectory) + { + var processStartInfo = new ProcessStartInfo(command, arguments) { - BeforeTests(); - RunTests(); - AfterTests(); + WorkingDirectory = workingDirectory, + RedirectStandardInput = true, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + Process.Start(processStartInfo)?.WaitForExit(); + } - return TestRunParser.Parse(_options, _originalSyntaxTree); - } + private void BeforeTests() + { + RewriteProjectFile(); + RewriteTestsFile(); + + if (CaptureOutput) + AddCaptureOutputAssemblyAttributes(); + } - 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 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 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() - { - 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()); + private void RewriteTestsFile() => + 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() - { - UndoRewriteProjectFile(); - UndoRewriteTestsFile(); + private void AfterTests() + { + UndoRewriteProjectFile(); + UndoRewriteTestsFile(); - if (CaptureOutput) - UndoAddCaptureOuputAssemblyAttributes(); - } + if (CaptureOutput) + 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) - { - 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 1783a6d..6e999e2 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 sealed 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