diff --git a/src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs b/src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs
new file mode 100644
index 0000000000000..9a221a2488c7c
--- /dev/null
+++ b/src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.Versioning;
+
+namespace System.IO
+{
+ // Adds test helper APIs to manipulate Windows virtual drives via SUBST.
+ [SupportedOSPlatform("windows")]
+ public class VirtualDriveHelper : IDisposable
+ {
+ // Temporary Windows directory that can be mounted to a drive letter using the subst command
+ private string? _virtualDriveTargetDir = null;
+ // Windows drive letter that points to a mounted directory using the subst command
+ private char _virtualDriveLetter = default;
+
+ ///
+ /// If there is a SUBST'ed drive, Dispose unmounts it to free the drive letter.
+ ///
+ public void Dispose()
+ {
+ try
+ {
+ if (VirtualDriveLetter != default)
+ {
+ DeleteVirtualDrive(VirtualDriveLetter);
+ Directory.Delete(VirtualDriveTargetDir, recursive: true);
+ }
+ }
+ catch { } // avoid exceptions on dispose
+ }
+
+ ///
+ /// Returns the path of a folder that is to be mounted using SUBST.
+ ///
+ public string VirtualDriveTargetDir
+ {
+ get
+ {
+ if (_virtualDriveTargetDir == null)
+ {
+ // Create a folder inside the temp directory so that it can be mounted to a drive letter with subst
+ _virtualDriveTargetDir = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
+ Directory.CreateDirectory(_virtualDriveTargetDir);
+ }
+
+ return _virtualDriveTargetDir;
+ }
+ }
+
+ ///
+ /// Returns the drive letter of a drive letter that represents a mounted folder using SUBST.
+ ///
+ public char VirtualDriveLetter
+ {
+ get
+ {
+ if (_virtualDriveLetter == default)
+ {
+ // Mount the folder to a drive letter
+ _virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir);
+ }
+ return _virtualDriveLetter;
+ }
+ }
+
+ ///
+ /// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
+ /// subst is not available in Windows Nano.
+ ///
+ private static char CreateVirtualDrive(string targetDir)
+ {
+ char driveLetter = GetNextAvailableDriveLetter();
+ bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir));
+ if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
+ {
+ throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst");
+ }
+ return driveLetter;
+
+ // Finds the next unused drive letter and returns it.
+ char GetNextAvailableDriveLetter()
+ {
+ List existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();
+
+ // A,B are reserved, C is usually reserved
+ IEnumerable range = Enumerable.Range('D', 'Z' - 'D');
+ IEnumerable castRange = range.Select(x => Convert.ToChar(x));
+ IEnumerable allDrivesLetters = castRange.Except(existingDrives);
+
+ if (!allDrivesLetters.Any())
+ {
+ throw new ArgumentOutOfRangeException("No drive letters available");
+ }
+
+ return allDrivesLetters.First();
+ }
+ }
+
+ ///
+ /// On Windows, unassigns the specified virtual drive letter from its mounted folder.
+ ///
+ private static void DeleteVirtualDrive(char driveLetter)
+ {
+ bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:"));
+ if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
+ {
+ throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst");
+ }
+ }
+
+ private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments)
+ {
+ var info = new ProcessStartInfo
+ {
+ FileName = fileName,
+ UseShellExecute = false,
+ RedirectStandardOutput = true
+ };
+
+ foreach (var argument in arguments)
+ {
+ info.ArgumentList.Add(argument);
+ }
+
+ return info;
+ }
+
+ private static bool RunProcess(ProcessStartInfo startInfo)
+ {
+ using var process = Process.Start(startInfo);
+ process.WaitForExit();
+ return process.ExitCode == 0;
+ }
+
+ private static string SubstPath
+ {
+ get
+ {
+ string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
+ return Path.Join(systemRoot, "System32", "subst.exe");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Unix.Tests.cs b/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Unix.Tests.cs
index 11c6f45cab9a7..e7723229c0d2d 100644
--- a/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Unix.Tests.cs
+++ b/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Unix.Tests.cs
@@ -5,7 +5,7 @@
using System.Linq;
using Xunit;
-namespace System.IO.FileSystem.DriveInfoTests
+namespace System.IO.FileSystem.Tests
{
public partial class DriveInfoUnixTests
{
diff --git a/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Windows.Tests.cs b/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Windows.Tests.cs
index c52980953e9e0..06dee252d848e 100644
--- a/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Windows.Tests.cs
+++ b/src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Windows.Tests.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
-using Xunit;
using System.Text;
+using Xunit;
-namespace System.IO.FileSystem.DriveInfoTests
+namespace System.IO.FileSystem.Tests
{
+ [PlatformSpecific(TestPlatforms.Windows)]
public class DriveInfoWindowsTests
{
[Theory]
@@ -35,7 +34,6 @@ public void Ctor_InvalidPath_ThrowsArgumentException(string driveName)
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestConstructor()
{
string[] variableInput = { "{0}", "{0}", "{0}:", "{0}:", @"{0}:\", @"{0}:\\", "{0}://" };
@@ -54,7 +52,6 @@ public void TestConstructor()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestGetDrives()
{
var validExpectedDrives = GetValidDriveLettersOnMachine();
@@ -97,7 +94,6 @@ public void TestDriveProperties_AppContainer()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestDriveFormat()
{
DriveInfo validDrive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -124,7 +120,6 @@ public void TestDriveFormat()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestDriveType()
{
var validDrive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -137,7 +132,6 @@ public void TestDriveType()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestValidDiskSpaceProperties()
{
bool win32Result;
@@ -169,7 +163,6 @@ public void TestValidDiskSpaceProperties()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void TestInvalidDiskProperties()
{
string invalidDriveName = GetInvalidDriveLettersOnMachine().First().ToString();
@@ -189,7 +182,6 @@ public void TestInvalidDiskProperties()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void GetVolumeLabel_Returns_CorrectLabel()
{
void DoDriveCheck()
@@ -225,7 +217,6 @@ void DoDriveCheck()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void SetVolumeLabel_Roundtrips()
{
DriveInfo drive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -246,7 +237,6 @@ public void SetVolumeLabel_Roundtrips()
}
[Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
public void VolumeLabelOnNetworkOrCdRom_Throws()
{
// Test setting the volume label on a Network or CD-ROM
diff --git a/src/libraries/System.IO.FileSystem.DriveInfo/tests/System.IO.FileSystem.DriveInfo.Tests.csproj b/src/libraries/System.IO.FileSystem.DriveInfo/tests/System.IO.FileSystem.DriveInfo.Tests.csproj
index be64739138482..3d282e1aea7cd 100644
--- a/src/libraries/System.IO.FileSystem.DriveInfo/tests/System.IO.FileSystem.DriveInfo.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem.DriveInfo/tests/System.IO.FileSystem.DriveInfo.Tests.csproj
@@ -5,5 +5,7 @@
+
+
\ No newline at end of file
diff --git a/src/libraries/System.IO.FileSystem.DriveInfo/tests/VirtualDrives.Windows.Tests.cs b/src/libraries/System.IO.FileSystem.DriveInfo/tests/VirtualDrives.Windows.Tests.cs
new file mode 100644
index 0000000000000..57fe6141fc8a9
--- /dev/null
+++ b/src/libraries/System.IO.FileSystem.DriveInfo/tests/VirtualDrives.Windows.Tests.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using Xunit;
+
+namespace System.IO.FileSystem.Tests
+{
+ // Separate class from the rest of the DriveInfo tests to prevent adding an extra virtual drive to GetDrives().
+ public class DriveInfoVirtualDriveTests
+ {
+ // Cannot set the volume label on a SUBST'ed folder
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public void SetVolumeLabel_OnVirtualDrive_Throws()
+ {
+ using VirtualDriveHelper virtualDrive = new();
+ char letter = virtualDrive.VirtualDriveLetter; // Trigger calling subst
+ DriveInfo drive = DriveInfo.GetDrives().Where(d => d.RootDirectory.FullName[0] == letter).FirstOrDefault();
+ Assert.NotNull(drive);
+ Assert.Throws(() => drive.VolumeLabel = "impossible");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs
index 8c7f3608d35ce..87c96cfed7916 100644
--- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs
+++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs
@@ -61,61 +61,6 @@ public static bool CreateJunction(string junctionPath, string targetPath)
return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath));
}
- ///
- /// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
- /// subst is not available in Windows Nano.
- ///
- public static char CreateVirtualDrive(string targetDir)
- {
- if (!OperatingSystem.IsWindows())
- {
- throw new PlatformNotSupportedException();
- }
-
- char driveLetter = GetNextAvailableDriveLetter();
- bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir));
- if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
- {
- throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst");
- }
- return driveLetter;
-
- // Finds the next unused drive letter and returns it.
- char GetNextAvailableDriveLetter()
- {
- List existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();
-
- // A,B are reserved, C is usually reserved
- IEnumerable range = Enumerable.Range('D', 'Z' - 'D');
- IEnumerable castRange = range.Select(x => Convert.ToChar(x));
- IEnumerable allDrivesLetters = castRange.Except(existingDrives);
-
- if (!allDrivesLetters.Any())
- {
- throw new ArgumentOutOfRangeException("No drive letters available");
- }
-
- return allDrivesLetters.First();
- }
- }
-
- ///
- /// On Windows, unassigns the specified virtual drive letter from its mounted folder.
- ///
- public static void DeleteVirtualDrive(char driveLetter)
- {
- if (!OperatingSystem.IsWindows())
- {
- throw new PlatformNotSupportedException();
- }
-
- bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:"));
- if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
- {
- throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst");
- }
- }
-
public static void Mount(string volumeName, string mountPoint)
{
if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar)
@@ -173,21 +118,6 @@ private static bool RunProcess(ProcessStartInfo startInfo)
return process.ExitCode == 0;
}
- private static string SubstPath
- {
- get
- {
- if (!OperatingSystem.IsWindows())
- {
- throw new PlatformNotSupportedException();
- }
-
- string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
- string system32 = Path.Join(systemRoot, "System32");
- return Path.Join(system32, "subst.exe");
- }
- }
-
/// For standalone debugging help. Change Main0 to Main
public static void Main0(string[] args)
{
diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
index abf73ff35986c..5180426ca1044 100644
--- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
+++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
@@ -97,6 +97,7 @@
+
diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs
index d88425086b702..fb9997e6ab17e 100644
--- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs
+++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs
@@ -11,17 +11,11 @@ namespace System.IO.Tests
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))]
public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks
{
+ private VirtualDriveHelper VirtualDrive { get; } = new VirtualDriveHelper();
+
protected override void Dispose(bool disposing)
{
- try
- {
- if (VirtualDriveLetter != default)
- {
- MountHelper.DeleteVirtualDrive(VirtualDriveLetter);
- Directory.Delete(VirtualDriveTargetDir, recursive: true);
- }
- }
- catch { } // avoid exceptions on dispose
+ VirtualDrive.Dispose();
base.Dispose(disposing);
}
@@ -210,38 +204,6 @@ public void VirtualDrive_SymbolicLinks_WithIndirection(
Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName);
}
- private string GetVirtualOrRealPath(bool condition) => condition ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir;
-
- // Temporary Windows directory that can be mounted to a drive letter using the subst command
- private string? _virtualDriveTargetDir = null;
- private string VirtualDriveTargetDir
- {
- get
- {
- if (_virtualDriveTargetDir == null)
- {
- // Create a folder inside the temp directory so that it can be mounted to a drive letter with subst
- _virtualDriveTargetDir = Path.Join(Path.GetTempPath(), GetRandomDirName());
- Directory.CreateDirectory(_virtualDriveTargetDir);
- }
-
- return _virtualDriveTargetDir;
- }
- }
-
- // Windows drive letter that points to a mounted directory using the subst command
- private char _virtualDriveLetter = default;
- private char VirtualDriveLetter
- {
- get
- {
- if (_virtualDriveLetter == default)
- {
- // Mount the folder to a drive letter
- _virtualDriveLetter = MountHelper.CreateVirtualDrive(VirtualDriveTargetDir);
- }
- return _virtualDriveLetter;
- }
- }
+ private string GetVirtualOrRealPath(bool condition) => condition ? $"{VirtualDrive.VirtualDriveLetter}:" : VirtualDrive.VirtualDriveTargetDir;
}
}