From 78390ccc416ab73bbbc4105c0657a7a6e660363d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 13:50:42 +0200 Subject: [PATCH 1/3] Directly replace with .NET 9 --- src/Exercism.TestRunner.CSharp/TestSuite.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Exercism.TestRunner.CSharp/TestSuite.cs b/src/Exercism.TestRunner.CSharp/TestSuite.cs index e27777e..374f986 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -55,9 +55,9 @@ private void Rewrite() private void RewriteProjectFile() => File.WriteAllText(_options.ProjectFilePath, _originalProjectFile - .Replace("net5.0", "net8.0") - .Replace("net6.0", "net8.0") - .Replace("net7.0", "net8.0") + .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()); From fe5f1b0a4bc911cb570f45dcc9bd68c7095aabb0 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 13:52:13 +0200 Subject: [PATCH 2/3] Fix null issue in rewriter --- src/Exercism.TestRunner.CSharp/TestsRewriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs index 04a35d3..9be9db8 100644 --- a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs @@ -20,7 +20,7 @@ private static SyntaxNode UnskipTests(this SyntaxNode testsRoot) => private class UnskipTestsRewriter : CSharpSyntaxRewriter { - public override SyntaxNode VisitAttributeArgument(AttributeArgumentSyntax node) => + public override SyntaxNode? VisitAttributeArgument(AttributeArgumentSyntax node) => node.NameEquals?.Name.Identifier.Text == "Skip" ? null : base.VisitAttributeArgument(node); } } From e33ee4a797f31007d3512ab3da8d61b1bc2f4adc Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 4 May 2025 18:50:42 +0200 Subject: [PATCH 3/3] Allow capturing console and trace output --- src/Exercism.TestRunner.CSharp/Options.cs | 2 + src/Exercism.TestRunner.CSharp/TestSuite.cs | 30 +++- .../TestsRewriter.cs | 5 +- .../CaptureConsoleOutput.cs | 11 ++ .../CaptureConsoleOutput.csproj | 22 +++ .../CaptureConsoleOutputTests.cs | 5 + .../expected_results.json | 12 ++ .../capture-console-output/packages.lock.json | 170 ++++++++++++++++++ .../expected_results.json | 1 + 9 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 tests/capture-console-output/CaptureConsoleOutput.cs create mode 100644 tests/capture-console-output/CaptureConsoleOutput.csproj create mode 100644 tests/capture-console-output/CaptureConsoleOutputTests.cs create mode 100644 tests/capture-console-output/expected_results.json create mode 100644 tests/capture-console-output/packages.lock.json diff --git a/src/Exercism.TestRunner.CSharp/Options.cs b/src/Exercism.TestRunner.CSharp/Options.cs index 3b4d564..62a449f 100644 --- a/src/Exercism.TestRunner.CSharp/Options.cs +++ b/src/Exercism.TestRunner.CSharp/Options.cs @@ -21,6 +21,8 @@ public Options(string slug, string inputDirectory, string outputDirectory) => public string TestsFilePath => Path.Combine(InputDirectory, $"{Exercise}Tests.cs"); public string ProjectFilePath => Path.Combine(InputDirectory, $"{Exercise}.csproj"); + + public string AssemblyInfoFilePath => Path.Combine(InputDirectory, "AssemblyInfo.cs"); public string BuildLogFilePath => Path.Combine(InputDirectory, "msbuild.log"); diff --git a/src/Exercism.TestRunner.CSharp/TestSuite.cs b/src/Exercism.TestRunner.CSharp/TestSuite.cs index 374f986..9592892 100644 --- a/src/Exercism.TestRunner.CSharp/TestSuite.cs +++ b/src/Exercism.TestRunner.CSharp/TestSuite.cs @@ -7,6 +7,8 @@ namespace Exercism.TestRunner.CSharp { internal class TestSuite { + private const string AssemblyInfo = "[assembly: CaptureConsole]\n[assembly: CaptureTrace]\n"; + private readonly SyntaxTree _originalSyntaxTree; private readonly string _originalProjectFile; private readonly Options _options; @@ -20,14 +22,14 @@ private TestSuite(SyntaxTree originalSyntaxTree, string originalProjectFile, Opt public TestRun Run() { - Rewrite(); - RunDotnetTest(); - UndoRewrite(); + BeforeTests(); + RunTests(); + AfterTests(); return TestRunParser.Parse(_options, _originalSyntaxTree); } - private void RunDotnetTest() + private void RunTests() { var workingDirectory = Path.GetDirectoryName(_options.TestsFilePath)!; RunProcess("dotnet", "restore --source /root/.nuget/packages/", workingDirectory); @@ -46,10 +48,13 @@ private static void RunProcess(string command, string arguments, string workingD Process.Start(processStartInfo)?.WaitForExit(); } - private void Rewrite() + private void BeforeTests() { RewriteProjectFile(); RewriteTestsFile(); + + if (CaptureOutput) + AddCaptureOuputAssemblyAttributes(); } private void RewriteProjectFile() => @@ -60,18 +65,29 @@ private void RewriteProjectFile() => .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 UndoRewrite() + private void AfterTests() { UndoRewriteProjectFile(); UndoRewriteTestsFile(); + + if (CaptureOutput) + UndoAddCaptureOuputAssemblyAttributes(); } private void UndoRewriteProjectFile() => File.WriteAllText(_options.ProjectFilePath, _originalProjectFile); private void UndoRewriteTestsFile() => File.WriteAllText(_options.TestsFilePath, _originalSyntaxTree.ToString()); + private void UndoAddCaptureOuputAssemblyAttributes() => File.Delete(_options.AssemblyInfoFilePath); + + private bool CaptureOutput => _originalProjectFile.Contains("xunit.v3"); + public static TestSuite FromOptions(Options options) { var originalSyntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(options.TestsFilePath)); diff --git a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs index 9be9db8..1783a6d 100644 --- a/src/Exercism.TestRunner.CSharp/TestsRewriter.cs +++ b/src/Exercism.TestRunner.CSharp/TestsRewriter.cs @@ -2,8 +2,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - namespace Exercism.TestRunner.CSharp { internal static class TestsRewriter @@ -12,8 +10,7 @@ public static SyntaxTree Rewrite(this SyntaxTree tree) => tree.WithRootAndOptions(tree.GetRoot().Rewrite(), tree.Options); private static SyntaxNode Rewrite(this SyntaxNode node) => - node.UnskipTests() - .NormalizeWhitespace(); + node.UnskipTests().NormalizeWhitespace(); private static SyntaxNode UnskipTests(this SyntaxNode testsRoot) => new UnskipTestsRewriter().Visit(testsRoot); diff --git a/tests/capture-console-output/CaptureConsoleOutput.cs b/tests/capture-console-output/CaptureConsoleOutput.cs new file mode 100644 index 0000000..8727b3f --- /dev/null +++ b/tests/capture-console-output/CaptureConsoleOutput.cs @@ -0,0 +1,11 @@ +using System.Diagnostics; + +public static class Fake +{ + public static int Add(int x, int y) + { + Console.WriteLine($"Console: adding {x} and {y}"); + Trace.WriteLine($"Trace: adding {x} and {y}"); + return x + y; + } +} diff --git a/tests/capture-console-output/CaptureConsoleOutput.csproj b/tests/capture-console-output/CaptureConsoleOutput.csproj new file mode 100644 index 0000000..bc72d77 --- /dev/null +++ b/tests/capture-console-output/CaptureConsoleOutput.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + Exe + enable + enable + true + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/capture-console-output/CaptureConsoleOutputTests.cs b/tests/capture-console-output/CaptureConsoleOutputTests.cs new file mode 100644 index 0000000..3811842 --- /dev/null +++ b/tests/capture-console-output/CaptureConsoleOutputTests.cs @@ -0,0 +1,5 @@ +public class FakeTests +{ + [Fact] + public void Add_should_add_numbers() => Assert.Equal(2, Fake.Add(1, 1)); +} \ No newline at end of file diff --git a/tests/capture-console-output/expected_results.json b/tests/capture-console-output/expected_results.json new file mode 100644 index 0000000..b2f3880 --- /dev/null +++ b/tests/capture-console-output/expected_results.json @@ -0,0 +1,12 @@ +{ + "version": 3, + "status": "pass", + "tests": [ + { + "name": "Add should add numbers", + "status": "pass", + "output": "Console: adding 1 and 1\nTrace: adding 1 and 1", + "test_code": "Assert.Equal(2, Fake.Add(1, 1))" + } + ] +} diff --git a/tests/capture-console-output/packages.lock.json b/tests/capture-console-output/packages.lock.json new file mode 100644 index 0000000..6b33877 --- /dev/null +++ b/tests/capture-console-output/packages.lock.json @@ -0,0 +1,170 @@ +{ + "version": 1, + "dependencies": { + "net9.0": { + "Exercism.Tests.xunit.v3": { + "type": "Direct", + "requested": "[0.1.0-beta1, )", + "resolved": "0.1.0-beta1", + "contentHash": "XjVtQWWxmHDDj7UMdkPKpBFFKnsW0tkBhlyJSfFFh+fWwGemyyJwJYhdsvWhiKKCY7zItB+mI/o0OQtOKQxUhA==", + "dependencies": { + "xunit.v3.extensibility.core": "1.1.0" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.12.0, )", + "resolved": "17.12.0", + "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", + "dependencies": { + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "lbyYtsBxA8Pz8kaf5Xn/Mj1mL9z2nlBWdZhqFaj66nxXBa4JwiTDm4eGcpSMet6du9TOWI6bfha+gQR6+IHawg==" + }, + "xunit.v3": { + "type": "Direct", + "requested": "[1.1.0, )", + "resolved": "1.1.0", + "contentHash": "1ckSz5GVswlM9TCk5bGdHOjnYwqAWjkeqxckoHawQIA8sTeuN+RCBUypCi5A/Um0XlczRx5TjAK5W6BbN0HLcQ==", + "dependencies": { + "xunit.analyzers": "1.20.0", + "xunit.v3.assert": "[1.1.0]", + "xunit.v3.core": "[1.1.0]" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.12.0", + "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.5.3", + "contentHash": "h34zKNpGyni66VH738mRHeXSnf3klSShUdavUWNhSfWICUUi5aXeI0LBvoX/ad93N0+9xBDU3Fyi6WfxrwKQGw==", + "dependencies": { + "Microsoft.Testing.Platform": "1.5.3" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.5.3", + "contentHash": "WqJydnJ99dEKtquR9HwINz104ehWJKTXbQQrydGatlLRw14bmsx0pa8+E6KUXMYXZAimN0swWlDmcJGjjW4TIg==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.5.3", + "contentHash": "bOtpRMSPeT5YLQo+NNY8EtdNTphAUcmALjW4ABU7P0rb6yR2XAZau3TzNieLmR3lRuwudguWzzBhgcLRXwZh0A==", + "dependencies": { + "Microsoft.Testing.Platform": "1.5.3" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.12.0", + "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.6.0", + "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.20.0", + "contentHash": "HElev2E9vFbPxwKRQtpCSSzLOu8M/N9EWBCB37v7SRx6z4Lbj19FxfLEig3v9jiI6s4b0l2uena91nEsTWl9jA==" + }, + "xunit.v3.assert": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "4D+eM08ImfhA+zLbRzi8HA4qsT98zDxgaCD7vCg8yFesokKsgSsqWsAmImHFjVymGVhVS7WFGb19d6v1k9i0xQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0", + "System.Memory": "4.6.0" + } + }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "Cq55z8pC7fOkfj+3TB/YQ6OW96qWqxKiMd15CtkIl37VtV9EsiUL4B4HsR6VLJCzkk7cBiXQ1ABVIcp3TCm6HQ==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kXP/1d3jnQ2m4skcdM3gSMmubI6P747D6KVswzeedysgFkLj2xJlfo7p7slsmtEnp8BZb8X6D92Hssd/UtVPMw==", + "dependencies": { + "Microsoft.Testing.Platform.MSBuild": "1.5.3", + "xunit.v3.extensibility.core": "[1.1.0]", + "xunit.v3.runner.inproc.console": "[1.1.0]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "AeQbbYN001x0c+B9pqwml6jZPovHz8O/sOp7jmrjz90rUzz/QPal12SlHLKYszR44CMnW4MsDam3RYT5pkYUxw==", + "dependencies": { + "xunit.v3.common": "[1.1.0]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "Q81J0VPuu8fpF+/1CIjThqKKUjnqh0TQrLlD0iORkF75KdsOV+iGWT8c3AVuY96kDoxXxkTf0ZvJsK6o9osc1A==", + "dependencies": { + "xunit.v3.common": "[1.1.0]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "lX/4TwIJe9ysCd5dqLk/Doq8ieYaZGivgf95xR59wRuSV+nHzHnyhpjXfaPUp8nkncUH1rOmJ85o1KebipisXQ==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.5.3", + "Microsoft.Testing.Platform": "1.5.3", + "xunit.v3.extensibility.core": "[1.1.0]", + "xunit.v3.runner.common": "[1.1.0]" + } + } + } + } +} \ No newline at end of file diff --git a/tests/different-types-of-tests/expected_results.json b/tests/different-types-of-tests/expected_results.json index 3c7be8c..1e1758b 100644 --- a/tests/different-types-of-tests/expected_results.json +++ b/tests/different-types-of-tests/expected_results.json @@ -15,6 +15,7 @@ { "name": "Div should divide numbers", "status": "pass", + "output": "Ok, passed 100 tests.", "test_code": "Prop.ForAll\u003CPositiveInt\u003E(i =\u003E Fake.Div(i.Get, i.Get) == 1)\n .QuickCheckThrowOnFailure()" } ]