-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to create temp mapped drive for integration tests (#8366)
Fixes #7330 (plus one subtask of #8329) Tests only change (no production code affected) Context Drive enumeration integration tests need to simulate attempt to enumerate whole drive. To prevent security and test runtime considerations - a dummy folder is created and mapped to a free letter to be offered to test as a drive for enumeration. Changes Made Added utility for mapping drives and mounted to affected unit tests.
- Loading branch information
1 parent
f0f9c50
commit 51df476
Showing
4 changed files
with
223 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#nullable enable | ||
using System; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.Versioning; | ||
using System.Text; | ||
|
||
namespace Microsoft.Build.UnitTests.Shared; | ||
|
||
internal static class DriveMapping | ||
{ | ||
private const int ERROR_FILE_NOT_FOUND = 2; | ||
private const int ERROR_INSUFFICIENT_BUFFER = 122; | ||
private const int DDD_REMOVE_DEFINITION = 2; | ||
private const int DDD_NO_FLAG = 0; | ||
// extra space for '\??\'. Not counting for long paths support in tests. | ||
private const int MAX_PATH = 259; | ||
|
||
/// <summary> | ||
/// Windows specific. Maps path to a requested drive. | ||
/// </summary> | ||
/// <param name="letter">Drive letter</param> | ||
/// <param name="path">Path to be mapped</param> | ||
[SupportedOSPlatform("windows")] | ||
public static void MapDrive(char letter, string path) | ||
{ | ||
if (!DefineDosDevice(DDD_NO_FLAG, ToDeviceName(letter), path)) | ||
{ | ||
NativeMethodsShared.ThrowExceptionForErrorCode(Marshal.GetLastWin32Error()); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Windows specific. Unmaps drive mapping. | ||
/// </summary> | ||
/// <param name="letter">Drive letter.</param> | ||
[SupportedOSPlatform("windows")] | ||
public static void UnmapDrive(char letter) | ||
{ | ||
if (!DefineDosDevice(DDD_REMOVE_DEFINITION, ToDeviceName(letter), null)) | ||
{ | ||
NativeMethodsShared.ThrowExceptionForErrorCode(Marshal.GetLastWin32Error()); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Windows specific. Fetches path mapped under specific drive letter. | ||
/// </summary> | ||
/// <param name="letter">Drive letter.</param> | ||
/// <returns>Path mapped under specified letter. Empty string if mapping not found.</returns> | ||
[SupportedOSPlatform("windows")] | ||
public static string GetDriveMapping(char letter) | ||
{ | ||
// since this is just for test purposes - let's not overcomplicate with long paths support | ||
char[] buffer = new char[MAX_PATH]; | ||
|
||
while (QueryDosDevice(ToDeviceName(letter), buffer, buffer.Length) == 0) | ||
{ | ||
// Return empty string if the drive is not mapped | ||
int err = Marshal.GetLastWin32Error(); | ||
if (err == ERROR_FILE_NOT_FOUND) | ||
{ | ||
return string.Empty; | ||
} | ||
|
||
if (err != ERROR_INSUFFICIENT_BUFFER) | ||
{ | ||
NativeMethodsShared.ThrowExceptionForErrorCode(err); | ||
} | ||
|
||
buffer = new char[buffer.Length * 4]; | ||
} | ||
|
||
// Translate from the native path semantic - starting with '\??\' | ||
return new string(buffer, 4, buffer.Length - 4); | ||
} | ||
|
||
private static string ToDeviceName(char letter) | ||
{ | ||
return $"{char.ToUpper(letter)}:"; | ||
} | ||
|
||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] | ||
[SupportedOSPlatform("windows")] | ||
private static extern bool DefineDosDevice([In] int flags, [In] string deviceName, [In] string? path); | ||
|
||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] | ||
[SupportedOSPlatform("windows")] | ||
private static extern int QueryDosDevice([In] string deviceName, [Out] char[] buffer, [In] int bufSize); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#nullable enable | ||
using System; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Runtime.Versioning; | ||
|
||
namespace Microsoft.Build.UnitTests.Shared; | ||
|
||
/// <summary> | ||
/// Windows specific. Class managing system resource - temporary local path mapped to available drive letter. | ||
/// </summary> | ||
public class DummyMappedDrive : IDisposable | ||
{ | ||
public char MappedDriveLetter { get; init; } = 'z'; | ||
private readonly string _mappedPath; | ||
private readonly bool _mapped; | ||
|
||
public DummyMappedDrive() | ||
{ | ||
_mappedPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
|
||
if (!NativeMethodsShared.IsWindows) | ||
{ | ||
return; | ||
} | ||
|
||
Directory.CreateDirectory(_mappedPath); | ||
File.Create(Path.Combine(_mappedPath, "x")).Dispose(); | ||
|
||
for (char driveLetter = 'z'; driveLetter >= 'a'; driveLetter--) | ||
{ | ||
if (DriveMapping.GetDriveMapping(driveLetter) == string.Empty) | ||
{ | ||
DriveMapping.MapDrive(driveLetter, _mappedPath); | ||
MappedDriveLetter = driveLetter; | ||
_mapped = true; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
private void ReleaseUnmanagedResources(bool disposing) | ||
{ | ||
Exception? e = null; | ||
if (Directory.Exists(_mappedPath)) | ||
{ | ||
try | ||
{ | ||
Directory.Delete(_mappedPath, true); | ||
} | ||
catch (Exception exc) | ||
{ | ||
e = exc; | ||
Debug.Fail("Exception in DummyMappedDrive finalizer: " + e.ToString()); | ||
} | ||
} | ||
|
||
if (_mapped && NativeMethodsShared.IsWindows) | ||
{ | ||
try | ||
{ | ||
DriveMapping.UnmapDrive(MappedDriveLetter); | ||
} | ||
catch (Exception exc) | ||
{ | ||
e = e == null ? exc : new AggregateException(e, exc); | ||
Debug.Fail("Exception in DummyMappedDrive finalizer: " + e.ToString()); | ||
} | ||
} | ||
|
||
if (disposing && e != null) | ||
{ | ||
throw e; | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
ReleaseUnmanagedResources(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
~DummyMappedDrive() => ReleaseUnmanagedResources(false); | ||
} |