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;
}