Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check - #125348

Merged
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls
Mar 11, 2026
Merged

Fix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists check#125348
danmoseley merged 2 commits into
mainfrom
copilot/fix-guard-unmount-calls

Conversation

CopilotAI commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called MountHelper.Unmount(mountedDirName). When Directory.Delete removes the mount point directory as part of the delete operation, the subsequent Unmount call throws (Win32 error 4390/3), gets caught by the scenario's catch, and sets s_pass = false.

Changes

  • Delete_MountVolume.cs — Scenarios 1, 2, 3.1, 3.2 finally blocks: Wrap Unmount/DeleteDir calls with Directory.Exists(mountedDirName), matching the pattern already used in Scenarios 3.3–3.5.
// Beforefinally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}// Afterfinally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}
Original prompt

Problem

The Directory_Delete_MountVolume.RunTest() test in src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs fails because the finally blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally call MountHelper.Unmount(mountedDirName), but Directory.Delete may have already removed the mount point directory. When Unmount tries to call DeleteVolumeMountPoint on a path that no longer exists, it throws (Win32 error 4390 or 3), which gets caught by the scenario's catch block and sets s_pass = false, failing the test.

Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with if (Directory.Exists(mountedDirName)) before attempting to unmount. The fix is to apply the same pattern to the 4 finally blocks that are missing it.

Fix

Change the finally blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the Unmount and DeleteDir calls with Directory.Exists(mountedDirName), matching the existing pattern used in Scenarios 3.3–3.5.

There are exactly 4 finally blocks to change. Each one currently looks like:

finally{MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}

Each should be changed to:

finally{if(Directory.Exists(mountedDirName)){MountHelper.Unmount(mountedDirName);DeleteDir(mountedDirName,true);}}

The 4 locations in the file src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs are:

  1. Scenario 1 finally (around lines 80–84)
  2. Scenario 2 finally (around lines 125–129)
  3. Scenario 3.1 finally (around lines 167–171)
  4. Scenario 3.2 finally (around lines 209–213)

Do not change anything else. No changes to s_pass, Eval, method signatures, test structure, or any other lines. Only add the Directory.Exists guard to these 4 finally blocks.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: explain this test to me and what s_pass asserts

Assistant: ## Explanation of Delete_MountVolume.RunTest()

This is a Windows-specific test that verifies Directory.Delete works correctly when NTFS volume mount points are involved. A volume mount point is a directory that acts as an entry point to another volume (similar to a drive letter, but mapped to a folder).

What It Tests

The test runs 7 scenarios, all variations of the same theme — deleting directories that are accessed through or contain mounted volumes:

ScenarioDescription
1A different drive is mounted onto the current drive. Delete a directory on the other drive via the mount point.
2The current drive is mounted onto a different drive. Delete a directory on the current drive via the mount point on the other drive.
3.1The current drive is mounted onto itself (at the root). Delete a directory via that self-referencing mount.
3.2The current drive is mounted onto the current working directory. Delete via that mount.
3.3⚠️ Delete the mounted directory itself (the mount point). Verifies the mount point is removed without nuking the underlying volume.
3.4⚠️ Delete the parent directory of a mount point (parent also has other files/dirs). Verifies recursive delete handles mount points inside the tree.
3.5Same as 3.4 but targets a different subdirectory as the mount point within the tree.

The Pattern in Each Scenario

  1. Create a directory to serve as a mount point.
  2. Mount a volume onto that directory using MountHelper.Mount.
  3. Create a directory tree (files and subdirectories) on the source volume.
  4. Delete the tree — either via the mount path or by deleting a parent that contains the mount.
  5. Verify the directory no longer exists.
  6. Clean up — unmount and delete the mount directory in a finally block.

What s_pass Asserts

s_pass is a boolean accumulator for the overall test result. It starts as true and is set to false in two situations:

  1. Any Eval(...) call returns falseEval checks a condition (e.g., "directory was deleted") and sets s_pass = false if the condition fails, logging the error.
  2. Any scenario throws an unhandled exception — each catch block sets s_pass = false and logs the error.

