Skip to content

Commit a2af8ae

Browse files
authored
Re-enable Windows test that verifies DriveInfo.VolumeLabel setter fails on SUBST'd drive (#59850)
* Re-enable test that verifies DriveInfo.VolumeLabel setter fails on SUBST'd drive
1 parent db6f5ce commit a2af8ae

File tree

8 files changed

+188
-126
lines changed

8 files changed

+188
-126
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Runtime.Versioning;
8+
9+
namespace System.IO
10+
{
11+
// Adds test helper APIs to manipulate Windows virtual drives via SUBST.
12+
[SupportedOSPlatform("windows")]
13+
public class VirtualDriveHelper : IDisposable
14+
{
15+
// Temporary Windows directory that can be mounted to a drive letter using the subst command
16+
private string? _virtualDriveTargetDir = null;
17+
// Windows drive letter that points to a mounted directory using the subst command
18+
private char _virtualDriveLetter = default;
19+
20+
/// <summary>
21+
/// If there is a SUBST'ed drive, Dispose unmounts it to free the drive letter.
22+
/// </summary>
23+
public void Dispose()
24+
{
25+
try
26+
{
27+
if (VirtualDriveLetter != default)
28+
{
29+
DeleteVirtualDrive(VirtualDriveLetter);
30+
Directory.Delete(VirtualDriveTargetDir, recursive: true);
31+
}
32+
}
33+
catch { } // avoid exceptions on dispose
34+
}
35+
36+
/// <summary>
37+
/// Returns the path of a folder that is to be mounted using SUBST.
38+
/// </summary>
39+
public string VirtualDriveTargetDir
40+
{
41+
get
42+
{
43+
if (_virtualDriveTargetDir == null)
44+
{
45+
// Create a folder inside the temp directory so that it can be mounted to a drive letter with subst
46+
_virtualDriveTargetDir = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
47+
Directory.CreateDirectory(_virtualDriveTargetDir);
48+
}
49+
50+
return _virtualDriveTargetDir;
51+
}
52+
}
53+
54+
/// <summary>
55+
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST.
56+
/// </summary>
57+
public char VirtualDriveLetter
58+
{
59+
get
60+
{
61+
if (_virtualDriveLetter == default)
62+
{
63+
// Mount the folder to a drive letter
64+
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir);
65+
}
66+
return _virtualDriveLetter;
67+
}
68+
}
69+
70+
///<summary>
71+
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
72+
/// subst is not available in Windows Nano.
73+
/// </summary>
74+
private static char CreateVirtualDrive(string targetDir)
75+
{
76+
char driveLetter = GetNextAvailableDriveLetter();
77+
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir));
78+
if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
79+
{
80+
throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst");
81+
}
82+
return driveLetter;
83+
84+
// Finds the next unused drive letter and returns it.
85+
char GetNextAvailableDriveLetter()
86+
{
87+
List<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();
88+
89+
// A,B are reserved, C is usually reserved
90+
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D');
91+
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x));
92+
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives);
93+
94+
if (!allDrivesLetters.Any())
95+
{
96+
throw new ArgumentOutOfRangeException("No drive letters available");
97+
}
98+
99+
return allDrivesLetters.First();
100+
}
101+
}
102+
103+
/// <summary>
104+
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
105+
/// </summary>
106+
private static void DeleteVirtualDrive(char driveLetter)
107+
{
108+
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:"));
109+
if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
110+
{
111+
throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst");
112+
}
113+
}
114+
115+
private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments)
116+
{
117+
var info = new ProcessStartInfo
118+
{
119+
FileName = fileName,
120+
UseShellExecute = false,
121+
RedirectStandardOutput = true
122+
};
123+
124+
foreach (var argument in arguments)
125+
{
126+
info.ArgumentList.Add(argument);
127+
}
128+
129+
return info;
130+
}
131+
132+
private static bool RunProcess(ProcessStartInfo startInfo)
133+
{
134+
using var process = Process.Start(startInfo);
135+
process.WaitForExit();
136+
return process.ExitCode == 0;
137+
}
138+
139+
private static string SubstPath
140+
{
141+
get
142+
{
143+
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
144+
return Path.Join(systemRoot, "System32", "subst.exe");
145+
}
146+
}
147+
}
148+
}

