Skip to content

Commit

Permalink
Re-enable Windows test that verifies DriveInfo.VolumeLabel setter fai…
Browse files Browse the repository at this point in the history
…ls on SUBST'd drive (#59850)

* Re-enable test that verifies DriveInfo.VolumeLabel setter fails on SUBST'd drive
  • Loading branch information
carlossanlop authored Nov 25, 2021
1 parent db6f5ce commit a2af8ae
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 126 deletions.
148 changes: 148 additions & 0 deletions src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// If there is a SUBST'ed drive, Dispose unmounts it to free the drive letter.
/// </summary>
public void Dispose()
{
try
{
if (VirtualDriveLetter != default)
{
DeleteVirtualDrive(VirtualDriveLetter);
Directory.Delete(VirtualDriveTargetDir, recursive: true);
}
}
catch { } // avoid exceptions on dispose
}

/// <summary>
/// Returns the path of a folder that is to be mounted using SUBST.
/// </summary>
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;
}
}

/// <summary>
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST.
/// </summary>
public char VirtualDriveLetter
{
get
{
if (_virtualDriveLetter == default)
{
// Mount the folder to a drive letter
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir);
}
return _virtualDriveLetter;
}
}

///<summary>
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
/// subst is not available in Windows Nano.
/// </summary>
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<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();

// A,B are reserved, C is usually reserved
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D');
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x));
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives);

if (!allDrivesLetters.Any())
{
throw new ArgumentOutOfRangeException("No drive letters available");
}

return allDrivesLetters.First();
}
}

/// <summary>
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
/// </summary>
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");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Linq;
using Xunit;

namespace System.IO.FileSystem.DriveInfoTests
namespace System.IO.FileSystem.Tests
{
public partial class DriveInfoUnixTests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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}://" };
Expand All @@ -54,7 +52,6 @@ public void TestConstructor()
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void TestGetDrives()
{
var validExpectedDrives = GetValidDriveLettersOnMachine();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -137,7 +132,6 @@ public void TestDriveType()
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void TestValidDiskSpaceProperties()
{
bool win32Result;
Expand Down Expand Up @@ -169,7 +163,6 @@ public void TestValidDiskSpaceProperties()
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void TestInvalidDiskProperties()
{
string invalidDriveName = GetInvalidDriveLettersOnMachine().First().ToString();
Expand All @@ -189,7 +182,6 @@ public void TestInvalidDiskProperties()
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void GetVolumeLabel_Returns_CorrectLabel()
{
void DoDriveCheck()
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
<ItemGroup>
<Compile Include="DriveInfo.Unix.Tests.cs" Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'" />
<Compile Include="DriveInfo.Windows.Tests.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="VirtualDrives.Windows.Tests.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(CommonTestPath)System\IO\VirtualDriveHelper.Windows.cs" Link="Common\System\IO\VirtualDriveHelper.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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<IOException>(() => drive.VolumeLabel = "impossible");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,61 +61,6 @@ public static bool CreateJunction(string junctionPath, string targetPath)
return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath));
}

///<summary>
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command.
/// subst is not available in Windows Nano.
/// </summary>
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<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList();

// A,B are reserved, C is usually reserved
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D');
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x));
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives);

if (!allDrivesLetters.Any())
{
throw new ArgumentOutOfRangeException("No drive letters available");
}

return allDrivesLetters.First();
}
}

/// <summary>
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
/// </summary>
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)
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
<Compile Include="$(CommonPath)System\IO\PathInternal.cs" Link="Common\System\IO\PathInternal.cs" />
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs" Link="Common\System\IO\PathInternal.Windows.cs" />
<Compile Include="$(CommonTestPath)System\IO\VirtualDriveHelper.Windows.cs" Link="Common\System\IO\VirtualDriveHelper.Windows.cs" />
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.IO.FileSystem.AccessControl\src\System.IO.FileSystem.AccessControl.csproj" />
</ItemGroup>
Expand Down
Loading

0 comments on commit a2af8ae

Please sign in to comment.