Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f515710
Relax LinkTarget so it always returns null when steps on an error
jozkee Jul 14, 2021
08233ba
Make polling use the symbolic link target's LastWriteTime
jozkee Jul 14, 2021
d780995
Fix for the case where the link can change its target
jozkee Jul 15, 2021
ac4a845
Add more test cases and exclude them from non-netcoreapp tfms
jozkee Jul 15, 2021
b4895ad
Fix project references in ref projects
jozkee Jul 15, 2021
ebb0326
Do not use UnsupportedOSPlatforms on test project in order to fix CI …
jozkee Jul 16, 2021
1164e33
Do not return link's LastWriteTime when target not exists
jozkee Jul 19, 2021
98b737a
Address feedback on tests and improve them to cover more scenarios.
jozkee Jul 19, 2021
75fcf96
Make the project unsupported in browser.
jozkee Jul 19, 2021
144335a
Fix duplicate reference to PlatformAttributes with IncludePlatformAtt…
jozkee Jul 19, 2021
9c50a3a
Disable default items for browser
jozkee Jul 20, 2021
b2f9bad
Undo unrelated changes to Strings.resx
jozkee Jul 20, 2021
d8d143a
Replace Thread.Sleep with Task.Delay, add assertion messages to try t…
jozkee Jul 20, 2021
c7e28d4
Replace HasChanged for RegisterChangeCallback in tests
jozkee Jul 21, 2021
5b85631
Add messages to asserts to attempt to debug CI issues
jozkee Jul 21, 2021
5c27cae
Add date format to assertion messages.
jozkee Jul 21, 2021
19dd9c3
Increase delay between writes to one second since OSX doesn't report …
jozkee Jul 21, 2021
8c1b3a2
Merge branch 'main' of https://github.com/dotnet/runtime into symlink…
jozkee Jul 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add more test cases and exclude them from non-netcoreapp tfms
  • Loading branch information
