diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs index 1cfbd351824de8..b6b2be09ad54db 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs @@ -4,13 +4,13 @@ using Microsoft.Win32.SafeHandles; using System.ComponentModel; using System.IO.Pipes; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.ServiceProcess; using System.Threading.Tasks; using Xunit; +using System.Threading; namespace System.IO.Tests { @@ -176,4 +176,148 @@ public struct SHARE_INFO_502 [DllImport(Interop.Libraries.Netapi32)] public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved); } + + [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc + [OuterLoop("Has a very complex setup logic that in theory might have some side-effects")] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] + public class DeviceInterfaceTests + { + [Fact] + public async Task DeviceInterfaceCanBeOpenedForAsyncIO() + { + FileStream? fileStream = OpenFirstAvailableDeviceInterface(); + + if (fileStream is null) + { + // it's OK to not have any such devices available + // this test is just best effort + return; + } + + using (fileStream) + { + Assert.True(fileStream.CanRead); + Assert.False(fileStream.CanWrite); + Assert.False(fileStream.CanSeek); // #54143 + + try + { + CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(250)); + + await fileStream.ReadAsync(new byte[4096], cts.Token); + } + catch (OperationCanceledException) + { + // most likely there is no data available and the task is going to get cancelled + // which is fine, we just want to make sure that reading from devices is supported (#54143) + } + } + } + + private static FileStream? OpenFirstAvailableDeviceInterface() + { + const int DIGCF_PRESENT = 0x2; + const int DIGCF_DEVICEINTERFACE = 0x10; + const int ERROR_NO_MORE_ITEMS = 259; + + HidD_GetHidGuid(out Guid HidGuid); + IntPtr deviceInfoSet = SetupDiGetClassDevs(in HidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + try + { + SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); + deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData); + + uint deviceIndex = 0; + while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex++, ref deviceInfoData)) + { + if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS) + { + break; + } + + SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); + deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData); + + if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, in HidGuid, deviceIndex, ref deviceInterfaceData)) + { + continue; + } + + SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = new SP_DEVICE_INTERFACE_DETAIL_DATA(); + deviceInterfaceDetailData.cbSize = IntPtr.Size == 8 ? 8 : 6; + + uint size = (uint)Marshal.SizeOf(deviceInterfaceDetailData); + + if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref deviceInterfaceDetailData, size, ref size, IntPtr.Zero)) + { + continue; + } + + string devicePath = deviceInterfaceDetailData.DevicePath; + Assert.StartsWith(@"\\?\hid", devicePath); + + try + { + return new FileStream(devicePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0, FileOptions.Asynchronous); + } + catch (IOException) + { + continue; // device has been locked by another process + } + } + } + finally + { + SetupDiDestroyDeviceInfoList(deviceInfoSet); + } + + return null; + } + + [StructLayout(LayoutKind.Sequential)] + struct SP_DEVICE_INTERFACE_DATA + { + public int cbSize; + public Guid interfaceClassGuid; + public int flags; + private nuint reserved; + } + + [StructLayout(LayoutKind.Sequential)] + struct SP_DEVINFO_DATA + { + public uint cbSize; + public Guid ClassGuid; + public uint DevInst; + public nint Reserved; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct SP_DEVICE_INTERFACE_DETAIL_DATA + { + public int cbSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] // 256 should be always enough for device interface path + public string DevicePath; + } + + [DllImport("hid.dll", SetLastError = true)] + static extern void HidD_GetHidGuid(out Guid HidGuid); + + [DllImport("setupapi.dll", SetLastError = true)] + static extern IntPtr SetupDiGetClassDevs(in Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags); + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, in Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); + + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, ref uint RequiredSize, IntPtr DeviceInfoData); + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + } } diff --git a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj index 04d57626857104..e9ab0a7365e303 100644 --- a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj @@ -1,4 +1,4 @@ - + true true @@ -19,13 +19,14 @@ + + -