From e12e140bec9801b214ca7be9c31cb61690834805 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:03:57 +0000 Subject: [PATCH 1/4] Initial plan From c8753c0f458e95bede2c6ad42235ce33a4226db5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:10:27 +0000 Subject: [PATCH 2/4] Add tests to improve code coverage - added 12 new tests Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- .../ContextTests.cs | 20 +++ .../ProgramTests.cs | 75 +++++++++ .../SonarHotSpotTests.cs | 96 ++++++++++++ .../SonarIssueTests.cs | 146 ++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 test/DemaConsulting.SonarMark.Tests/SonarHotSpotTests.cs create mode 100644 test/DemaConsulting.SonarMark.Tests/SonarIssueTests.cs diff --git a/test/DemaConsulting.SonarMark.Tests/ContextTests.cs b/test/DemaConsulting.SonarMark.Tests/ContextTests.cs index c49a41a..bb16049 100644 --- a/test/DemaConsulting.SonarMark.Tests/ContextTests.cs +++ b/test/DemaConsulting.SonarMark.Tests/ContextTests.cs @@ -500,4 +500,24 @@ public void Context_Create_WithLogFile_WritesToLogFile() Assert.Contains("Normal message", logContent); Assert.Contains("Error message", logContent); } + + /// + /// Test that creating context with invalid log file path throws exception + /// + [TestMethod] + public void Context_Create_InvalidLogFilePath_ThrowsException() + { + // Use an invalid path that will fail to create + var invalidPath = Path.Combine("/invalid/directory/that/does/not/exist", "test.log"); + + try + { + Context.Create(["--log", invalidPath]); + Assert.Fail("Expected InvalidOperationException was not thrown"); + } + catch (InvalidOperationException ex) + { + Assert.Contains("Failed to open log file", ex.Message); + } + } } diff --git a/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs b/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs index 2ecfcb1..1d90ba3 100644 --- a/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.SonarMark.Tests/ProgramTests.cs @@ -78,4 +78,79 @@ public void Program_Run_ValidateFlag_OutputsNotImplemented() // Just verify it doesn't throw and succeeds Assert.AreEqual(0, context.ExitCode); } + + /// + /// Test that Run method with no flags outputs banner and error for missing server + /// + [TestMethod] + public void Program_Run_NoFlags_OutputsBannerAndRequiresServer() + { + var originalOut = Console.Out; + var output = new StringWriter(); + Console.SetOut(output); + + try + { + using var context = Context.Create([]); + Program.Run(context); + + var outputText = output.ToString(); + Assert.Contains("SonarMark version", outputText); + Assert.Contains("--server parameter is required", outputText); + Assert.AreEqual(1, context.ExitCode); + } + finally + { + Console.SetOut(originalOut); + } + } + + /// + /// Test that Run method with server but no project key outputs error + /// + [TestMethod] + public void Program_Run_ServerWithoutProjectKey_OutputsError() + { + var originalOut = Console.Out; + var output = new StringWriter(); + Console.SetOut(output); + + try + { + using var context = Context.Create(["--server", "https://sonarcloud.io"]); + Program.Run(context); + + var outputText = output.ToString(); + Assert.Contains("--project-key parameter is required", outputText); + Assert.AreEqual(1, context.ExitCode); + } + finally + { + Console.SetOut(originalOut); + } + } + + /// + /// Test that Run method with silent flag suppresses banner + /// + [TestMethod] + public void Program_Run_SilentFlag_SuppressesBanner() + { + var originalOut = Console.Out; + var output = new StringWriter(); + Console.SetOut(output); + + try + { + using var context = Context.Create(["--silent"]); + Program.Run(context); + + var outputText = output.ToString(); + Assert.DoesNotContain("SonarMark version", outputText); + } + finally + { + Console.SetOut(originalOut); + } + } } diff --git a/test/DemaConsulting.SonarMark.Tests/SonarHotSpotTests.cs b/test/DemaConsulting.SonarMark.Tests/SonarHotSpotTests.cs new file mode 100644 index 0000000..7f53815 --- /dev/null +++ b/test/DemaConsulting.SonarMark.Tests/SonarHotSpotTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SonarMark.Tests; + +/// +/// Tests for SonarHotSpot class +/// +[TestClass] +public class SonarHotSpotTests +{ + /// + /// Test that SonarHotSpot can be created with all properties + /// + [TestMethod] + public void SonarHotSpot_Constructor_AllProperties_CreatesInstance() + { + // Arrange & Act + var hotSpot = new SonarHotSpot( + "hs-key-123", + "test_project:src/File.cs", + 42, + "Security vulnerability detected", + "sql-injection", + "HIGH"); + + // Assert + Assert.AreEqual("hs-key-123", hotSpot.Key); + Assert.AreEqual("test_project:src/File.cs", hotSpot.Component); + Assert.AreEqual(42, hotSpot.Line); + Assert.AreEqual("Security vulnerability detected", hotSpot.Message); + Assert.AreEqual("sql-injection", hotSpot.SecurityCategory); + Assert.AreEqual("HIGH", hotSpot.VulnerabilityProbability); + } + + /// + /// Test that SonarHotSpot can be created with null line number + /// + [TestMethod] + public void SonarHotSpot_Constructor_NullLine_CreatesInstance() + { + // Arrange & Act + var hotSpot = new SonarHotSpot( + "hs-key-456", + "test_project:src/Global.cs", + null, + "Global security issue", + "weak-cryptography", + "MEDIUM"); + + // Assert + Assert.AreEqual("hs-key-456", hotSpot.Key); + Assert.AreEqual("test_project:src/Global.cs", hotSpot.Component); + Assert.IsNull(hotSpot.Line); + Assert.AreEqual("Global security issue", hotSpot.Message); + Assert.AreEqual("weak-cryptography", hotSpot.SecurityCategory); + Assert.AreEqual("MEDIUM", hotSpot.VulnerabilityProbability); + } + + /// + /// Test that SonarHotSpot supports LOW vulnerability probability + /// + [TestMethod] + public void SonarHotSpot_Constructor_LowProbability_CreatesInstance() + { + // Arrange & Act + var hotSpot = new SonarHotSpot( + "hs-key-789", + "test_project:src/Helper.cs", + 10, + "Potential security issue", + "xss", + "LOW"); + + // Assert + Assert.AreEqual("hs-key-789", hotSpot.Key); + Assert.AreEqual("LOW", hotSpot.VulnerabilityProbability); + } +} diff --git a/test/DemaConsulting.SonarMark.Tests/SonarIssueTests.cs b/test/DemaConsulting.SonarMark.Tests/SonarIssueTests.cs new file mode 100644 index 0000000..454fd17 --- /dev/null +++ b/test/DemaConsulting.SonarMark.Tests/SonarIssueTests.cs @@ -0,0 +1,146 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SonarMark.Tests; + +/// +/// Tests for SonarIssue class +/// +[TestClass] +public class SonarIssueTests +{ + /// + /// Test that SonarIssue can be created with all properties + /// + [TestMethod] + public void SonarIssue_Constructor_AllProperties_CreatesInstance() + { + // Arrange & Act + var issue = new SonarIssue( + "issue-key-123", + "csharpsquid:S1234", + "MAJOR", + "test_project:src/File.cs", + 42, + "Issue message", + "BUG"); + + // Assert + Assert.AreEqual("issue-key-123", issue.Key); + Assert.AreEqual("csharpsquid:S1234", issue.Rule); + Assert.AreEqual("MAJOR", issue.Severity); + Assert.AreEqual("test_project:src/File.cs", issue.Component); + Assert.AreEqual(42, issue.Line); + Assert.AreEqual("Issue message", issue.Message); + Assert.AreEqual("BUG", issue.Type); + } + + /// + /// Test that SonarIssue can be created with null line number + /// + [TestMethod] + public void SonarIssue_Constructor_NullLine_CreatesInstance() + { + // Arrange & Act + var issue = new SonarIssue( + "issue-key-456", + "csharpsquid:S5678", + "MINOR", + "test_project:src/Global.cs", + null, + "Global issue", + "CODE_SMELL"); + + // Assert + Assert.AreEqual("issue-key-456", issue.Key); + Assert.AreEqual("csharpsquid:S5678", issue.Rule); + Assert.AreEqual("MINOR", issue.Severity); + Assert.AreEqual("test_project:src/Global.cs", issue.Component); + Assert.IsNull(issue.Line); + Assert.AreEqual("Global issue", issue.Message); + Assert.AreEqual("CODE_SMELL", issue.Type); + } + + /// + /// Test that SonarIssue supports BLOCKER severity + /// + [TestMethod] + public void SonarIssue_Constructor_BlockerSeverity_CreatesInstance() + { + // Arrange & Act + var issue = new SonarIssue( + "issue-key-789", + "csharpsquid:S9999", + "BLOCKER", + "test_project:src/Critical.cs", + 10, + "Critical issue", + "BUG"); + + // Assert + Assert.AreEqual("issue-key-789", issue.Key); + Assert.AreEqual("BLOCKER", issue.Severity); + Assert.AreEqual("BUG", issue.Type); + } + + /// + /// Test that SonarIssue supports CRITICAL severity + /// + [TestMethod] + public void SonarIssue_Constructor_CriticalSeverity_CreatesInstance() + { + // Arrange & Act + var issue = new SonarIssue( + "issue-key-abc", + "csharpsquid:S8888", + "CRITICAL", + "test_project:src/Important.cs", + 20, + "Critical security issue", + "VULNERABILITY"); + + // Assert + Assert.AreEqual("issue-key-abc", issue.Key); + Assert.AreEqual("CRITICAL", issue.Severity); + Assert.AreEqual("VULNERABILITY", issue.Type); + } + + /// + /// Test that SonarIssue supports INFO severity + /// + [TestMethod] + public void SonarIssue_Constructor_InfoSeverity_CreatesInstance() + { + // Arrange & Act + var issue = new SonarIssue( + "issue-key-def", + "csharpsquid:S7777", + "INFO", + "test_project:src/Helper.cs", + 5, + "Informational message", + "CODE_SMELL"); + + // Assert + Assert.AreEqual("issue-key-def", issue.Key); + Assert.AreEqual("INFO", issue.Severity); + Assert.AreEqual("CODE_SMELL", issue.Type); + } +} From d1ee3af132f3688cc78f6d0ab9b1985908a4a685 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:20:52 +0000 Subject: [PATCH 3/4] Add Runner class and integration tests for improved coverage Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- .../IntegrationTests.cs | 333 ++++++++++++++++++ test/DemaConsulting.SonarMark.Tests/Runner.cs | 68 ++++ 2 files changed, 401 insertions(+) create mode 100644 test/DemaConsulting.SonarMark.Tests/IntegrationTests.cs create mode 100644 test/DemaConsulting.SonarMark.Tests/Runner.cs diff --git a/test/DemaConsulting.SonarMark.Tests/IntegrationTests.cs b/test/DemaConsulting.SonarMark.Tests/IntegrationTests.cs new file mode 100644 index 0000000..e533cf4 --- /dev/null +++ b/test/DemaConsulting.SonarMark.Tests/IntegrationTests.cs @@ -0,0 +1,333 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SonarMark.Tests; + +/// +/// Integration tests that run the SonarMark application through dotnet +/// +[TestClass] +public class IntegrationTests +{ + private string _dllPath = string.Empty; + + /// + /// Initialize test by locating the SonarMark DLL + /// + [TestInitialize] + public void TestInitialize() + { + // The DLL should be in the same directory as the test assembly + // because the test project references the main project + var baseDir = AppContext.BaseDirectory; + _dllPath = Path.Combine(baseDir, "DemaConsulting.SonarMark.dll"); + + Assert.IsTrue(File.Exists(_dllPath), $"Could not find SonarMark DLL at {_dllPath}"); + } + + /// + /// Test that version flag outputs version information + /// + [TestMethod] + public void IntegrationTest_VersionFlag_OutputsVersion() + { + // Run the application with --version flag + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--version"); + + // Verify success + Assert.AreEqual(0, exitCode); + + // Verify version is output + Assert.IsFalse(string.IsNullOrWhiteSpace(output)); + Assert.DoesNotContain("Error", output); + } + + /// + /// Test that help flag outputs usage information + /// + [TestMethod] + public void IntegrationTest_HelpFlag_OutputsUsageInformation() + { + // Run the application with --help flag + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--help"); + + // Verify success + Assert.AreEqual(0, exitCode); + + // Verify usage information + Assert.Contains("Usage: sonarmark", output); + Assert.Contains("Options:", output); + Assert.Contains("--version", output); + Assert.Contains("--help", output); + Assert.Contains("--server", output); + Assert.Contains("--project-key", output); + } + + /// + /// Test that validate flag outputs not implemented message + /// + [TestMethod] + public void IntegrationTest_ValidateFlag_OutputsNotImplemented() + { + // Run the application with --validate flag + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--validate"); + + // Verify success + Assert.AreEqual(0, exitCode); + + // Verify not implemented message + Assert.Contains("Self-validation not yet implemented", output); + } + + /// + /// Test that missing server parameter shows error + /// + [TestMethod] + public void IntegrationTest_MissingServerParameter_ShowsError() + { + // Run the application without required parameters + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("--server parameter is required", output); + } + + /// + /// Test that missing project-key parameter shows error + /// + [TestMethod] + public void IntegrationTest_MissingProjectKeyParameter_ShowsError() + { + // Run the application with server but without project-key + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--server", "https://sonarcloud.io"); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("--project-key parameter is required", output); + } + + /// + /// Test that silent flag suppresses output + /// + [TestMethod] + public void IntegrationTest_SilentFlag_SuppressesOutput() + { + // Run the application with --silent flag + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--silent"); + + // Verify error exit code (missing required parameters) + Assert.AreEqual(1, exitCode); + + // Verify no banner in output + Assert.DoesNotContain("SonarMark version", output); + } + + /// + /// Test that invalid argument shows error + /// + [TestMethod] + public void IntegrationTest_InvalidArgument_ShowsError() + { + // Run the application with invalid argument + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--invalid-argument"); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("Error:", output); + Assert.Contains("Unsupported argument", output); + } + + /// + /// Test that report-depth without value shows error + /// + [TestMethod] + public void IntegrationTest_ReportDepthWithoutValue_ShowsError() + { + // Run the application with --report-depth but no value + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--report-depth"); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("Error:", output); + Assert.Contains("--report-depth requires a depth argument", output); + } + + /// + /// Test that report-depth with invalid value shows error + /// + [TestMethod] + public void IntegrationTest_ReportDepthWithInvalidValue_ShowsError() + { + // Run the application with --report-depth and invalid value + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--report-depth", "invalid"); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("Error:", output); + Assert.Contains("--report-depth requires a positive integer", output); + } + + /// + /// Test that report-depth with zero shows error + /// + [TestMethod] + public void IntegrationTest_ReportDepthWithZero_ShowsError() + { + // Run the application with --report-depth 0 + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--report-depth", "0"); + + // Verify error exit code + Assert.AreEqual(1, exitCode); + + // Verify error message + Assert.Contains("Error:", output); + Assert.Contains("--report-depth requires a positive integer", output); + } + + /// + /// Test that token parameter is accepted + /// + [TestMethod] + public void IntegrationTest_TokenParameter_IsAccepted() + { + // Run the application with token parameter + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--token", "test-token"); + + // Verify error exit code (missing server and project-key) + Assert.AreEqual(1, exitCode); + + // Verify it's not an argument error + Assert.DoesNotContain("Unsupported argument", output); + } + + /// + /// Test that branch parameter is accepted + /// + [TestMethod] + public void IntegrationTest_BranchParameter_IsAccepted() + { + // Run the application with branch parameter + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--branch", "main"); + + // Verify error exit code (missing server and project-key) + Assert.AreEqual(1, exitCode); + + // Verify it's not an argument error + Assert.DoesNotContain("Unsupported argument", output); + } + + /// + /// Test that enforce flag is accepted + /// + [TestMethod] + public void IntegrationTest_EnforceFlag_IsAccepted() + { + // Run the application with enforce flag + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--enforce"); + + // Verify error exit code (missing server and project-key) + Assert.AreEqual(1, exitCode); + + // Verify it's not an argument error + Assert.DoesNotContain("Unsupported argument", output); + } + + /// + /// Test that report parameter is accepted + /// + [TestMethod] + public void IntegrationTest_ReportParameter_IsAccepted() + { + // Run the application with report parameter + var exitCode = Runner.Run( + out var output, + "dotnet", + _dllPath, + "--report", "output.md"); + + // Verify error exit code (missing server and project-key) + Assert.AreEqual(1, exitCode); + + // Verify it's not an argument error + Assert.DoesNotContain("Unsupported argument", output); + } +} diff --git a/test/DemaConsulting.SonarMark.Tests/Runner.cs b/test/DemaConsulting.SonarMark.Tests/Runner.cs new file mode 100644 index 0000000..468e356 --- /dev/null +++ b/test/DemaConsulting.SonarMark.Tests/Runner.cs @@ -0,0 +1,68 @@ +// Copyright (c) DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Diagnostics; + +namespace DemaConsulting.SonarMark.Tests; + +/// +/// Program runner class for integration testing +/// +internal static class Runner +{ + /// + /// Runs the specified program and captures its output + /// + /// Program output (stdout and stderr combined) + /// Program name or path + /// Program arguments + /// Program exit code + /// Thrown when process fails to start + public static int Run(out string output, string program, params string[] arguments) + { + // Construct the start information + var startInfo = new ProcessStartInfo(program) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + // Add the arguments + foreach (var argument in arguments) + { + startInfo.ArgumentList.Add(argument); + } + + // Start the process + var process = Process.Start(startInfo) ?? + throw new InvalidOperationException("Failed to start process"); + + // Wait for the process to exit + process.WaitForExit(); + + // Combine stdout and stderr, save the output and return the exit code + var stdout = process.StandardOutput.ReadToEnd(); + var stderr = process.StandardError.ReadToEnd(); + output = stdout + stderr; + return process.ExitCode; + } +} From 43294722dcc37eefb042ebc4029fa6a1b5e0da2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:24:20 +0000 Subject: [PATCH 4/4] Fix Runner to use async stream reading and proper disposal Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- test/DemaConsulting.SonarMark.Tests/Runner.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/DemaConsulting.SonarMark.Tests/Runner.cs b/test/DemaConsulting.SonarMark.Tests/Runner.cs index 468e356..b70e80f 100644 --- a/test/DemaConsulting.SonarMark.Tests/Runner.cs +++ b/test/DemaConsulting.SonarMark.Tests/Runner.cs @@ -53,15 +53,19 @@ public static int Run(out string output, string program, params string[] argumen } // Start the process - var process = Process.Start(startInfo) ?? - throw new InvalidOperationException("Failed to start process"); + using var process = Process.Start(startInfo) ?? + throw new InvalidOperationException("Failed to start process"); + + // Read output asynchronously to avoid buffer overflow + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); // Wait for the process to exit process.WaitForExit(); // Combine stdout and stderr, save the output and return the exit code - var stdout = process.StandardOutput.ReadToEnd(); - var stderr = process.StandardError.ReadToEnd(); + var stdout = outputTask.GetAwaiter().GetResult(); + var stderr = errorTask.GetAwaiter().GetResult(); output = stdout + stderr; return process.ExitCode; }