jozkee committed Jul 15, 2021
commit ac4a8457c5b4f8cab4a1c0cc0132846646241a41
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>Microsoft.Extensions.FileProviders.Physical</RootNamespace>
Expand All @@ -12,6 +12,10 @@
Link="Common\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
<Compile Remove="PhysicalFileProviderTests.netcoreapp.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<ProjectReference Include="..\src\Microsoft.Extensions.FileProviders.Physical.csproj" SkipUseReferenceAssembly="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace Microsoft.Extensions.FileProviders
{
public class PhysicalFileProviderTests
public partial class PhysicalFileProviderTests
{
private const int WaitTimeForTokenToFire = 500;
private const int WaitTimeForTokenCallback = 10000;
Expand Down Expand Up @@ -1512,68 +1512,6 @@ public void UsePollingFileWatcher_FileWatcherNotNull_ReturnsFalse()
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged(bool useWildcard)
{
// Arrange
using var root = new DisposableFileSystem();
string fileName = Path.GetRandomFileName();
string filePath = Path.Combine(root.RootPath, fileName);
File.WriteAllText(filePath, "v1.1");

using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : fileName);
Assert.False(token.HasChanged);

// Act
File.WriteAllText(filePath, "v1.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert
Assert.True(token.HasChanged);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink(bool useWildcard)
{
// Arrange
using var rootOfFile = new DisposableFileSystem();
string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
File.WriteAllText(filePath, "v1.1");

using var rootOfLink = new DisposableFileSystem();
string linkName = Path.GetRandomFileName();
string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
File.CreateSymbolicLink(linkPath, filePath);

using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);
Assert.False(token.HasChanged);

// Act
File.WriteAllText(filePath, "v1.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert
Assert.True(token.HasChanged);
}

private int GetTokenPollingInterval(IChangeToken changeToken)
{
TimeSpan pollingInterval = (changeToken as CompositeChangeToken).ChangeTokens[1] switch
{
PollingWildCardChangeToken wildcardChangeToken => wildcardChangeToken.PollingInterval,
PollingFileChangeToken => PollingFileChangeToken.PollingInterval,
_ => throw new InvalidOperationException()
};

return (int)pollingInterval.TotalMilliseconds;
}

[Fact]
public void CreateFileWatcher_CreatesWatcherWithPollingAndActiveFlags()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 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.Threading;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Microsoft.Extensions.FileProviders
{
public partial class PhysicalFileProviderTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged(bool useWildcard)
{
// Arrange
using var root = new DisposableFileSystem();
string fileName = Path.GetRandomFileName();
string filePath = Path.Combine(root.RootPath, fileName);
File.WriteAllText(filePath, "v1.1");

using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : fileName);
Assert.False(token.HasChanged);

// Act
File.WriteAllText(filePath, "v1.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert
Assert.True(token.HasChanged);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink(bool useWildcard)
{
// Arrange
using var rootOfFile = new DisposableFileSystem();
string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
File.WriteAllText(filePath, "v1.1");

using var rootOfLink = new DisposableFileSystem();
string linkName = Path.GetRandomFileName();
string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
File.CreateSymbolicLink(linkPath, filePath);

using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);
Assert.False(token.HasChanged);

// Act
File.WriteAllText(filePath, "v1.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert
Assert.True(token.HasChanged);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetNotExists(bool useWildcard)
{
// Arrange
using var rootOfLink = new DisposableFileSystem();
string linkName = Path.GetRandomFileName();
string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
File.CreateSymbolicLink(linkPath, "not-existent-file");

// Act
using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);

// Assert
Assert.False(token.HasChanged);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetChanged(bool useWildcard)
{
// Arrange
using var rootOfFile = new DisposableFileSystem();
string file1Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
File.WriteAllText(file1Path, "v1.1");

string file2Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
File.WriteAllText(file2Path, "v2.1");

using var rootOfLink = new DisposableFileSystem();
string linkName = Path.GetRandomFileName();
string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
File.CreateSymbolicLink(linkPath, file1Path);

string filter = useWildcard ? "*" : linkName;
using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
IChangeToken token = provider.Watch(filter);
Assert.False(token.HasChanged);

// Act 1 - Change file 1's content.
File.WriteAllText(file1Path, "v1.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert 1
Assert.True(token.HasChanged);

// Act 2 - Change link target to file 2.
token = provider.Watch(filter); // Once HasChanged is true, the value will always be true. Get a new change token.
Assert.False(token.HasChanged);
File.Delete(linkPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to reset the target without deleting the link?

Copy link
Member Author

@jozkee jozkee Jul 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Through File.CreateSymbolicLink, it is not. It may be possible to modify the target of an existing link by using FSCTL_SET_REPARSE_POINT, see https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-point-operations.

For Unix, I know that you can use the -f switch in ln -sf bar foo to force the link to be created even if it already exists, but I guess that's just deleting the old link.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ln.html says that -f in ln is functionally equivalent to "call unlink", which we call File.Delete.

File.CreateSymbolicLink(linkPath, file2Path);
Thread.Sleep(GetTokenPollingInterval(token));

// Assert 2
Assert.True(token.HasChanged); // It should report the change regardless of the timestamp being older.

// Act 3 - Change file 2's content.
token = provider.Watch(filter);
Assert.False(token.HasChanged);
File.WriteAllText(file2Path, "v2.2");
Thread.Sleep(GetTokenPollingInterval(token));

// Assert 3
Assert.True(token.HasChanged);
}

private int GetTokenPollingInterval(IChangeToken changeToken)
{
TimeSpan pollingInterval = (changeToken as CompositeChangeToken).ChangeTokens[1] switch
{
PollingWildCardChangeToken wildcardChangeToken => wildcardChangeToken.PollingInterval,
PollingFileChangeToken => PollingFileChangeToken.PollingInterval,
_ => throw new InvalidOperationException()
};

return (int)pollingInterval.TotalMilliseconds;
}
}
}