Skip to content

Commit

Permalink
Address suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
carlossanlop committed Nov 25, 2021
1 parent 1d34c1d commit c5676d7
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 145 deletions.
218 changes: 103 additions & 115 deletions src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,160 +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;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;

public class VirtualDriveHelper : IDisposable
namespace System.IO.FileSystem
{
// 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()
// Adds test helper APIs to manipulate Windows virtual drives via SUBST.
[SupportedOSPlatform("windows")]
public class VirtualDriveHelper : IDisposable
{
try
// 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()
{
if (VirtualDriveLetter != default)
try
{
DeleteVirtualDrive(VirtualDriveLetter);
Directory.Delete(VirtualDriveTargetDir, recursive: true);
if (VirtualDriveLetter != default)
{
DeleteVirtualDrive(VirtualDriveLetter);
Directory.Delete(VirtualDriveTargetDir, recursive: true);
}
}
catch { } // avoid exceptions on dispose
}
catch { } // avoid exceptions on dispose
}

/// <summary>
/// Returns the path of a folder that is to be mounted using SUBST.
/// </summary>
public string VirtualDriveTargetDir
{
get
/// <summary>
/// Returns the path of a folder that is to be mounted using SUBST.
/// </summary>
public string VirtualDriveTargetDir
{
if (_virtualDriveTargetDir == null)
get
{
// 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);
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;
}

return _virtualDriveTargetDir;
}
}

/// <summary>
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST.
/// </summary>
public char VirtualDriveLetter
{
get
/// <summary>
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST.
/// </summary>
public char VirtualDriveLetter
{
if (_virtualDriveLetter == default)
get
{
// Mount the folder to a drive letter
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir);
if (_virtualDriveLetter == default)
{
// Mount the folder to a drive letter
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir);
}
return _virtualDriveLetter;
}
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>
public char CreateVirtualDrive(string targetDir)
{
if (!OperatingSystem.IsWindows())
///<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)
{
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;

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();

// 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);

// 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");
}

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

return allDrivesLetters.First();
}
}

/// <summary>
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
/// </summary>
public void DeleteVirtualDrive(char driveLetter)
{
if (!OperatingSystem.IsWindows())
/// <summary>
/// On Windows, unassigns the specified virtual drive letter from its mounted folder.
/// </summary>
private static void DeleteVirtualDrive(char driveLetter)
{
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");
}
}

bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:"));
if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter))
private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments)
{
throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst");
}
}
var info = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardOutput = true
};

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);
}

foreach (var argument in arguments)
{
info.ArgumentList.Add(argument);
return info;
}

return info;
}

private static bool RunProcess(ProcessStartInfo startInfo)
{
var process = Process.Start(startInfo);
process.WaitForExit();
return process.ExitCode == 0;
}
private static bool RunProcess(ProcessStartInfo startInfo)
{
using var process = Process.Start(startInfo);
process.WaitForExit();
return process.ExitCode == 0;
}

private string SubstPath
{
get
private static string SubstPath
{
if (!OperatingSystem.IsWindows())
get
{
throw new PlatformNotSupportedException();
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
return Path.Join(systemRoot, "System32", "subst.exe");
}

string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows";
string system32 = Path.Join(systemRoot, "System32");
return Path.Join(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
Expand Up @@ -9,27 +9,9 @@
using System.Text;
using Xunit;

namespace System.IO.FileSystem.DriveInfoTests
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 : IDisposable
{
private VirtualDriveHelper VirtualDrive { get; } = new VirtualDriveHelper();

public void Dispose() => VirtualDrive.Dispose();

// Cannot set the volume label on a SUBST'ed folder
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))]
[PlatformSpecific(TestPlatforms.Windows)]
public void SetVolumeLabel_OnVirtualDrive_Throws()
{
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");
}
}

[PlatformSpecific(TestPlatforms.Windows)]
public class DriveInfoWindowsTests
{
[Theory]
Expand All @@ -52,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 @@ -71,7 +52,6 @@ public void TestConstructor()
}

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

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

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

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void GetVolumeLabel_Returns_CorrectLabel()
{
void DoDriveCheck()
Expand Down Expand Up @@ -242,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 @@ -263,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,6 +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>
Loading

0 comments on commit c5676d7

Please sign in to comment.