At the very end, in the outermost finally:

Assert.True(s_pass);

This is a single assertion that summarizes all scenarios. If any scenario failed a condition or threw an exception, s_pass will be false and the test fails. This pattern was common in older test code — it lets the test run all scenarios even if one fails (rather than stopping at the first Assert failure), collecting all errors before reporting.

Why It's Disabled

The [ActiveIssue("https://github.com/dotnet/runtime/issues/14378")] attribute means this...

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…tory.Exists check
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix unmount calls in Directory_Delete_MountVolume testsFix Directory_Delete_MountVolume test: guard Unmount calls with Directory.Exists checkMar 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes intermittent failures in the Directory_Delete_MountVolume Windows/NTFS test by avoiding cleanup exceptions when the mount point directory has already been removed by Directory.Delete.

Changes:

  • Guard MountHelper.Unmount(mountedDirName) and DeleteDir(mountedDirName, true) in Scenarios 1, 2, 3.1, and 3.2 with Directory.Exists(mountedDirName).
  • Align cleanup behavior with the existing “only unmount if the mount point still exists” pattern already used in later scenarios.

@ViveliDuChViveliDuCh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'm still new to this area and I was only able to validate x64 scenarios (not ARM64) with a repro that simulates the gone-mount-point condition.

Thanks! @jozkee

