Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-enable Windows test that verifies DriveInfo.VolumeLabel setter fails on SUBST'd drive #59850

Merged
merged 3 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

carlossanlop marked this conversation as resolved.
Show resolved Hide resolved
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