diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/DumpDriveInformation.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/DumpDriveInformation.cs new file mode 100644 index 00000000000000..10c6fac755729f --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/DumpDriveInformation.cs @@ -0,0 +1,82 @@ +// 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.Runtime.InteropServices; +using Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace System.IO.Tests +{ + public class DumpDriveInformation + { + // When running both inner and outer loop together, dump only once + private static bool s_dumped = false; + + [Fact] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.Wasi, "Not applicable")] + public void DumpDriveInformationToConsole() + { + if (s_dumped || !PlatformDetection.IsInHelix) + return; + + s_dumped = true; + + // Not really a test, but useful to dump drive/volume information to the test log + // to help debug environmental issues with mount volume tests in CI. + // Follows the pattern of DescriptionNameTests.DumpRuntimeInformationToConsole. + + Console.WriteLine("### DRIVE INFORMATION"); + Console.WriteLine($"### Machine: {Environment.MachineName}"); + Console.WriteLine($"### OS: {RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})"); + Console.WriteLine($"### Current directory: {Directory.GetCurrentDirectory()}"); + + foreach (DriveInfo drive in DriveInfo.GetDrives()) + { + try + { + string info = $"### Drive {drive.Name}: Type={drive.DriveType}"; + if (drive.IsReady) + { + info += $" Format={drive.DriveFormat} Size={drive.TotalSize / (1024 * 1024)}MB"; + + if (OperatingSystem.IsWindows()) + { + char[] volName = new char[260]; + bool hasGuid = DllImports.GetVolumeNameForVolumeMountPoint(drive.Name, volName, volName.Length); + info += hasGuid + ? $" VolumeGUID={new string(volName).TrimEnd('\0')}" + : $" VolumeGUID=NONE({Marshal.GetLastPInvokeErrorMessage()})"; + } + } + else + { + info += " NotReady"; + } + Console.WriteLine(info); + } + catch (Exception ex) + { + Console.WriteLine($"### Drive {drive.Name}: error probing: {ex.GetType().Name}: {ex.Message}"); + } + } + + if (OperatingSystem.IsWindows()) + { + string otherNtfs = IOServices.GetNtfsDriveOtherThanCurrent(); + Console.WriteLine($"### GetNtfsDriveOtherThanCurrent() = {otherNtfs ?? "(null)"}"); + } + } + + [Fact] + [OuterLoop] + [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.Wasi, "Not applicable")] + public void DumpDriveInformationToConsoleOuter() + { + // Outer loop runs don't run inner loop tests. + // But we want to log this data for any Helix run. + DumpDriveInformationToConsole(); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/DllImports.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/DllImports.cs index 41cab0dc0e1ca9..9e3614abae71f3 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/DllImports.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/DllImports.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.InteropServices; -using System.Text; internal static partial class DllImports { @@ -16,4 +15,8 @@ internal static partial class DllImports [LibraryImport("kernel32.dll", EntryPoint = "GetDriveTypeW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] internal static partial int GetDriveType(string drive); + + [LibraryImport("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetVolumeNameForVolumeMountPoint(string volumeMountPoint, [Out] char[] volumeName, int bufferLength); } diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/IOServices.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/IOServices.cs index 9044a5d82cd245..13a3f0b08bb5a6 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/IOServices.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/PortedCommon/IOServices.cs @@ -64,13 +64,27 @@ public static string GetNtfsDriveOtherThan(string drive) if (!IsReady(otherDrive)) continue; - if (IsDriveNTFS(otherDrive)) - return otherDrive; + if (!IsDriveNTFS(otherDrive)) + continue; + + // Filter out drives that don't support volume mount point operations + // (e.g., SUBST drives, Azure resource disks without volume GUIDs). + // These report as Fixed/NTFS/Ready but GetVolumeNameForVolumeMountPoint fails. + if (!HasVolumeGuid(otherDrive)) + continue; + + return otherDrive; } return null; } + private static bool HasVolumeGuid(string drive) + { + char[] volumeName = new char[260]; + return DllImports.GetVolumeNameForVolumeMountPoint(drive, volumeName, volumeName.Length); + } + public static string GetNonNtfsDriveOtherThanCurrent() { return GetNonNtfsDriveOtherThan(GetCurrentDrive()); diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index ca891f26eab4e0..14ae1f46b52bc3 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -42,6 +42,7 @@ +