Fix flaky mount volume tests: refactor into separate test methods with typed Win32 exception handling#125625
Fix flaky mount volume tests: refactor into separate test methods with typed Win32 exception handling#125625
Conversation
…rectory.Exists - ReparsePoints_MountVolume.cs (fix #125624): 4 finally blocks in Scenarios 1, 2, 3.1, 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When the mount point was already removed the call threw (Win32 error 4390/3), got caught by the scenario catch, and set s_pass=false. Wrap with Directory.Exists guard. - Delete_MountVolume.cs (remaining issue in #125295): Scenarios 3.3, 3.4, 3.5 used if (!Eval(!Directory.Exists(mountedDirName), ...)) in finally blocks, which set s_pass=false during cleanup when Directory.Delete left the mount point around. Change to plain if (Directory.Exists(mountedDirName)) guard. For Scenario 3.3 (which had no assertion in the try block), move the Eval assertion into the try block after Task.Delay(300).Wait(). Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
|
Tagging subscribers to this area: @dotnet/area-system-io |
|
@copilot can you double-check that the changes will fix the failed scenarios. Here's the console logs for both issues: |
The Helix log SAS tokens in your comment have expired (1-hour window) so I can't fetch them, but here's my code-level analysis: Log 1 — The 4 Log 2 — Scenarios 3.3–3.5 used My fix removes the Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@copilot the allowlist was updated, can you try #125625 (comment) again. |
The Directory.Exists guard (from PR #125348) is insufficient when MountHelper.Mount fails after Directory.CreateDirectory succeeds: the directory exists but is not a mount point, so DeleteVolumeMountPoint throws Win32 error 4390. This exception escapes the finally block, gets caught by the scenario catch, and sets s_pass = false. Confirmed by the console log (refs/heads/main, 2026-03-15, after PR #125348): Scenario 1 of Delete_MountVolume still failing with error 4390. Wrap all MountHelper.Unmount calls in `try { } catch { }` so that cleanup exceptions never escape the finally block: - Delete_MountVolume.cs: scenarios 1, 2, 3.1, 3.2, 3.3, 3.4, 3.5 - ReparsePoints_MountVolume.cs: scenarios 1, 2, 3.1, 3.2 Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
After fetching the first log, I found the root cause: the When Fix (commit Changed 11
|
|
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
This PR reduces Windows/ARM64 test flakiness in mount-volume directory tests by preventing cleanup (finally) failures from incorrectly failing the test scenarios.
Changes:
- Wrap
MountHelper.Unmountinsidetry/catchwithinfinallyblocks to prevent cleanup exceptions from failing scenarios. - Keep
Directory.Existsguards to skip cleanup when the mount directory is already removed. - Restructure scenario 3.3 cleanup/validation in
Delete_MountVolume.csby moving theEvalassertion out offinally.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/ReparsePoints_MountVolume.cs | Makes cleanup robust by swallowing Unmount exceptions when the dir exists but isn’t a mount point. |
| src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs | Applies the same robust cleanup pattern across scenarios and moves an Eval check into the main scenario flow. |
| DeleteDir(mountedDirName, true); | ||
| if (Directory.Exists(mountedDirName)) | ||
| { | ||
| try { MountHelper.Unmount(mountedDirName); } catch { } |
|
|
||
| Directory.Delete(mountedDirName, true); | ||
| Task.Delay(300).Wait(); | ||
| Eval(!Directory.Exists(mountedDirName), "Err_001yph! Directory {0} still exist: {1}", mountedDirName, Directory.Exists(mountedDirName)); |
|
@copilot address feedback and also take a look at this other log showing another error Fix the code to don't eat up exceptions and also break down the scenarios in |
…32 exception handling
- Update MountHelper.Unmount to throw Win32Exception instead of Exception, enabling typed
exception filtering by NativeErrorCode in callers
- Replace all bare `catch { }` cleanup handlers with
`catch (Win32Exception ex) when (ex.NativeErrorCode is 4390 or 3)`:
- 4390 = ERROR_NOT_A_REPARSE_POINT (dir exists but was never mounted, or partially cleaned)
- 3 = ERROR_PATH_NOT_FOUND (path on a drive that's no longer accessible)
- Break ReparsePoints_MountVolume.cs single RunTest into 4 separate ConditionalFact methods
(Scenario1-2 require another NTFS drive; Scenario31-32 require current drive to be NTFS)
- Break Delete_MountVolume.cs single RunTest into 7 separate ConditionalFact methods
(Scenario1 requires IsNtfs+OtherNtfsDrive; Scenario2 requires OtherNtfsDrive;
Scenario31-35 require current drive to be NTFS)
- Remove s_pass/Eval pattern; replace with Assert.True/False directly in test methods
- Remove debug file infrastructure; Console output is sufficient for CI diagnostics
- Update Delete.Windows.cs cleanup similarly
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
Done in commit
The Win32 error 3 case shown in the log (Scenario 2 cleanup failing because the E: drive path |
finallycleanup blocks in bothReparsePoints_MountVolume.csandDelete_MountVolume.cscould cause intermittent CI failures on Windows/ARM64 by either settings_pass = falseduring cleanup or swallowing unexpected exceptions. Helix logs confirmed failures with Win32 errors 4390 ("not a reparse point") and 3 ("path not found") escaping cleanup and failing test scenarios.The root cause is that
MountHelper.Unmountthrows whenever the directory exists but is not a valid mount point. This happens in two scenarios:MountHelper.Mountfails afterDirectory.CreateDirectoryhas already created the directory — the directory exists but was never made a mount point.Description
ReparsePointUtilities.csChanged
MountHelper.Unmountto throwWin32Exceptioninstead of rawException. This enables typed exception filtering byNativeErrorCodeat all call sites, avoiding fragile message-string parsing.ReparsePoints_MountVolume.csandDelete_MountVolume.csThe single monolithic
RunTest()/runTest()methods have been broken into separate[ConditionalFact]test methods per scenario:ReparsePoints_MountVolume.cs: 4 separate methods (Scenario1_DifferentDriveMountedOnCurrentDrive,Scenario2_CurrentDriveMountedOnOtherDrive,Scenario31_CurrentDriveMountedOnCurrentDrive,Scenario32_CurrentDriveMountedOnCurrentDirectory), each with its own condition (IsNtfsWithOtherNtfsDrive,HasOtherNtfsDrive, orIsNtfs).Delete_MountVolume.cs: 7 separate methods (Scenario1throughScenario35), same per-scenario conditional attributes.The
s_pass/Evalpattern has been removed and replaced withAssert.True/Assert.Falsedirectly in each test method, eliminating shared mutable state between scenarios.All cleanup
finallyblocks now use a specific exception filter instead of barecatch { }:4390=ERROR_NOT_A_REPARSE_POINT— directory exists but is not a mount point (Mount never succeeded or binding already removed)3=ERROR_PATH_NOT_FOUND— cleanup path is on a drive that is no longer accessibleAny other exception propagates normally so genuine issues are not silently ignored.
Delete.Windows.csThe same specific exception filter applied to the existing
Delete_VolumeMountPointcleanup block.Original prompt
Problem
The
Directory_ReparsePoints_MountVolume.runTest()test insrc/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/ReparsePoints_MountVolume.csfails (see issue #125624) because thefinallyblocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally callMountHelper.Unmount(mountedDirName), but the mount point directory may have already been removed. WhenUnmounttries to callDeleteVolumeMountPointon a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario'scatchblock and setss_pass = false, failing the test.This is the exact same root cause that was fixed for the sibling test
Directory_Delete_MountVolumein PR #125348, which was merged on 2026-03-11.Fix
Change the
finallyblocks in Scenarios 1, 2, 3.1, and 3.2 to guard theUnmountandDeleteDircalls withDirectory.Exists(mountedDirName), matching the pattern already applied in PR #125348 toDelete_MountVolume.cs.There are exactly 4
finallyblocks to change insrc/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/ReparsePoints_MountVolume.cs. Each one currently looks like:Each should be changed to:
The 4 locations in the file are:
Do not change anything else. No changes to
s_pass,Eval, method signatures, test structure, or any other lines. Only add theDirectory.Existsguard to these 4finallyblocks.Fixes #125624
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Can you verify if there's a similar root cause to @dotnet/runtime/issues/125624 as @dotnet/runtime/pull/125348
Assistant:
Let me investigate both the issue and the pull request to understand their root causes.
Assistant: Now let me look at the actual test file that's failing in issue #125624 — the
Directory_ReparsePoints_MountVolumetest — to see if it has the same pattern:Assistant: Yes, the root cause is essentially identical. Here's the analysis:
Same Root Cause
Both
Directory_ReparsePoints_MountVolume.runTest()(issue #125624) andDirectory_Delete_MountVolume.RunTest()(fixed by PR #125348) share the exact same bug pattern in the same test directory:The pattern
The
finallyblocks inReparsePoints_MountVolume.csunconditionally callMountHelper.Unmount(mountedDirName)without first checking whether the mount point directory still exists. If the directory has already been removed (or is in a bad state),Unmountthrows a Win32 exception, which gets caught by the scenario'scatchblock and setss_pass = false, failing the test.Affected
finallyblocks inReparsePoints_MountVolume.csThere are 4
finallyblocks that need the same fix — at lines 110–114, 187–191, 262–266, and 337–341:What PR #125348 did for
Delete_MountVolume.csPR #125348 wrapped the same unguarded
Unmount/DeleteDircalls with aDirectory.Existscheck inDelete_MountVolume.cs:The fix needed for #125624
The exact same
Directory.Existsguard needs to be applied to the 4finallyblocks inReparsePoints_MountVolume.cs(Scenarios 1, 2, 3.1, and 3.2). The two test files are structurally almost identical — they're sibling tests for mount volume operations — and the bug is the same: unguarded cleanup that throws when the mount point is already gone.Would you like me to create a PR to apply this fix?
User: yes please
Assistant: I've started the process to create a pull request in
dotnet/runtime. The coding agent is now working on applying theDirectory.Existsguard to the 4finallyblocks in `ReparsePoints_MountVolum...This pull request was created from Copilot chat.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.