Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses a significant performance issue where excessive console logging from JSON-RPC communication was causing Stryker.NET to take hours instead of minutes. The solution redirects JSON-RPC trace output from the console to dedicated log files when file logging is enabled.
Changes:
- Refactored RPC logging from console-based to file-based output via a new
FileRpcListenerclass - Changed the
enableDiagnosticboolean parameter torpcLogFilePathnullable string parameter across the test server connection infrastructure - Added logic to generate per-instance RPC log file paths when
LogToFileoption is enabled - Removed unused
Serilog.Eventsimport
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| TestingPlatformClient.cs | Updated constructor to accept optional log file path instead of boolean flag; creates FileRpcListener when path is provided |
| RPC/ConsoleRpcListener.cs | Renamed class to FileRpcListener and reimplemented to write to file instead of console; added StreamWriter disposal |
| ITestServerConnectionFactory.cs | Updated CreateClient interface signature to accept string? rpcLogFilePath parameter |
| DefaultTestServerConnectionFactory.cs | Updated CreateClient implementation to pass rpcLogFilePath to TestingPlatformClient constructor |
| AssemblyTestServer.cs | Added BuildRpcLogFilePath method to generate log file paths; removed unused Serilog import; updated to pass log path to CreateClient |
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/ConsoleRpcListener.cs
Outdated
Show resolved
Hide resolved
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/ConsoleRpcListener.cs
Outdated
Show resolved
Hide resolved
src/Stryker.TestRunner.MicrosoftTestPlatform/TestingPlatformClient.cs
Outdated
Show resolved
Hide resolved
…his caused the timeout value to increase slowly causing stryker to hang at some point.
src/Stryker.TestRunner.MicrosoftTestPlatform.UnitTest/SingleMicrosoftTestPlatformRunnerTests.cs
Show resolved
Hide resolved
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/ConsoleRpcListener.cs
Outdated
Show resolved
Hide resolved
...rationtest/TargetProjects/NetCore/NetCoreTestProject.MSTest/NetCoreTestProject.MSTest.csproj
Show resolved
Hide resolved
integrationtest/TargetProjects/NetCore/ExtraProject.XUnit/ExtraProject.XUnit.csproj
Show resolved
Hide resolved
integrationtest/TargetProjects/NetCore/EmptyTestProject/EmptyTestProject.csproj
Show resolved
Hide resolved
src/Stryker.TestRunner.MicrosoftTestPlatform.UnitTest/AssemblyTestServerTests.cs
Show resolved
Hide resolved
…with RunAllTestsInternalAsync
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
integrationtest/TargetProjects/MicrosoftTestPlatform/UnitTests.MSTest/SampleTests.cs:10
SampleTests.csuses MSTest types ([TestClass],[TestMethod],[DataRow],Assert) but theusing Microsoft.VisualStudio.TestTools.UnitTesting;directive was removed. This will not compile unless these are referenced viaglobal usingor fully-qualified names. Add the MSTest using back (or introduce aglobal usingfor the project) so the integration test target project builds.
using TargetProject.StrykerFeatures;
namespace NetCoreTestProject.MSTest.MTP;
[TestClass]
public class SampleTests
{
[TestMethod]
[DataRow(29, false)]
[DataRow(31, true)]
src/Stryker.TestRunner.MicrosoftTestPlatform/AssemblyTestServer.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 38 out of 39 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (3)
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/FileRpcListener.cs:1
Path.GetDirectoryName(filePath)can be null (e.g., whenfilePathis just a filename). The null-forgiving operator will route this into the catch block and degrade logging unnecessarily. Handle the null directory case explicitly (only callCreateDirectorywhen the directory part is non-null/non-empty)."
src/Stryker.TestRunner.MicrosoftTestPlatform/SingleMicrosoftTestPlatformRunner.cs:1RunAllTestsInternalAsyncprocesses a single assembly (and returns(Result, TimedOut, DiscoveredTests)for that assembly). Consider renaming to reflect the actual responsibility (e.g.,ProcessSingleAssemblyAsync/RunAssemblyTestsInternalAsync) to reduce confusion and keep call sites readable.
integrationtest/TargetProjects/MicrosoftTestPlatform/UnitTests.MSTest/SampleTests.cs:1- This file uses MSTest attributes (
[DataTestMethod],[DataRow],[TestMethod]) andAssert, but the explicitusing Microsoft.VisualStudio.TestTools.UnitTesting;was removed. If this project doesn’t define an equivalent global using, this will fail to compile. Either re-add the using here or ensure the project has aglobal using Microsoft.VisualStudio.TestTools.UnitTesting;.
using TargetProject.StrykerFeatures;
src/Stryker.TestRunner.MicrosoftTestPlatform/AssemblyTestServer.cs
Outdated
Show resolved
Hide resolved
src/Stryker.TestRunner.MicrosoftTestPlatform/AssemblyTestServer.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/FileRpcListener.cs:1
Path.GetDirectoryName(filePath)can returnnullwhenfilePathis just a filename (no directory component). In that case this throws and you silently degrade toStreamWriter.Null, meaning--log-to-filemay appear enabled but produces no file. Consider handling the “no directory” case explicitly (e.g., only callingCreateDirectorywhen the directory is non-empty, or defaulting to the current directory) so relative filenames work reliably.
src/Stryker.TestRunner.MicrosoftTestPlatform/SingleMicrosoftTestPlatformRunner.cs:1RunAllTestsInternalAsyncnow operates on a single assembly (string assembly) and returns per-assembly results, which makes the name misleading (it reads like “all assemblies”). Renaming to something likeRunAssemblyTestsInternalAsync/ProcessAssemblyAsyncwould reduce confusion, especially since this method isvirtualand used for test overrides.
src/Stryker.TestRunner.MicrosoftTestPlatform/AssemblyTestServer.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/FileRpcListener.cs:1
Path.GetDirectoryName(filePath)can return null (e.g., whenfilePathis just a filename without a directory). The null-forgiving operator forces an exception path and silently degrades toStreamWriter.Null, which can unintentionally disable RPC logs. Prefer explicitly handling a null/empty directory (skipCreateDirectoryin that case) so valid relative paths still work.
src/Stryker.TestRunner.MicrosoftTestPlatform/ProcessHandle.cs:1- Calling
Task.Wait(...)during disposal introduces a blocking wait that can slow teardown (potentially 5s per process) and can be problematic in environments sensitive to blocking (e.g., limited-thread test runners). Consider using a non-blocking approach where possible (e.g.,WaitAsyncwith a timeout in async disposal paths) or minimizing the wait to only cases where a clean shutdown is required, while still ensuringcommandTaskis safely disposed.
src/Stryker.TestRunner.MicrosoftTestPlatform/SingleMicrosoftTestPlatformRunner.cs:1 accumulator.HasTimeoutis only set inside theif (discoveredTests is not null)block. IfRunAssemblyTestsAsynccan returntimedOut == truewithdiscoveredTests == null(e.g., discovery failure but test execution/connection still hangs), the run may incorrectly return a non-timeoutTestRunResulteven though an assembly timed out. SetHasTimeoutbased ontimedOutindependently ofdiscoveredTests(and keep theHandleAssemblyTimeoutAsynccall conditional on having discovery data).
src/Stryker.TestRunner.MicrosoftTestPlatform/SingleMicrosoftTestPlatformRunner.cs:1accumulator.HasTimeoutis only set inside theif (discoveredTests is not null)block. IfRunAssemblyTestsAsynccan returntimedOut == truewithdiscoveredTests == null(e.g., discovery failure but test execution/connection still hangs), the run may incorrectly return a non-timeoutTestRunResulteven though an assembly timed out. SetHasTimeoutbased ontimedOutindependently ofdiscoveredTests(and keep theHandleAssemblyTimeoutAsynccall conditional on having discovery data).
integrationtest/TargetProjects/MicrosoftTestPlatform/UnitTests.MSTest/SampleTests.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/FileRpcListener.cs:1
Path.GetDirectoryName(filePath)can return null (e.g., whenfilePathis just "rpc.log" or a root path). The null-forgiving operator will turn that into a runtime exception before you hit the catch logic. Consider guarding the directory creation (only callCreateDirectorywhen the directory part is non-null/non-empty) so the listener can safely handle relative filenames too.
src/Stryker.TestRunner.MicrosoftTestPlatform/RPC/FileRpcListener.cs:1TraceListener.Write/WriteLinemay be called concurrently from multiple threads;StreamWriteris not thread-safe, which can interleave or corrupt log output. Consider synchronizing writes (e.g., a private lock around_writer.Write*) or wrapping the writer with a thread-safeTextWriter.Synchronized(...).
stryker-on-stryker.ps1:1- Adding
--open-report:dashboardin the CI-driven script is risky:--open-report-style flags commonly try to open a browser/URL, which may fail or behave poorly on headless CI agents. Also, the--open-report:dashboardsyntax (colon-delimited) is unusual for CLI args; it may need to be--open-report dashboard/--open-report=dashboarddepending on the option parser. Consider gating “open report” behind an explicit switch/env var for local runs and using the documented argument syntax to avoid CI failures.
| public void Dispose() | ||
| { | ||
| commandTask.Dispose(); | ||
| _processHandle.Dispose(); | ||
| outputStream.Dispose(); | ||
| } |
There was a problem hiding this comment.
CliTestServerProcess (as shown in the hunk) is constructed with (CommandTask<CommandResult> commandTask, Stream outputStream), but Dispose() now references _processHandle, which is not in scope for a primary-constructor parameter list like this unless it’s declared elsewhere in the class body. This will fail to compile unless _processHandle was also added as a field/parameter. Either update the CliTestServerProcess constructor to accept/store the IProcessHandle (recommended, given the new ProcessHandle disposal behavior), or revert to disposing commandTask here and ensure the process handle lifecycle is owned consistently by one component.
| <ItemGroup> | ||
| <PackageReference Include="coverlet.collector" Version="6.0.4" /> | ||
| <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" /> | ||
| <PackageReference Include="MSTest.TestAdapter" Version="3.11.1" /> | ||
| <PackageReference Include="MSTest.TestFramework" Version="3.11.1" /> |
There was a problem hiding this comment.
The PR description focuses on MTP coverage analysis, RPC logging, and timeout fixes, but this PR also removes coverlet.collector from multiple integration/example projects (and updates lockfiles accordingly). If this is intentional and required for the MTP changes, it would be helpful to mention it explicitly in the PR description (or split it into a separate PR) to keep the scope clear and to simplify review/rollback.
|


related to #3408
todo:
Console logging was excessive. All the RPC requests were logged directly to the console. This pr fixes that by logging to a file instead of console.
The pr also fixes a bug in timeout calculation. The timeout value kept increasing for every mutant being tested. This made the timeout value grow too large on a large project. At one point stryker waited for 23 hours per timeout...