Skip to content

Commit 2a1b15d

Browse files
authored
62606 Return fully qualified path from FileSystemEventArgs (#63051)
* FullPath property now returns fully qualified path Gets the fully qualified path after combining the directory and name part as per the thread discussion. Also added and updated tests for the new change. Fix #62606 * OldFullPath returns fully qualified path To be consistent with new behavior of FullPath from FileSystemEventArgs same change was applied for RenamedEventArgs including updated tests. Fix #62606 * Marked some test cases as Windows OS specific In the context of using an actual full path which can vary on Unix vs Windows Fix #62606 * Using Path.Join instead of the custom Combine Adjusted unit tests to match the new logic. Fix #62606 * Account for corner case when fullPath matches root The previous buggy implementation of RenamedEventArgs produced a convenient side-effect for PhysicalFilesWatcher. Without the trailing slash adde by RenamedEventArgs the root was longer than the fullPath by a trailing slash. Fix #62606 * Handle relative paths from CWD in given drive Fix #62606 * Clearly separated unix and windows test cases Implemented most of the code review suggestions. Fix #62606 * Use EnsureTrailingSeparator instead of custom code Also made unit tests more focused by removing needless asserts from the tests Fix #62606 * Keeping the old behavior with trailing separator Reverted the changes to PhysicalFilesWatcher and added back the trailing separator behavior as per code review suggestion Fix #62606 * Used Path.Combien vs PathInternalEnsureSeparator As per code review suggestions. Fix #62606 * Added blank line for readability Pipeline retrigger. Some pipelines timed out, most probably transient issues. Fix #62606
1 parent 26c7d7d commit 2a1b15d

File tree

6 files changed

+172
-50
lines changed

6 files changed

+172
-50
lines changed

src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
<Compile Include="System\IO\WaitForChangedResult.cs" />
2626
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
2727
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
28-
</ItemGroup>
29-
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
28+
<Compile Include="$(CommonPath)System\IO\PathInternal.cs"
29+
Link="Common\System\IO\PathInternal.cs" />
3030
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
3131
Link="Common\System\Text\ValueStringBuilder.cs" />
32+
</ItemGroup>
33+
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
3234
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs"
3335
Link="Common\System\IO\PathInternal.Windows.cs" />
3436
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"

src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,12 @@ public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, stri
1919
{
2020
_changeType = changeType;
2121
_name = name;
22-
_fullPath = Combine(directory, name);
23-
}
22+
_fullPath = Path.Join(Path.GetFullPath(directory), name);
2423

25-
/// <summary>Combines a directory path and a relative file name into a single path.</summary>
26-
/// <param name="directoryPath">The directory path.</param>
27-
/// <param name="name">The file name.</param>
28-
/// <returns>The combined name.</returns>
29-
/// <remarks>
30-
/// This is like Path.Combine, except without argument validation,
31-
/// and a separator is used even if the name argument is empty.
32-
/// </remarks>
33-
internal static string Combine(string directoryPath, string? name)
34-
{
35-
bool hasSeparator = false;
36-
if (directoryPath.Length > 0)
24+
if (string.IsNullOrWhiteSpace(name))
3725
{
38-
char c = directoryPath[directoryPath.Length - 1];
39-
hasSeparator = c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
26+
_fullPath = PathInternal.EnsureTrailingSeparator(_fullPath);
4027
}
41-
42-
return hasSeparator ?
43-
directoryPath + name :
44-
directoryPath + Path.DirectorySeparatorChar + name;
4528
}
4629

4730
/// <devdoc>

src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ public RenamedEventArgs(WatcherChangeTypes changeType, string directory, string?
1818
: base(changeType, directory, name)
1919
{
2020
_oldName = oldName;
21-
_oldFullPath = Combine(directory, oldName);
21+
_oldFullPath = Path.Join(Path.GetFullPath(directory), oldName);
22+
23+
if (string.IsNullOrWhiteSpace(oldName))
24+
{
25+
_oldFullPath = PathInternal.EnsureTrailingSeparator(_oldFullPath);
26+
}
2227
}
2328

2429
/// <devdoc>

src/libraries/System.IO.FileSystem.Watcher/tests/Args.FileSystemEventArgs.cs

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,89 @@ namespace System.IO.Tests
88
public class FileSystemEventArgsTests
99
{
1010
[Theory]
11-
[InlineData(WatcherChangeTypes.Changed, "C:", "foo.txt")]
12-
[InlineData(WatcherChangeTypes.All, "C:", "foo.txt")]
13-
[InlineData((WatcherChangeTypes)0, "", "")]
14-
[InlineData((WatcherChangeTypes)0, "", null)]
15-
public static void FileSystemEventArgs_ctor(WatcherChangeTypes changeType, string directory, string name)
11+
[InlineData(WatcherChangeTypes.Changed, "bar", "foo.txt")]
12+
[InlineData(WatcherChangeTypes.All, "bar", "foo.txt")]
13+
[InlineData((WatcherChangeTypes)0, "bar", "")]
14+
[InlineData((WatcherChangeTypes)0, "bar", null)]
15+
public static void FileSystemEventArgs_ctor_NonPathPropertiesAreSetCorrectly(WatcherChangeTypes changeType, string directory, string name)
1616
{
1717
FileSystemEventArgs args = new FileSystemEventArgs(changeType, directory, name);
1818

19-
if (!directory.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
20-
{
21-
directory += Path.DirectorySeparatorChar;
22-
}
23-
2419
Assert.Equal(changeType, args.ChangeType);
25-
Assert.Equal(directory + name, args.FullPath);
2620
Assert.Equal(name, args.Name);
2721
}
2822

23+
[Theory]
24+
[PlatformSpecific(TestPlatforms.Windows)]
25+
[InlineData("D:\\", "foo.txt", "D:\\foo.txt")]
26+
[InlineData("E:\\bar", "foo.txt", "E:\\bar\\foo.txt")]
27+
public static void FileSystemEventArgs_ctor_DirectoryIsAbsolutePath_Windows(string directory, string name, string expectedFullPath)
28+
{
29+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
30+
31+
Assert.Equal(expectedFullPath, args.FullPath);
32+
}
33+
34+
[Theory]
35+
[PlatformSpecific(TestPlatforms.AnyUnix)]
36+
[InlineData("/", "foo.txt", "/foo.txt")]
37+
[InlineData("/bar", "foo.txt", "/bar/foo.txt")]
38+
public static void FileSystemEventArgs_ctor_DirectoryIsAbsolutePath_Unix(string directory, string name, string expectedFullPath)
39+
{
40+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
41+
42+
Assert.Equal(expectedFullPath, args.FullPath);
43+
}
44+
45+
[Theory]
46+
[PlatformSpecific(TestPlatforms.Windows)]
47+
[InlineData("bar", "foo.txt")]
48+
[InlineData("bar\\baz", "foo.txt")]
49+
public static void FileSystemEventArgs_ctor_DirectoryIsRelativePath_Windows(string directory, string name)
50+
{
51+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
52+
53+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), directory, name), args.FullPath);
54+
}
55+
56+
[Theory]
57+
[PlatformSpecific(TestPlatforms.AnyUnix)]
58+
[InlineData("bar", "foo.txt")]
59+
[InlineData("bar/baz", "foo.txt")]
60+
public static void FileSystemEventArgs_ctor_DirectoryIsRelativePath_Unix(string directory, string name)
61+
{
62+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
63+
64+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), directory, name), args.FullPath);
65+
}
66+
67+
[Theory]
68+
[PlatformSpecific(TestPlatforms.Windows)]
69+
[InlineData("C:", "foo.txt")]
70+
public static void FileSystemEventArgs_ctor_RelativePathFromCurrentDirectoryInGivenDrive(string directory, string name)
71+
{
72+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
73+
74+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), name), args.FullPath);
75+
}
76+
77+
[Theory]
78+
[InlineData("bar", "")]
79+
[InlineData("bar", null)]
80+
public static void FileSystemEventArgs_ctor_When_EmptyFileName_Then_FullPathReturnsTheDirectoryFullPath_WithTrailingSeparator(string directory, string name)
81+
{
82+
FileSystemEventArgs args = new FileSystemEventArgs(WatcherChangeTypes.All, directory, name);
83+
84+
directory = PathInternal.EnsureTrailingSeparator(directory);
85+
86+
Assert.Equal(PathInternal.EnsureTrailingSeparator(Directory.GetCurrentDirectory()) + directory, args.FullPath);
87+
}
88+
2989
[Fact]
3090
public static void FileSystemEventArgs_ctor_Invalid()
3191
{
32-
Assert.Throws<NullReferenceException>(() => new FileSystemEventArgs((WatcherChangeTypes)0, null, string.Empty));
92+
Assert.Throws<ArgumentNullException>(() => new FileSystemEventArgs((WatcherChangeTypes)0, null, "foo.txt"));
93+
Assert.Throws<ArgumentException>(() => new FileSystemEventArgs((WatcherChangeTypes)0, "", "foo.txt"));
3394
}
3495
}
3596
}