src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Unix.Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Linq;
66
using Xunit;
77

8-
namespace System.IO.FileSystem.DriveInfoTests
8+
namespace System.IO.FileSystem.Tests
99
{
1010
public partial class DriveInfoUnixTests
1111
{

src/libraries/System.IO.FileSystem.DriveInfo/tests/DriveInfo.Windows.Tests.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.IO;
64
using System.Collections;
75
using System.Collections.Generic;
86
using System.Linq;
97
using System.Runtime.InteropServices;
108
using System.Security;
11-
using Xunit;
129
using System.Text;
10+
using Xunit;
1311

14-
namespace System.IO.FileSystem.DriveInfoTests
12+
namespace System.IO.FileSystem.Tests
1513
{
14+
[PlatformSpecific(TestPlatforms.Windows)]
1615
public class DriveInfoWindowsTests
1716
{
1817
[Theory]
@@ -35,7 +34,6 @@ public void Ctor_InvalidPath_ThrowsArgumentException(string driveName)
3534
}
3635

3736
[Fact]
38-
[PlatformSpecific(TestPlatforms.Windows)]
3937
public void TestConstructor()
4038
{
4139
string[] variableInput = { "{0}", "{0}", "{0}:", "{0}:", @"{0}:\", @"{0}:\\", "{0}://" };
@@ -54,7 +52,6 @@ public void TestConstructor()
5452
}
5553

5654
[Fact]
57-
[PlatformSpecific(TestPlatforms.Windows)]
5855
public void TestGetDrives()
5956
{
6057
var validExpectedDrives = GetValidDriveLettersOnMachine();
@@ -97,7 +94,6 @@ public void TestDriveProperties_AppContainer()
9794
}
9895

9996
[Fact]
100-
[PlatformSpecific(TestPlatforms.Windows)]
10197
public void TestDriveFormat()
10298
{
10399
DriveInfo validDrive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -124,7 +120,6 @@ public void TestDriveFormat()
124120
}
125121

126122
[Fact]
127-
[PlatformSpecific(TestPlatforms.Windows)]
128123
public void TestDriveType()
129124
{
130125
var validDrive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -137,7 +132,6 @@ public void TestDriveType()
137132
}
138133

139134
[Fact]
140-
[PlatformSpecific(TestPlatforms.Windows)]
141135
public void TestValidDiskSpaceProperties()
142136
{
143137
bool win32Result;
@@ -169,7 +163,6 @@ public void TestValidDiskSpaceProperties()
169163
}
170164

171165
[Fact]
172-
[PlatformSpecific(TestPlatforms.Windows)]
173166
public void TestInvalidDiskProperties()
174167
{
175168
string invalidDriveName = GetInvalidDriveLettersOnMachine().First().ToString();
@@ -189,7 +182,6 @@ public void TestInvalidDiskProperties()
189182
}
190183

191184
[Fact]
192-
[PlatformSpecific(TestPlatforms.Windows)]
193185
public void GetVolumeLabel_Returns_CorrectLabel()
194186
{
195187
void DoDriveCheck()
@@ -225,7 +217,6 @@ void DoDriveCheck()
225217
}
226218

227219
[Fact]
228-
[PlatformSpecific(TestPlatforms.Windows)]
229220
public void SetVolumeLabel_Roundtrips()
230221
{
231222
DriveInfo drive = DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed).First();
@@ -246,7 +237,6 @@ public void SetVolumeLabel_Roundtrips()
246237
}
247238

248239
[Fact]
249-
[PlatformSpecific(TestPlatforms.Windows)]
250240
public void VolumeLabelOnNetworkOrCdRom_Throws()
251241
{
252242
// Test setting the volume label on a Network or CD-ROM

src/libraries/System.IO.FileSystem.DriveInfo/tests/System.IO.FileSystem.DriveInfo.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
<ItemGroup>
66
<Compile Include="DriveInfo.Unix.Tests.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'" />
77
<Compile Include="DriveInfo.Windows.Tests.cs" Condition="'$(TargetsWindows)' == 'true'" />
8+
<Compile Include="VirtualDrives.Windows.Tests.cs" Condition="'$(TargetsWindows)' == 'true'" />
9+
<Compile Include="$(CommonTestPath)System\IO\VirtualDriveHelper.Windows.cs" Link="Common\System\IO\VirtualDriveHelper.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
810
</ItemGroup>
911
</Project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.InteropServices;
8+
using System.Security;
9+
using System.Text;
10+
using Xunit;
11+
12+
namespace System.IO.FileSystem.Tests
13+
{
14+
// Separate class from the rest of the DriveInfo tests to prevent adding an extra virtual drive to GetDrives().
15+
public class DriveInfoVirtualDriveTests
16+
{
17+
// Cannot set the volume label on a SUBST'ed folder
18+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))]
19+
[PlatformSpecific(TestPlatforms.Windows)]
20+
public void SetVolumeLabel_OnVirtualDrive_Throws()
21+
{
22+
using VirtualDriveHelper virtualDrive = new();
23+
char letter = virtualDrive.VirtualDriveLetter; // Trigger calling subst
24+
DriveInfo drive = DriveInfo.GetDrives().Where(d => d.RootDirectory.FullName[0] == letter).FirstOrDefault();
25+
Assert.NotNull(drive);
26+
Assert.Throws<IOException>(() => drive.VolumeLabel = "impossible");
27+
}
28+
}
29+
}

src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -61,61 +61,6 @@ public static bool CreateJunction(string junctionPath, string targetPath)
6161
return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath));
6262
}
6363

