Skip to content

Commit 7833828

Browse files
authored
[FileStream] add tests for device and UNC paths (#54545)
* add a test for unseekable device by using a path to named pipe * add a test for seekable device by using DeviceID instead of drive letter * add a test for a UNC file path (local file share)
1 parent f55390a commit 7833828

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed

src/libraries/Common/tests/TestUtilities/System/IO/FileCleanupTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected virtual void Dispose(bool disposing)
7979
/// <param name="index">An optional index value to use as a suffix on the file name. Typically a loop index.</param>
8080
/// <param name="memberName">The member name of the function calling this method.</param>
8181
/// <param name="lineNumber">The line number of the function calling this method.</param>
82-
protected string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) =>
82+
protected virtual string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0) =>
8383
Path.Combine(TestDirectory, GetTestFileName(index, memberName, lineNumber));
8484

8585
/// <summary>Gets a test file name that is associated with the call site.</summary>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
using System.ComponentModel;
6+
using System.IO.Pipes;
7+
using System.Linq;
8+
using System.Runtime.CompilerServices;
9+
using System.Runtime.InteropServices;
10+
using System.Text;
11+
using System.ServiceProcess;
12+
using System.Threading.Tasks;
13+
using Xunit;
14+
15+
namespace System.IO.Tests
16+
{
17+
[PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept
18+
public class UnseekableDeviceFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests
19+
{
20+
protected override async Task<StreamPair> CreateConnectedStreamsAsync()
21+
{
22+
string pipeName = FileSystemTest.GetNamedPipeServerStreamName();
23+
string pipePath = Path.GetFullPath($@"\\.\pipe\{pipeName}");
24+
25+
var server = new NamedPipeServerStream(pipeName, PipeDirection.In);
26+
var clienStream = new FileStream(File.OpenHandle(pipePath, FileMode.Open, FileAccess.Write, FileShare.None), FileAccess.Write);
27+
28+
await server.WaitForConnectionAsync();
29+
30+
var serverStrean = new FileStream(new SafeFileHandle(server.SafePipeHandle.DangerousGetHandle(), true), FileAccess.Read);
31+
32+
server.SafePipeHandle.SetHandleAsInvalid();
33+
34+
return (serverStrean, clienStream);
35+
}
36+
37+
protected override Type UnsupportedConcurrentExceptionType => null;
38+
protected override bool UsableAfterCanceledReads => false;
39+
protected override bool FullyCancelableOperations => false;
40+
protected override bool BlocksOnZeroByteReads => OperatingSystem.IsWindows();
41+
protected override bool SupportsConcurrentBidirectionalUse => false;
42+
}
43+
44+
[PlatformSpecific(TestPlatforms.Windows)] // DOS device paths (\\.\ and \\?\) are a Windows concept
45+
public class SeekableDeviceFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests
46+
{
47+
protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0)
48+
{
49+
string filePath = Path.GetFullPath(base.GetTestFilePath(index, memberName, lineNumber));
50+
string drive = Path.GetPathRoot(filePath);
51+
StringBuilder volumeNameBuffer = new StringBuilder(filePath.Length + 1024);
52+
53+
// the following method maps drive letter like "C:\" to a DeviceID (a DOS device path)
54+
// example: "\\?\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\"
55+
if (!GetVolumeNameForVolumeMountPoint(drive, volumeNameBuffer, volumeNameBuffer.Capacity))
56+
{
57+
throw new Win32Exception(Marshal.GetLastPInvokeError(), "GetVolumeNameForVolumeMountPoint failed");
58+
}
59+
60+
// instead of:
61+
// 'C:\Users\x\AppData\Local\Temp\y\z
62+
// we want something like:
63+
// '\\.\Volume{724edb31-eaa5-4728-a4e3-f2474fd34ae2}\Users\x\AppData\Local\Temp\y\z
64+
string devicePath = filePath.Replace(drive, volumeNameBuffer.ToString());
65+
Assert.StartsWith(@"\\?\", devicePath);
66+
#if DEBUG
67+
// we do want to test \\.\ prefix as well
68+
devicePath = devicePath.Replace(@"\\?\", @"\\.\");
69+
#endif
70+
71+
return devicePath;
72+
}
73+
74+
[DllImport(Interop.Libraries.Kernel32, EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
75+
private static extern bool GetVolumeNameForVolumeMountPoint(string volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity);
76+
}
77+
78+
[PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc
79+
[Collection("NoParallelTests")] // don't run in parallel, as file sharing logic is not thread-safe
80+
[OuterLoop("Requires admin privileges to create a file share")]
81+
[ConditionalClass(typeof(UncFilePathFileStreamStandaloneConformanceTests), nameof(CanShareFiles))]
82+
public class UncFilePathFileStreamStandaloneConformanceTests : UnbufferedAsyncFileStreamStandaloneConformanceTests
83+
{
84+
public static bool CanShareFiles => _canShareFiles.Value;
85+
86+
private static Lazy<bool> _canShareFiles = new Lazy<bool>(() =>
87+
{
88+
if (!PlatformDetection.IsWindowsAndElevated || PlatformDetection.IsWindowsNanoServer)
89+
{
90+
return false;
91+
}
92+
93+
// the "Server Service" allows for file sharing. It can be disabled on some of our CI machines.
94+
using (ServiceController sharingService = new ServiceController("Server"))
95+
{
96+
return sharingService.Status == ServiceControllerStatus.Running;
97+
}
98+
});
99+
100+
protected override string GetTestFilePath(int? index = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0)
101+
{
102+
string testDirectoryPath = Path.GetFullPath(TestDirectory);
103+
string shareName = new DirectoryInfo(testDirectoryPath).Name;
104+
string fileName = GetTestFileName(index, memberName, lineNumber);
105+
106+
SHARE_INFO_502 shareInfo = default;
107+
shareInfo.shi502_netname = shareName;
108+
shareInfo.shi502_path = testDirectoryPath;
109+
shareInfo.shi502_remark = "folder created to test UNC file paths";
110+
shareInfo.shi502_max_uses = -1;
111+
112+
int infoSize = Marshal.SizeOf(shareInfo);
113+
IntPtr infoBuffer = Marshal.AllocCoTaskMem(infoSize);
114+
115+
try
116+
{
117+
Marshal.StructureToPtr(shareInfo, infoBuffer, false);
118+
119+
int shareResult = NetShareAdd(string.Empty, 502, infoBuffer, IntPtr.Zero);
120+
121+
if (shareResult != 0 && shareResult != 2118) // is a failure that is not a NERR_DuplicateShare
122+
{
123+
throw new Exception($"Failed to create a file share, NetShareAdd returned {shareResult}");
124+
}
125+
}
126+
finally
127+
{
128+
Marshal.FreeCoTaskMem(infoBuffer);
129+
}
130+
131+
// now once the folder has been shared we can use "localhost" to access it:
132+
// both type of slashes are valid, so let's test one for Debug and another for other configs
133+
#if DEBUG
134+
return @$"//localhost/{shareName}/{fileName}";
135+
#else
136+
return @$"\\localhost\{shareName}\{fileName}";
137+
#endif
138+
}
139+
140+
protected override void Dispose(bool disposing)
141+
{
142+
string testDirectoryPath = Path.GetFullPath(TestDirectory);
143+
string shareName = new DirectoryInfo(testDirectoryPath).Name;
144+
145+
try
146+
{
147+
NetShareDel(string.Empty, shareName, 0);
148+
}
149+
finally
150+
{
151+
base.Dispose(disposing);
152+
}
153+
}
154+
155+
[StructLayout(LayoutKind.Sequential)]
156+
public struct SHARE_INFO_502
157+
{
158+
[MarshalAs(UnmanagedType.LPWStr)]
159+
public string shi502_netname;
160+
public uint shi502_type;
161+
[MarshalAs(UnmanagedType.LPWStr)]
162+
public string shi502_remark;
163+
public int shi502_permissions;
164+
public int shi502_max_uses;
165+
public int shi502_current_uses;
166+
[MarshalAs(UnmanagedType.LPWStr)]
167+
public string shi502_path;
168+
public IntPtr shi502_passwd;
169+
public int shi502_reserved;
170+
public IntPtr shi502_security_descriptor;
171+
}
172+
173+
[DllImport(Interop.Libraries.Netapi32)]
174+
public static extern int NetShareAdd([MarshalAs(UnmanagedType.LPWStr)]string servername, int level, IntPtr buf, IntPtr parm_err);
175+
176+
[DllImport(Interop.Libraries.Netapi32)]
177+
public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved);
178+
}
179+
}

src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
2626
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
2727
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
28+
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
2829
</ItemGroup>
2930
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
3031
<Compile Remove="..\**\*.Unix.cs" />

src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
7373
<Compile Include="FileSystemTest.Windows.cs" />
7474
<Compile Include="FileStream\ctor_options_as.Windows.cs" />
75+
<Compile Include="FileStream\FileStreamConformanceTests.Windows.cs" />
7576
<Compile Include="RandomAccess\NoBuffering.Windows.cs" />
7677
<Compile Include="RandomAccess\SectorAlignedMemory.Windows.cs" />
7778
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
@@ -80,6 +81,7 @@
8081
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
8182
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
8283
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
84+
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
8385
</ItemGroup>
8486
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
8587
<Compile Include="FileSystemTest.Browser.cs" />

0 commit comments

Comments
 (0)