src/libraries/System.IO.FileSystem.Watcher/tests/Args.RenamedEventArgs.cs

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,93 @@ namespace System.IO.Tests
88
public class RenamedEventArgsTests
99
{
1010
[Theory]
11-
[InlineData(WatcherChangeTypes.Changed, "C:", "foo.txt", "bar.txt")]
12-
[InlineData(WatcherChangeTypes.All, "C:", "foo.txt", "bar.txt")]
13-
[InlineData((WatcherChangeTypes)0, "", "", "")]
14-
[InlineData((WatcherChangeTypes)0, "", null, null)]
15-
public static void RenamedEventArgs_ctor(WatcherChangeTypes changeType, string directory, string name, string oldName)
11+
[InlineData(WatcherChangeTypes.Changed, "bar", "foo.txt", "bar.txt")]
12+
[InlineData(WatcherChangeTypes.All, "bar", "foo.txt", "bar.txt")]
13+
[InlineData((WatcherChangeTypes)0, "bar", "", "")]
14+
[InlineData((WatcherChangeTypes)0, "bar", null, null)]
15+
public static void RenamedEventArgs_ctor_NonPathPropertiesAreSetCorrectly(WatcherChangeTypes changeType, string directory, string name, string oldName)
1616
{
1717
RenamedEventArgs args = new RenamedEventArgs(changeType, directory, name, oldName);
18+
1819
Assert.Equal(changeType, args.ChangeType);
19-
Assert.Equal(directory + Path.DirectorySeparatorChar + name, args.FullPath);
2020
Assert.Equal(name, args.Name);
2121
Assert.Equal(oldName, args.OldName);
22+
23+
// FullPath is tested as part of the base class FileSystemEventArgs tests
2224
}
2325

2426
[Theory]
25-
[InlineData(WatcherChangeTypes.Changed, "C:", "foo.txt", "bar.txt")]
26-
[InlineData(WatcherChangeTypes.All, "C:", "foo.txt", "bar.txt")]
27-
[InlineData((WatcherChangeTypes)0, "", "", "")]
28-
[InlineData((WatcherChangeTypes)0, "", null, null)]
29-
public static void RenamedEventArgs_ctor_OldFullPath(WatcherChangeTypes changeType, string directory, string name, string oldName)
27+
[PlatformSpecific(TestPlatforms.Windows)]
28+
[InlineData("D:\\", "foo.txt", "bar.txt", "D:\\bar.txt")]
29+
[InlineData("E:\\bar", "foo.txt", "bar.txt", "E:\\bar\\bar.txt")]
30+
public static void RenamedEventArgs_ctor_OldFullPath_DirectoryIsAnAbsolutePath_Windows(string directory, string name, string oldName, string expectedOldFullPath)
3031
{
31-
RenamedEventArgs args = new RenamedEventArgs(changeType, directory, name, oldName);
32-
Assert.Equal(directory + Path.DirectorySeparatorChar + oldName, args.OldFullPath);
32+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
33+
34+
Assert.Equal(expectedOldFullPath, args.OldFullPath);
35+
}
36+
37+
[Theory]
38+
[PlatformSpecific(TestPlatforms.AnyUnix)]
39+
[InlineData("/", "foo.txt", "bar.txt", "/bar.txt")]
40+
[InlineData("/bar", "foo.txt", "bar.txt", "/bar/bar.txt")]
41+
public static void RenamedEventArgs_ctor_OldFullPath_DirectoryIsAnAbsolutePath_Unix(string directory, string name, string oldName, string expectedOldFullPath)
42+
{
43+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
44+
45+
Assert.Equal(expectedOldFullPath, args.OldFullPath);
46+
}
47+
48+
[Theory]
49+
[PlatformSpecific(TestPlatforms.Windows)]
50+
[InlineData("bar", "foo.txt", "bar.txt")]
51+
[InlineData("bar\\baz", "foo.txt", "bar.txt")]
52+
public static void RenamedEventArgs_ctor_OldFullPath_DirectoryIsRelativePath_Windows(string directory, string name, string oldName)
53+
{
54+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
55+
56+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), directory, oldName), args.OldFullPath);
57+
}
58+
59+
[Theory]
60+
[PlatformSpecific(TestPlatforms.AnyUnix)]
61+
[InlineData("bar", "foo.txt", "bar.txt")]
62+
[InlineData("bar/baz", "foo.txt", "bar.txt")]
63+
public static void RenamedEventArgs_ctor_OldFullPath_DirectoryIsRelativePath_Unix(string directory, string name, string oldName)
64+
{
65+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
66+
67+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), directory, oldName), args.OldFullPath);
68+
}
69+
70+
[Theory]
71+
[PlatformSpecific(TestPlatforms.Windows)]
72+
[InlineData("C:", "foo.txt", "bar.txt")]
73+
public static void RenamedEventArgs_ctor_OldFullPath_DirectoryIsRelativePathFromCurrentDirectoryInGivenDrive(string directory, string name, string oldName)
74+
{
75+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
76+
77+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), oldName), args.OldFullPath);
78+
}
79+
80+
[Theory]
81+
[InlineData("bar", "", "")]
82+
[InlineData("bar", null, null)]
83+
[InlineData("bar", "foo.txt", null)]
84+
public static void RenamedEventArgs_ctor_When_EmptyOldFileName_Then_OldFullPathReturnsTheDirectoryFullPath_WithTrailingSeparator(string directory, string name, string oldName)
85+
{
86+
RenamedEventArgs args = new RenamedEventArgs(WatcherChangeTypes.All, directory, name, oldName);
87+
88+
directory = PathInternal.EnsureTrailingSeparator(directory);
89+
90+
Assert.Equal(PathInternal.EnsureTrailingSeparator(Directory.GetCurrentDirectory()) + directory, args.OldFullPath);
3391
}
3492