64-
///<summary>
65-
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
66-
/// subst is not available in Windows Nano.
67-
/// </summary>
68-
public static char CreateVirtualDrive(string targetDir)
69-
{
70-
if (!OperatingSystem.IsWindows())
71-
{
72-
throw new PlatformNotSupportedException();
73-
}
74-
75-
char driveLetter = GetNextAvailableDriveLetter();
76-
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir));
77-
if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
78-
{
79-
throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst");
80-
}
81-
return driveLetter;
82-
83-
// Finds the next unused drive letter and returns it.
84-
char GetNextAvailableDriveLetter()
85-
{
86-
List<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();
87-
88-
// A,B are reserved, C is usually reserved
89-
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D');
90-
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x));
91-
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives);
92-
93-
if (!allDrivesLetters.Any())
94-
{
95-
throw new ArgumentOutOfRangeException("No drive letters available");
96-
}
97-
98-
return allDrivesLetters.First();
99-
}
100-
}
101-
102-
/// <summary>
103-
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
104-
/// </summary>
105-
public static void DeleteVirtualDrive(char driveLetter)
106-
{
107-
if (!OperatingSystem.IsWindows())
108-
{
109-
throw new PlatformNotSupportedException();
110-
}
111-
112-
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:"));
113-
if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
114-
{
115-
throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst");
116-
}
117-
}
118-
11964
public static void Mount(string volumeName, string mountPoint)
12065
{
12166
if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar)
@@ -173,21 +118,6 @@ private static bool RunProcess(ProcessStartInfo startInfo)
173118
return process.ExitCode == 0;
174119
}
175120

176-
private static string SubstPath
177-
{
178-
get
179-
{
180-
if (!OperatingSystem.IsWindows())
181-
{
182-
throw new PlatformNotSupportedException();
183-
}
184-
185-
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
186-
string system32 = Path.Join(systemRoot, "System32");
187-
return Path.Join(system32, "subst.exe");
188-
}
189-
}
190-
191121
/// For standalone debugging help. Change Main0 to Main
192122
public static void Main0(string[] args)
193123
{

src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
9898
<Compile Include="$(CommonPath)System\IO\PathInternal.cs" Link="Common\System\IO\PathInternal.cs" />
9999
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs" Link="Common\System\IO\PathInternal.Windows.cs" />
100+
<Compile Include="$(CommonTestPath)System\IO\VirtualDriveHelper.Windows.cs" Link="Common\System\IO\VirtualDriveHelper.Windows.cs" />
100101
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
101102
<ProjectReference Include="$(LibrariesProjectRoot)System.IO.FileSystem.AccessControl\src\System.IO.FileSystem.AccessControl.csproj" />
102103
</ItemGroup>

0 commit comments

Comments
 (0)