[fix] Throw when /diag is passed manually in integration tests#16055
[fix] Throw when /diag is passed manually in integration tests#16055nohwnd wants to merge 7 commits into
Conversation
Always auto-inject /diag in InvokeVsTest and InvokeDotnetTest, and throw an InvalidOperationException when tests pass /diag manually in their arguments, directing them to use DiagLogsDirectory. Key changes: - IntegrationTestBase: Add DiagLogsDirectory property and GetDiagLogContents() helper - IntegrationTestBase: IsDiagAlreadyEnabled now throws instead of silently skipping - All tests that manually passed /diag: remove the manual arg and use auto-injected diag - Tests that checked diag file content: switch to DiagLogsDirectory-based search - DataCollectorAttachmentProcessor: update datacollector log search to use DiagLogsDirectory - CreateNoNewWindowTests/SerializerSelectionTests: remove duplicate local GetDiagLogContents() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the integration test infrastructure to prevent tests from manually passing /diag (or --diag / -diag) and accidentally bypassing the framework’s diagnostic-log auto-injection and attachment collection, improving CI debuggability on failures.
Changes:
- Make
IntegrationTestBase.IsDiagAlreadyEnabledthrow when/diagis manually supplied, and exposeDiagLogsDirectory+ a sharedGetDiagLogContents()helper. - Update affected acceptance/integration tests to stop manually passing
/diagand instead read logs from the auto-injected diagnostics location. - Normalize host-process / datacollector log assertions to point at the diagnostics logs directory.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs | Adds DiagLogsDirectory and GetDiagLogContents(), and throws if tests manually pass diag arguments. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs | Removes manual /Diag: injection from code coverage argument construction. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs | Removes manual /Diag: and relies on GetDiagLogContents() for serializer assertions. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs | Stops manually adding diag arg; updates host-log counting to use DiagLogsDirectory. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PlatformTests.cs | Stops manually adding diag arg; updates host-log counting to use DiagLogsDirectory. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs | Removes manual diag args and switches log-content assertions to GetDiagLogContents(). |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs | Removes manual --diag: usage so auto-injection can attach logs. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs | Removes manual --diag: and attempts to assert on produced diag log files. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs | Removes manual /Diag: and updates datacollector log lookup to DiagLogsDirectory. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs | Removes manual /Diag: and uses shared GetDiagLogContents() for assertions. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs | Removes manual /Diag: from code coverage runner argument construction. |
| test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs | Removes manual /Diag: from blame/hang-dump test arguments. |
|
|
||
| // Verify out-of-proc coverlet collector load | ||
| var dataCollectorLog = Directory.GetFiles(logPathDirectory, $"coverletcoverage.{logId}.datacollector*log").Single(); | ||
| var dataCollectorLog = Directory.GetFiles(DiagLogsDirectory, "log.txt.datacollector*").Single(); |
|
|
||
| // Verify in-proc coverlet collector load | ||
| var hostLog = Directory.GetFiles(logPathDirectory, $"coverletcoverage.{logId}.host*log").Single(); | ||
| var hostLog = Directory.GetFiles(DiagLogsDirectory, "log.txt.host*").Single(); |
nohwnd
left a comment
There was a problem hiding this comment.
🧠 Reviewed by Expert Code Reviewer
Dimensions activated: Acceptance Test Coverage Design, Error Reporting & Diagnostic Clarity
Summary
The change is logically sound. Converting IsDiagAlreadyEnabled from a silent-skip to a hard throw is the right call — it makes misuse visible immediately rather than hiding it. The refactoring to DiagLogsDirectory and the consolidated GetDiagLogContents() base helper are clean improvements.
One concern is noted inline regarding the -diag substring check. The severity of a false-positive match escalated from "silently skip diagnostics" (harmless) to "throw InvalidOperationException" (test failure). Today's test argument strings are unlikely to trigger this, but the fix was the right moment to tighten the match predicate.
All other changes reviewed:
Assert.Contains(errorMessage, GetDiagLogContents())parameter order ✓ (needle first, haystack second — correct)DataCollectorTests.Coverlet.csfile pattern migration to"log.txt"/"log.txt.datacollector*"/"log.txt.host*"is consistent with howGetDiagArgnames the base log file ✓DataCollectionTests.cschange fromTempDirectory.PathtoDiagLogsDirectoryfor*.datacollector.*search is correct since diag injection now writes toDiagLogsDirectory✓- Hardcoded
c:\temp\logscpp\path removed fromDotnetTestTests.cs✓
🧠 Reviewed by Expert Code Reviewer 🧠
| private static bool IsDiagAlreadyEnabled(string arguments) | ||
| { | ||
| // Check args for --diag, /diag, -diag (case-insensitive) | ||
| // If user passed /diag, --diag, or -diag, throw to prevent silently skipping the auto-injected |
There was a problem hiding this comment.
[Error Reporting & Diagnostic Clarity] The -diag substring check (arguments.Contains("-diag", StringComparison.OrdinalIgnoreCase)) was previously a benign false-positive (it silently skipped diag injection). Now it throws, which upgrades a harmless false-positive to a test failure.
An argument string like --logger:"trx;LogFileName=my-diagnostics.trx" would match -diag and throw unexpectedly. The check should be anchored to word boundaries or prefixes to avoid this:
// More precise: require the flag to appear as a standalone argument token
if (Regex.IsMatch(arguments, @"(^|\s)(--diag|/diag|-diag)[:=\s]", RegexOptions.IgnoreCase))Or at minimum, ensure the character before diag is a flag prefix character (-, /) rather than using a bare Contains. The current form is a pre-existing issue, but the severity escalation from "silent skip" to "throw" makes it worth fixing now.
There was a problem hiding this comment.
Fixed. Replaced the bare Contains checks with a Regex.IsMatch that anchors the flag to a word boundary — the flag must be preceded by whitespace or start-of-string, and followed by =, :, whitespace, or end-of-string. This prevents false positives from substrings like --logger:my-diagnostics.trx.
🔧 Iterated by PR Iteration Agent 🔧
Replaces bare Contains checks with a Regex that requires the flag (--diag, /diag, -diag) to appear as a standalone argument token, preventing false positives from substrings like '--logger:my-diagnostics.trx'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
nohwnd
left a comment
There was a problem hiding this comment.
🧠 Reviewed by Expert Code Reviewer
Dimensions activated: Acceptance Test Coverage Design, Error Reporting & Diagnostic Clarity
The prior inline finding (bare Contains("-diag") false-positive upgraded to a throw) was resolved in the second commit. The regex (^|\s)(--diag|/diag|-diag)([:=\s]|$) is correctly anchored — it requires the flag to be a standalone argument token and avoids false positives from substrings like --logger:my-diagnostics.trx.
No remaining concerns. All changes reviewed:
IsDiagAlreadyEnabledregex anchoring ✓GetDiagLogContents()with directory-exists guard ✓DiagLogsDirectoryproperty consistency with injection logic ✓- PR description accurately reflects all changes ✓
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
This comment has been minimized.
This comment has been minimized.
InvokeVsTest was appending --diag after the -- separator, which made it a runsettings parameter instead of a vstest.console argument. This broke all RunsettingsTests that use -- to pass inline runsettings. VaildateDataCollectorOutput was searching for files named 'diaglog*' but the auto-injected diag uses 'log.txt' as the base name. Count diag log files directly from DiagLogsDirectory instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Change base class to AcceptanceTestBase with [NetCoreRunner] attribute so the test actually runs (was dead code due to TargetFramework guard) - Fix diag log glob patterns: log.datacollector* not log.txt.datacollector* - Use FrameworkArgValue instead of hardcoded .NETCoreApp,Version=v2.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nohwnd
left a comment
There was a problem hiding this comment.
🧠 Reviewed by Expert Code Reviewer
New commits reviewed (since last review at edd2cc4):
-
ccaabcbe—--diaginsertion before--separator: logic is correct.arguments?.IndexOf(" -- ") ?? -1safely handles null, and insertingdiagArg(which has a leading space) atseparatorPosproduces well-formed argument strings liketest.dll --diag:logs/log.txt -- runsettings. ✓ -
3c028e3c— Coverlet test revival: switching toAcceptanceTestBase+[NetCoreRunner(HOST_NET)]correctly activates the previously-dead test. Glob patternslog.datacollector*andlog.host*are consistent withPath.ChangeExtension("log.txt", "datacollector....")naming.FrameworkArgValuereplaces the hardcoded.NETCoreApp,Version=v2.1. ✓
No new concerns. All changes since last review look correct.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
🤖 Expert vstest review (automated)This is a test-infrastructure-only change in 🛑 BlockersNone.
|
- Rename IsDiagAlreadyEnabled to ThrowIfDiagInArguments (void, throws) and IsDiagInEnvironment (bool env-var check), making the two-phase logic explicit - Include the matched flag value and full argument string in the thrown InvalidOperationException for easier CI diagnosis - Promote the diag flag regex to a static readonly Regex (compiled) to avoid re-parsing on every test invocation - Fix GetDiagLogContents to use TopDirectoryOnly instead of AllDirectories, matching the behavior of the per-class helpers it replaced - Add Friends.cs with InternalsVisibleTo so DiagFlagPattern is testable - Add DiagFlagPatternTests covering positive and negative regex cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
|
Addressed the review feedback: Issues fixed:
Suggestions addressed:
|
| protected static string GetDiagArg(string rootDir) | ||
| => " --diag:" + Path.Combine(rootDir, "log.txt"); |
| // We use netcoreapp runner | ||
| // "...\vstest\tools\dotnet\dotnet.exe "...\vstest\artifacts\Debug\net8.0\vstest.console.dll" --collect:"XPlat Code Coverage" ... | ||
| _testEnvironment.RunnerFramework = CoreRunnerFramework; | ||
| // "...\vstest\tools\dotnet\dotnet.exe "...\vstest\artifacts\Debug\net11.0\vstest.console.dll" --collect:"XPlat Code Coverage" ... | ||
| var resultsDir = new TempDirectory(); | ||
|
|
nohwnd
left a comment
There was a problem hiding this comment.
🧠 Reviewed by Expert Code Reviewer
New commit reviewed (since last review at 3c028e3c):
-
8966ad39— Review feedback addressing commit. All changes look correct:DiagFlagPatternas compiled staticRegex: avoids re-allocating the pattern on every test invocation. ✓ThrowIfDiagInArguments/IsDiagInEnvironmentsplit: names now clearly distinguish the two-phase "guard vs. env-var skip" logic. Thearguments ?? ""guard at theInvokeVsTestcall site handles null safely. ✓- Error message includes
match.Value.Trim()andarguments: gives actionable CI diagnostics. ✓ GetDiagLogContents→TopDirectoryOnly: matches the behavior of the per-class helpers it replaced;CountTestHostLogsdeliberately keepsAllDirectoriessince host log files can be nested. ✓Friends.cs+DiagFlagPatternTests:InternalsVisibleTocorrectly targetsMicrosoft.TestPlatform.Acceptance.IntegrationTestswith the public key; test coverage spans all positive/negative regex cases including the subtle--diagnostic(suffix after token) and--no-diag-mode(prefix before token) false-positive guards. ✓
No remaining concerns.
🧠 Reviewed by Expert Code Reviewer 🧠
🧠 Reviewed by Expert Code Reviewer 🧠
Fixes #15625.
Root Cause
Integration tests that passed
/diag(or--diag/-diag) manually in their arguments would causeIsDiagAlreadyEnabledto returntrue, silently skipping the auto-injection of diagnostic logging. This meant those tests would not have their diagnostic logs collected as attachments on failure — making failures harder to diagnose.What the Fix Does
IntegrationTestBase.cs:IsDiagAlreadyEnablednow throwsInvalidOperationExceptionwhen it detects a manual/diagargument, with a helpful message directing the test author to useDiagLogsDirectoryinstead.DiagLogsDirectoryproperty ({TempDirectory.Path}/logs) so tests can reference the auto-injected diag path.GetDiagLogContents()protected helper that reads all.txtlog files fromDiagLogsDirectory— promoted from several test classes that each had their own copy.InvokeVsTestandInvokeDotnetTestto useDiagLogsDirectorydirectly.All tests that manually passed
/diag: Updated to remove the manual argument. The auto-injection now ensures diag logs are always collected.Tests that checked diag file content (
ExecutionTests.cs,DataCollectorTests.Coverlet.cs,DataCollectionTests.cs): Updated to useDiagLogsDirectory-based search patterns instead of custom paths.CreateNoNewWindowTests.cs/SerializerSelectionTests.cs: Removed duplicateGetDiagLogContents()private methods — now using the base class version.Tests
The existing acceptance tests verify this behavior. The fix ensures diagnostic logs are always attached on test failure, which improves debuggability in CI.