@danmoseley
danmoseley merged commit de40271 into mainMar 11, 2026
96 checks passed
@danmoseley
danmoseley deleted the copilot/fix-guard-unmount-calls branch March 11, 2026 04:13
CopilotAI added a commit that referenced this pull request Mar 13, 2026
…tory.Exists check (#125348)
`finally` blocks in Scenarios 1, 2, 3.1, and 3.2 unconditionally called
`MountHelper.Unmount(mountedDirName)`. When `Directory.Delete` removes
the mount point directory as part of the delete operation, the
subsequent `Unmount` call throws (Win32 error 4390/3), gets caught by
the scenario's `catch`, and sets `s_pass = false`.
## Changes
- **`Delete_MountVolume.cs` — Scenarios 1, 2, 3.1, 3.2 `finally`
blocks**: Wrap `Unmount`/`DeleteDir` calls with
`Directory.Exists(mountedDirName)`, matching the pattern already used in
Scenarios 3.3–3.5.
```csharp
// Before
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
// After
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
## Problem
The `Directory_Delete_MountVolume.RunTest()` test in
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
fails because the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2
unconditionally call `MountHelper.Unmount(mountedDirName)`, but
`Directory.Delete` may have already removed the mount point directory.
When `Unmount` tries to call `DeleteVolumeMountPoint` on a path that no
longer exists, it throws (Win32 error 4390 or 3), which gets caught by
the scenario's `catch` block and sets `s_pass = false`, failing the
test.
Scenarios 3.3, 3.4, and 3.5 already correctly guard their cleanup with
`if (Directory.Exists(mountedDirName))` before attempting to unmount.
The fix is to apply the same pattern to the 4 `finally` blocks that are
missing it.
## Fix
Change the `finally` blocks in Scenarios 1, 2, 3.1, and 3.2 to guard the
`Unmount` and `DeleteDir` calls with `Directory.Exists(mountedDirName)`,
matching the existing pattern used in Scenarios 3.3–3.5.
There are exactly 4 `finally` blocks to change. Each one currently looks
like:
```csharp
finally
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
```
Each should be changed to:
```csharp
finally
{
if (Directory.Exists(mountedDirName))
{
MountHelper.Unmount(mountedDirName);
DeleteDir(mountedDirName, true);
}
}
```
The 4 locations in the file
`src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/Delete_MountVolume.cs`
are:
1. **Scenario 1 finally** (around lines 80–84)
2. **Scenario 2 finally** (around lines 125–129)
3. **Scenario 3.1 finally** (around lines 167–171)
4. **Scenario 3.2 finally** (around lines 209–213)
**Do not change anything else.** No changes to `s_pass`, `Eval`, method
signatures, test structure, or any other lines. Only add the
`Directory.Exists` guard to these 4 `finally` blocks.
The following is the prior conversation context from the user's chat
exploration (may be truncated):
User: explain this test to me and what s_pass asserts
Assistant: ## Explanation of `Delete_MountVolume.RunTest()`
This is a Windows-specific test that verifies `Directory.Delete` works
correctly when **NTFS volume mount points** are involved. A volume mount
point is a directory that acts as an entry point to another volume
(similar to a drive letter, but mapped to a folder).
### What It Tests
The test runs **7 scenarios**, all variations of the same theme —
deleting directories that are accessed through or contain mounted
volumes:
| Scenario | Description |
|----------|-------------|
| **1** | A *different* drive is mounted onto the current drive. Delete
a directory on the other drive *via* the mount point. |
| **2** | The *current* drive is mounted onto a different drive. Delete
a directory on the current drive *via* the mount point on the other
drive. |
| **3.1** | The current drive is mounted onto *itself* (at the root).
Delete a directory via that self-referencing mount. |
| **3.2** | The current drive is mounted onto the *current working
directory*. Delete via that mount. |
| **3.3** | ⚠️ Delete the **mounted directory itself** (the mount
point). Verifies the mount point is removed without nuking the
underlying volume. |
| **3.4** | ⚠️ Delete the **parent directory** of a mount point (parent
also has other files/dirs). Verifies recursive delete handles mount
points inside the tree. |
| **3.5** | Same as 3.4 but targets a *different* subdirectory as the
mount point within the tree. |
### The Pattern in Each Scenario
1. **Create** a directory to serve as a mount point.
2. **Mount** a volume onto that directory using `MountHelper.Mount`.
3. **Create** a directory tree (files and subdirectories) on the source
volume.
4. **Delete** the tree — either via the mount path or by deleting a
parent that contains the mount.
5. **Verify** the directory no longer exists.
6. **Clean up** — unmount and delete the mount directory in a `finally`
block.
### What `s_pass` Asserts
`s_pass` is a **boolean accumulator** for the overall test result. It
starts as `true` and is set to `false` in two situations:
1. **Any `Eval(...)` call returns `false`** — `Eval` checks a condition
(e.g., "directory was deleted") and sets `s_pass = false` if the
condition fails, logging the error.
2. **Any scenario throws an unhandled exception** — each `catch` block
sets `s_pass = false` and logs the error.
At the very end, in the outermost `finally`:
```csharp
Assert.True(s_pass);
```
This is a **single assertion that summarizes all scenarios**. If *any*
scenario failed a condition or threw an exception, `s_pass` will be
`false` and the test fails. This pattern was common in older test code —
it lets the test run all scenarios even if one fails (rather than
stopping at the first `Assert` failure), collecting all errors before
reporting.
### Why It's Disabled
The `[ActiveIssue("https://github.com/dotnet/runtime/issues/14378")]`
attribute means this...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
*This pull request was created from Copilot chat.*
>
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
CopilotAI added a commit that referenced this pull request Mar 16, 2026
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>
danmoseley pushed a commit that referenced this pull request Apr 1, 2026
…s_MountVolume
The finally blocks in all 4 scenarios unconditionally call
MountHelper.Unmount, but the mount point directory may no longer
exist (e.g. the test deleted it, or Mount never succeeded after
CreateDirectory). This causes Win32 errors 4390/3 that propagate
out and fail the test.
Add Directory.Exists guards, matching the pattern already applied
to Delete_MountVolume.cs in PR #125348.
Fixes#125624
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Apr 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System.IO.Tests.Directory_Delete_MountVolume.RunTest failure

5 participants

@danmoseley@ViveliDuCh@jozkee