From 0b5978ae5913b46449ab16d0136d6fd716914b19 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 22 Jun 2021 11:27:04 +0200 Subject: [PATCH 1/8] add a test for unseekable device by using a path to named pipe --- .../FileStream/FileStreamConformanceTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index d1fd4fe764f4d..146e210560969 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -274,4 +274,31 @@ protected override async Task CreateConnectedStreamsAsync() protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); protected override bool SupportsConcurrentBidirectionalUse => false; } + + [PlatformSpecific(TestPlatforms.Windows)] // device paths (\\.\) are a Windows concept + public class UnseekableDeviceFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests + { + protected override async Task CreateConnectedStreamsAsync() + { + string pipeName = FileSystemTest.GetNamedPipeServerStreamName(); + string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}"); + + var server = new NamedPipeServerStream(pipeName, PipeDirection.In); + var clienStream = new FileStream(File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write, FileShare.None), FileAccess.Write); + + await server.WaitForConnectionAsync(); + + var serverStrean = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Read); + + server.SafePipeHandle.SetHandleAsInvalid(); + + return (serverStrean, clienStream); + } + + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool UsableAfterCanceledReads => false; + protected override bool FullyCancelableOperations => false; + protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); + protected override bool SupportsConcurrentBidirectionalUse => false; + } } From 518871d3e15020fabfd476b097b66813fb6456f4 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 22 Jun 2021 12:41:21 +0200 Subject: [PATCH 2/8] add a test for seekable device by using DeviceID instead of drive letter --- .../System/IO/FileCleanupTestBase.cs | 2 +- .../FileStream/FileStreamConformanceTests.cs | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/IO/FileCleanupTestBase.cs b/src/libraries/Common/tests/TestUtilities/System/IO/FileCleanupTestBase.cs index a0542ef930c02..02ffa607c94aa 100644 --- a/src/libraries/Common/tests/TestUtilities/System/IO/FileCleanupTestBase.cs +++ b/src/libraries/Common/tests/TestUtilities/System/IO/FileCleanupTestBase.cs @@ -79,7 +79,7 @@ protected virtual void Dispose(bool disposing) /// An optional index value to use as a suffix on the file name. Typically a loop index. /// The member name of the function calling this method. /// The line number of the function calling this method. - protected string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) => + protected virtual string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) => Path.Combine(TestDirectory, GetTestFileName(index, memberName, lineNumber)); /// Gets a test file name that is associated with the call site. diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index 146e210560969..047f6d79b0672 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -3,8 +3,12 @@ using Microsoft.Win32.SafeHandles; using System.Collections.Generic; +using System.ComponentModel; using System.IO.Pipes; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; using Xunit; @@ -275,7 +279,7 @@ protected override async Task CreateConnectedStreamsAsync() protected override bool SupportsConcurrentBidirectionalUse => false; } - [PlatformSpecific(TestPlatforms.Windows)] // device paths (\\.\) are a Windows concept + [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept public class UnseekableDeviceFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests { protected override async Task CreateConnectedStreamsAsync() @@ -301,4 +305,37 @@ protected override async Task CreateConnectedStreamsAsync() protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); protected override bool SupportsConcurrentBidirectionalUse => false; } + + [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept + public class SeekableDeviceFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests + { + protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + string filePath = Path.GetFullPath(base.GetTestFilePath(index, memberName, lineNumber)); + string drive = Path.GetPathRoot(filePath); + StringBuilder volumeNameBuffer = new StringBuilder(filePath.Length + 1024); + + // the following method maps drive letter like "C:\" to a DeviceID (a DOS device path) + // example: "\\?\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\" + if (!GetVolumeNameForVolumeMountPoint(drive, volumeNameBuffer, volumeNameBuffer.Capacity)) + { + throw new Win32Exception(Marshal.GetLastPInvokeError(), "GetVolumeNameForVolumeMountPoint failed"); + } + + // instead of: + // 'C:\Users\x\AppData\Local\Temp\y\z + // we want sth like: + // '\\.\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\Users\x\AppData\Local\Temp\y\z + string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString()); + Assert.StartsWith(@"\\?\", devicePath); +#if DEBUG + devicePath = devicePath.Replace(@"\\?\", @"\\.\"); // we want to test both \\.\ and \\?\ +#endif + + return devicePath; + } + + [DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] + private static extern bool GetVolumeNameForVolumeMountPoint(string volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity); + } } From 0efd7448d7dca5a9df0d65da50e86aba3cbb9d00 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 22 Jun 2021 13:48:28 +0200 Subject: [PATCH 3/8] add a test for a UNC file path (local file share) --- .../System/PlatformDetection.Windows.cs | 1 + .../FileStream/FileStreamConformanceTests.cs | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs index 078f29be48e68..610b17b520271 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs @@ -36,6 +36,7 @@ public static partial class PlatformDetection public static bool IsNotWindowsHomeEdition => !IsWindowsHomeEdition; public static bool IsNotInAppContainer => !IsInAppContainer; public static bool IsSoundPlaySupported => IsWindows && IsNotWindowsNanoServer; + public static bool IsAdmin_IsNotNano => PlatformDetection.IsWindowsAndElevated && PlatformDetection.IsNotWindowsNanoServer; // >= Windows 10 Anniversary Update public static bool IsWindows10Version1607OrGreater => IsWindowsVersionOrLater(10, 0, 14393); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index 047f6d79b0672..1fc05b7e08d1c 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -338,4 +338,67 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] [DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] private static extern bool GetVolumeNameForVolumeMountPoint(string volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity); } + + [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc + [Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe + [OuterLoop("Requires admin privileges to create a file share")] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsAdmin_IsNotNano))] + public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests + { + protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + string testDirectoryPath = Path.GetFullPath(TestDirectory); + string shareName = new DirectoryInfo(testDirectoryPath).Name; + string fileName = GetTestFileName(index, memberName, lineNumber); + + SHARE_INFO_502 shareInfo = default; + shareInfo.shi502_netname = shareName; + shareInfo.shi502_path = testDirectoryPath; + shareInfo.shi502_remark = "folder created to test UNC file paths"; + shareInfo.shi502_max_uses = -1; + + int infoSize = Marshal.SizeOf(shareInfo); + IntPtr infoBuffer = Marshal.AllocCoTaskMem(infoSize); + + try + { + Marshal.StructureToPtr(shareInfo, infoBuffer, false); + + int shareResult = NetShareAdd(string.Empty, 502, infoBuffer, IntPtr.Zero); + + if (shareResult != 0 && shareResult != 2118) // is a failure that is not a NERR_DuplicateShare + { + throw new Exception($"Failed to create a file share, NetShareAdd returned {shareResult}"); + } + } + finally + { + Marshal.FreeCoTaskMem(infoBuffer); + } + + // now once the folder has been shared we can use "localhost" to access it: + return @$"\\localhost\{shareName}\{fileName}"; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SHARE_INFO_502 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_netname; + public uint shi502_type; + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_remark; + public int shi502_permissions; + public int shi502_max_uses; + public int shi502_current_uses; + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_path; + public IntPtr shi502_passwd; + public int shi502_reserved; + public IntPtr shi502_security_descriptor; + } + + [DllImport(Interop.Libraries.Netapi32)] + public static extern int NetShareAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, int level, IntPtr buf, IntPtr parm_err); + } } From 20ba56987979e4182dab827775436256248877c1 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 22 Jun 2021 14:12:00 +0200 Subject: [PATCH 4/8] don't forget to remove the share --- .../FileStream/FileStreamConformanceTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index 1fc05b7e08d1c..d6e8a42829451 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -380,6 +380,21 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] return @$"\\localhost\{shareName}\{fileName}"; } + protected override void Dispose(bool disposing) + { + string testDirectoryPath = Path.GetFullPath(TestDirectory); + string shareName = new DirectoryInfo(testDirectoryPath).Name; + + try + { + NetShareDel(string.Empty, shareName, 0); + } + finally + { + base.Dispose(disposing); + } + } + [StructLayout(LayoutKind.Sequential)] public struct SHARE_INFO_502 { @@ -400,5 +415,8 @@ public struct SHARE_INFO_502 [DllImport(Interop.Libraries.Netapi32)] public static extern int NetShareAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, int level, IntPtr buf, IntPtr parm_err); + + [DllImport(Interop.Libraries.Netapi32)] + public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved); } } From a8c89fa6764539e387a08f0cd347f341724263df Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 23 Jun 2021 09:14:36 +0200 Subject: [PATCH 5/8] test both type of slashes in the UNC paths --- .../tests/FileStream/FileStreamConformanceTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index d6e8a42829451..c86ff11fd9c58 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -329,7 +329,8 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString()); Assert.StartsWith(@"\\?\", devicePath); #if DEBUG - devicePath = devicePath.Replace(@"\\?\", @"\\.\"); // we want to test both \\.\ and \\?\ + // we do want to test \\.\ prefix as well + devicePath = devicePath.Replace(@"\\?\", @"\\.\"); #endif return devicePath; @@ -377,7 +378,12 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] } // now once the folder has been shared we can use "localhost" to access it: + // both type of slashes are valid, so let's test one for Debug and another for other configs +#if DEBUG + return @$"//localhost/{shareName}/{fileName}"; +#else return @$"\\localhost\{shareName}\{fileName}"; +#endif } protected override void Dispose(bool disposing) From 47d2457812d57dbc0e5b5300bc38f08291745db9 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 23 Jun 2021 09:53:55 +0200 Subject: [PATCH 6/8] fix the compilation errors --- .../FileStreamConformanceTests.Windows.cs | 161 ++++++++++++++++++ .../FileStream/FileStreamConformanceTests.cs | 151 ---------------- .../tests/System.IO.FileSystem.Tests.csproj | 1 + 3 files changed, 162 insertions(+), 151 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs new file mode 100644 index 0000000000000..f90e506532309 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.IO.Pipes; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Tests +{ + [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept + public class UnseekableDeviceFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests + { + protected override async Task CreateConnectedStreamsAsync() + { + string pipeName = FileSystemTest.GetNamedPipeServerStreamName(); + string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}"); + + var server = new NamedPipeServerStream(pipeName, PipeDirection.In); + var clienStream = new FileStream(File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write, FileShare.None), FileAccess.Write); + + await server.WaitForConnectionAsync(); + + var serverStrean = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Read); + + server.SafePipeHandle.SetHandleAsInvalid(); + + return (serverStrean, clienStream); + } + + protected override Type UnsupportedConcurrentExceptionType => null; + protected override bool UsableAfterCanceledReads => false; + protected override bool FullyCancelableOperations => false; + protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); + protected override bool SupportsConcurrentBidirectionalUse => false; + } + + [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept + public class SeekableDeviceFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests + { + protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + string filePath = Path.GetFullPath(base.GetTestFilePath(index, memberName, lineNumber)); + string drive = Path.GetPathRoot(filePath); + StringBuilder volumeNameBuffer = new StringBuilder(filePath.Length + 1024); + + // the following method maps drive letter like "C:\" to a DeviceID (a DOS device path) + // example: "\\?\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\" + if (!GetVolumeNameForVolumeMountPoint(drive, volumeNameBuffer, volumeNameBuffer.Capacity)) + { + throw new Win32Exception(Marshal.GetLastPInvokeError(), "GetVolumeNameForVolumeMountPoint failed"); + } + + // instead of: + // 'C:\Users\x\AppData\Local\Temp\y\z + // we want sth like: + // '\\.\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\Users\x\AppData\Local\Temp\y\z + string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString()); + Assert.StartsWith(@"\\?\", devicePath); +#if DEBUG + // we do want to test \\.\ prefix as well + devicePath = devicePath.Replace(@"\\?\", @"\\.\"); +#endif + + return devicePath; + } + + [DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] + private static extern bool GetVolumeNameForVolumeMountPoint(string volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity); + } + + [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc + [Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe + [OuterLoop("Requires admin privileges to create a file share")] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsAdmin_IsNotNano))] + public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests + { + protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) + { + string testDirectoryPath = Path.GetFullPath(TestDirectory); + string shareName = new DirectoryInfo(testDirectoryPath).Name; + string fileName = GetTestFileName(index, memberName, lineNumber); + + SHARE_INFO_502 shareInfo = default; + shareInfo.shi502_netname = shareName; + shareInfo.shi502_path = testDirectoryPath; + shareInfo.shi502_remark = "folder created to test UNC file paths"; + shareInfo.shi502_max_uses = -1; + + int infoSize = Marshal.SizeOf(shareInfo); + IntPtr infoBuffer = Marshal.AllocCoTaskMem(infoSize); + + try + { + Marshal.StructureToPtr(shareInfo, infoBuffer, false); + + int shareResult = NetShareAdd(string.Empty, 502, infoBuffer, IntPtr.Zero); + + if (shareResult != 0 && shareResult != 2118) // is a failure that is not a NERR_DuplicateShare + { + throw new Exception($"Failed to create a file share, NetShareAdd returned {shareResult}"); + } + } + finally + { + Marshal.FreeCoTaskMem(infoBuffer); + } + + // now once the folder has been shared we can use "localhost" to access it: + // both type of slashes are valid, so let's test one for Debug and another for other configs +#if DEBUG + return @$"//localhost/{shareName}/{fileName}"; +#else + return @$"\\localhost\{shareName}\{fileName}"; +#endif + } + + protected override void Dispose(bool disposing) + { + string testDirectoryPath = Path.GetFullPath(TestDirectory); + string shareName = new DirectoryInfo(testDirectoryPath).Name; + + try + { + NetShareDel(string.Empty, shareName, 0); + } + finally + { + base.Dispose(disposing); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SHARE_INFO_502 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_netname; + public uint shi502_type; + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_remark; + public int shi502_permissions; + public int shi502_max_uses; + public int shi502_current_uses; + [MarshalAs(UnmanagedType.LPWStr)] + public string shi502_path; + public IntPtr shi502_passwd; + public int shi502_reserved; + public IntPtr shi502_security_descriptor; + } + + [DllImport(Interop.Libraries.Netapi32)] + public static extern int NetShareAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, int level, IntPtr buf, IntPtr parm_err); + + [DllImport(Interop.Libraries.Netapi32)] + public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved); + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index c86ff11fd9c58..d1fd4fe764f4d 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -3,12 +3,8 @@ using Microsoft.Win32.SafeHandles; using System.Collections.Generic; -using System.ComponentModel; using System.IO.Pipes; using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; using Xunit; @@ -278,151 +274,4 @@ protected override async Task CreateConnectedStreamsAsync() protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); protected override bool SupportsConcurrentBidirectionalUse => false; } - - [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept - public class UnseekableDeviceFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests - { - protected override async Task CreateConnectedStreamsAsync() - { - string pipeName = FileSystemTest.GetNamedPipeServerStreamName(); - string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}"); - - var server = new NamedPipeServerStream(pipeName, PipeDirection.In); - var clienStream = new FileStream(File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write, FileShare.None), FileAccess.Write); - - await server.WaitForConnectionAsync(); - - var serverStrean = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Read); - - server.SafePipeHandle.SetHandleAsInvalid(); - - return (serverStrean, clienStream); - } - - protected override Type UnsupportedConcurrentExceptionType => null; - protected override bool UsableAfterCanceledReads => false; - protected override bool FullyCancelableOperations => false; - protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows(); - protected override bool SupportsConcurrentBidirectionalUse => false; - } - - [PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept - public class SeekableDeviceFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests - { - protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) - { - string filePath = Path.GetFullPath(base.GetTestFilePath(index, memberName, lineNumber)); - string drive = Path.GetPathRoot(filePath); - StringBuilder volumeNameBuffer = new StringBuilder(filePath.Length + 1024); - - // the following method maps drive letter like "C:\" to a DeviceID (a DOS device path) - // example: "\\?\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\" - if (!GetVolumeNameForVolumeMountPoint(drive, volumeNameBuffer, volumeNameBuffer.Capacity)) - { - throw new Win32Exception(Marshal.GetLastPInvokeError(), "GetVolumeNameForVolumeMountPoint failed"); - } - - // instead of: - // 'C:\Users\x\AppData\Local\Temp\y\z - // we want sth like: - // '\\.\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\Users\x\AppData\Local\Temp\y\z - string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString()); - Assert.StartsWith(@"\\?\", devicePath); -#if DEBUG - // we do want to test \\.\ prefix as well - devicePath = devicePath.Replace(@"\\?\", @"\\.\"); -#endif - - return devicePath; - } - - [DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] - private static extern bool GetVolumeNameForVolumeMountPoint(string volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity); - } - - [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc - [Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe - [OuterLoop("Requires admin privileges to create a file share")] - [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsAdmin_IsNotNano))] - public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests - { - protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) - { - string testDirectoryPath = Path.GetFullPath(TestDirectory); - string shareName = new DirectoryInfo(testDirectoryPath).Name; - string fileName = GetTestFileName(index, memberName, lineNumber); - - SHARE_INFO_502 shareInfo = default; - shareInfo.shi502_netname = shareName; - shareInfo.shi502_path = testDirectoryPath; - shareInfo.shi502_remark = "folder created to test UNC file paths"; - shareInfo.shi502_max_uses = -1; - - int infoSize = Marshal.SizeOf(shareInfo); - IntPtr infoBuffer = Marshal.AllocCoTaskMem(infoSize); - - try - { - Marshal.StructureToPtr(shareInfo, infoBuffer, false); - - int shareResult = NetShareAdd(string.Empty, 502, infoBuffer, IntPtr.Zero); - - if (shareResult != 0 && shareResult != 2118) // is a failure that is not a NERR_DuplicateShare - { - throw new Exception($"Failed to create a file share, NetShareAdd returned {shareResult}"); - } - } - finally - { - Marshal.FreeCoTaskMem(infoBuffer); - } - - // now once the folder has been shared we can use "localhost" to access it: - // both type of slashes are valid, so let's test one for Debug and another for other configs -#if DEBUG - return @$"//localhost/{shareName}/{fileName}"; -#else - return @$"\\localhost\{shareName}\{fileName}"; -#endif - } - - protected override void Dispose(bool disposing) - { - string testDirectoryPath = Path.GetFullPath(TestDirectory); - string shareName = new DirectoryInfo(testDirectoryPath).Name; - - try - { - NetShareDel(string.Empty, shareName, 0); - } - finally - { - base.Dispose(disposing); - } - } - - [StructLayout(LayoutKind.Sequential)] - public struct SHARE_INFO_502 - { - [MarshalAs(UnmanagedType.LPWStr)] - public string shi502_netname; - public uint shi502_type; - [MarshalAs(UnmanagedType.LPWStr)] - public string shi502_remark; - public int shi502_permissions; - public int shi502_max_uses; - public int shi502_current_uses; - [MarshalAs(UnmanagedType.LPWStr)] - public string shi502_path; - public IntPtr shi502_passwd; - public int shi502_reserved; - public IntPtr shi502_security_descriptor; - } - - [DllImport(Interop.Libraries.Netapi32)] - public static extern int NetShareAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, int level, IntPtr buf, IntPtr parm_err); - - [DllImport(Interop.Libraries.Netapi32)] - public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved); - } } diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index a271109ce2fc6..10c06706b1bf3 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -71,6 +71,7 @@ + From 37675fce4ce6e77862f2a4dbc59e69b0ec5dcdcd Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 23 Jun 2021 13:39:20 +0200 Subject: [PATCH 7/8] check if Server Service is running for tests that use file sharing --- .../FileStreamConformanceTests.Windows.cs | 21 ++++++++++++++++++- ...stem.IO.FileSystem.Net5Compat.Tests.csproj | 1 + .../tests/System.IO.FileSystem.Tests.csproj | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) 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 f90e506532309..57b752cba82fe 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs @@ -4,9 +4,11 @@ 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; @@ -76,9 +78,26 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc [Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe [OuterLoop("Requires admin privileges to create a file share")] - [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsAdmin_IsNotNano))] + [ConditionalClass(typeof(UncFilePathFileStreamStandaloneConformanceTests), nameof(UncFilePathFileStreamStandaloneConformanceTests.CanShareFiles))] public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests { + public static bool CanShareFiles + { + get + { + if (!PlatformDetection.IsAdmin_IsNotNano) + { + return false; + } + + // the "Server Service" allows for file sharing. It can be disabled on some of our CI machines. + using (ServiceController sharingService = new ServiceController("Server")) + { + return sharingService.Status == ServiceControllerStatus.Running; + } + } + } + protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) { string testDirectoryPath = Path.GetFullPath(TestDirectory); 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 628bbb4324aea..81a91f996153b 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 @@ -25,6 +25,7 @@ + diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index 10c06706b1bf3..50d34587c3932 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -80,6 +80,7 @@ + From 051d4d75c67c1933b958266666a25addad904836 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 23 Jun 2021 18:43:33 +0200 Subject: [PATCH 8/8] address code review feedback --- .../System/PlatformDetection.Windows.cs | 1 - .../FileStreamConformanceTests.Windows.cs | 27 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs index 610b17b520271..078f29be48e68 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs @@ -36,7 +36,6 @@ public static partial class PlatformDetection public static bool IsNotWindowsHomeEdition => !IsWindowsHomeEdition; public static bool IsNotInAppContainer => !IsInAppContainer; public static bool IsSoundPlaySupported => IsWindows && IsNotWindowsNanoServer; - public static bool IsAdmin_IsNotNano => PlatformDetection.IsWindowsAndElevated && PlatformDetection.IsNotWindowsNanoServer; // >= Windows 10 Anniversary Update public static bool IsWindows10Version1607OrGreater => IsWindowsVersionOrLater(10, 0, 14393); 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 57b752cba82fe..1cfbd351824de 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs @@ -59,7 +59,7 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] // instead of: // 'C:\Users\x\AppData\Local\Temp\y\z - // we want sth like: + // we want something like: // '\\.\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\Users\x\AppData\Local\Temp\y\z string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString()); Assert.StartsWith(@"\\?\", devicePath); @@ -78,25 +78,24 @@ protected override string GetTestFilePath(int? index = null, [CallerMemberName] [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc [Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe [OuterLoop("Requires admin privileges to create a file share")] - [ConditionalClass(typeof(UncFilePathFileStreamStandaloneConformanceTests), nameof(UncFilePathFileStreamStandaloneConformanceTests.CanShareFiles))] + [ConditionalClass(typeof(UncFilePathFileStreamStandaloneConformanceTests), nameof(CanShareFiles))] public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests { - public static bool CanShareFiles + public static bool CanShareFiles => _canShareFiles.Value; + + private static Lazy _canShareFiles = new Lazy(() => { - get + if (!PlatformDetection.IsWindowsAndElevated || PlatformDetection.IsWindowsNanoServer) { - if (!PlatformDetection.IsAdmin_IsNotNano) - { - return false; - } + return false; + } - // the "Server Service" allows for file sharing. It can be disabled on some of our CI machines. - using (ServiceController sharingService = new ServiceController("Server")) - { - return sharingService.Status == ServiceControllerStatus.Running; - } + // the "Server Service" allows for file sharing. It can be disabled on some of our CI machines. + using (ServiceController sharingService = new ServiceController("Server")) + { + return sharingService.Status == ServiceControllerStatus.Running; } - } + }); protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) {