3593
[Fact]
3694
public static void RenamedEventArgs_ctor_Invalid()
3795
{
38-
Assert.Throws<NullReferenceException>(() => new RenamedEventArgs((WatcherChangeTypes)0, null, string.Empty, string.Empty));
96+
Assert.Throws<ArgumentException>(() => new RenamedEventArgs((WatcherChangeTypes)0, "", "foo.txt", "bar.txt"));
97+
Assert.Throws<ArgumentNullException>(() => new RenamedEventArgs((WatcherChangeTypes)0, null, "foo.txt", "bar.txt"));
3998
}
4099
}
41100
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
44
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
@@ -39,6 +39,18 @@
3939
Link="Common\System\IO\ReparsePointUtilities.cs" />
4040
<Compile Include="$(CommonTestPath)TestUtilities\System\DisableParallelization.cs"
4141
Link="Common\TestUtilities\System\DisableParallelization.cs" />
42+
<Compile Include="$(CommonPath)System\IO\PathInternal.cs"
43+
Link="Common\System\IO\PathInternal.cs" />
44+
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
45+
Link="Common\System\Text\ValueStringBuilder.cs" />
46+
</ItemGroup>
47+
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
48+
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs"
49+
Link="Common\System\IO\PathInternal.Windows.cs" />
50+
</ItemGroup>
51+
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
52+
<Compile Include="$(CommonPath)System\IO\PathInternal.Unix.cs"
53+
Link="Common\System\IO\PathInternal.Unix.cs" />
4254
</ItemGroup>
4355
<ItemGroup Condition="'$(TargetsLinux)' == 'true' or '$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">
4456
<Compile Include="FileSystemWatcher.Unix.cs" />

0 commit comments

Comments
 (0)