Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 20 additions & 6 deletions Source/Testably.Abstractions.Testing/FileSystem/ChangeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Testably.Abstractions.Testing.FileSystem;

internal sealed class ChangeHandler : IInterceptionHandler,
INotificationHandler
internal sealed class ChangeHandler
: IInterceptionHandler, INotificationHandler, IWatcherTriggeredHandler
{
private readonly Notification.INotificationFactory<ChangeDescription>
_changeOccurredCallbacks = Notification.CreateFactory<ChangeDescription>();
Expand All @@ -15,6 +15,9 @@ private readonly Notification.INotificationFactory<ChangeDescription>

private readonly MockFileSystem _mockFileSystem;

private readonly Notification.INotificationFactory<ChangeDescription>
_watcherNotificationTriggeredCallbacks = Notification.CreateFactory<ChangeDescription>();

public ChangeHandler(MockFileSystem mockFileSystem)
{
_mockFileSystem = mockFileSystem;
Expand All @@ -25,8 +28,7 @@ public ChangeHandler(MockFileSystem mockFileSystem)
/// <inheritdoc cref="IFileSystemEntity.FileSystem" />
public IFileSystem FileSystem => _mockFileSystem;

/// <inheritdoc
/// cref="IInterceptionHandler.Event" />
/// <inheritdoc cref="IInterceptionHandler.Event" />
public IAwaitableCallback<ChangeDescription> Event(
Action<ChangeDescription> interceptionCallback,
Func<ChangeDescription, bool>? predicate = null)
Expand All @@ -36,15 +38,24 @@ public IAwaitableCallback<ChangeDescription> Event(

#region INotificationHandler Members

/// <inheritdoc
/// cref="INotificationHandler.OnEvent" />
/// <inheritdoc cref="INotificationHandler.OnEvent" />
public IAwaitableCallback<ChangeDescription> OnEvent(
Action<ChangeDescription>? notificationCallback = null,
Func<ChangeDescription, bool>? predicate = null)
=> _changeOccurredCallbacks.RegisterCallback(notificationCallback, predicate);

#endregion

#region IWatcherTriggeredHandler Members

/// <inheritdoc cref="IWatcherTriggeredHandler.OnTriggered" />
public IAwaitableCallback<ChangeDescription> OnTriggered(
Action<ChangeDescription>? triggerCallback = null,
Func<ChangeDescription, bool>? predicate = null)
=> _watcherNotificationTriggeredCallbacks.RegisterCallback(triggerCallback, predicate);

#endregion

internal void NotifyCompletedChange(ChangeDescription? fileSystemChange)
{
if (fileSystemChange != null)
Expand All @@ -65,4 +76,7 @@ internal ChangeDescription NotifyPendingChange(WatcherChangeTypes changeType,
_changeOccurringCallbacks.InvokeCallbacks(fileSystemChange);
return fileSystemChange;
}

internal void NotifyWatcherTriggeredChange(ChangeDescription fileSystemChange)
=> _watcherNotificationTriggeredCallbacks.InvokeCallbacks(fileSystemChange);
}
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ private void NotifyChange(ChangeDescription item)
{
TriggerRenameNotification(item);
}

_fileSystem.ChangeHandler.NotifyWatcherTriggeredChange(item);
}
}

Expand Down Expand Up @@ -664,7 +666,8 @@ private void TriggerMacRenameNotification(ChangeDescription item, RenamedContext
{
CheckRenamePremise(context);

if (context.ComesFromInside && TryMakeRenamedEventArgs(item, out RenamedEventArgs? eventArgs))
if (context.ComesFromInside &&
TryMakeRenamedEventArgs(item, out RenamedEventArgs? eventArgs))
{
Renamed?.Invoke(this, eventArgs);
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace Testably.Abstractions.Testing.FileSystem;

/// <summary>
/// The notification handler for triggered notifications from the <see cref="IFileSystemWatcher" />.
/// </summary>
public interface IWatcherTriggeredHandler : IFileSystemEntity
{
/// <summary>
/// Callback executed after the <see cref="IFileSystemWatcher" /> notified about a change in the
/// <see cref="MockFileSystem" /> matching the <paramref name="predicate" />.
/// </summary>
/// <param name="triggerCallback">The callback to execute after the notification was triggered.</param>
/// <param name="predicate">
/// (optional) A predicate used to filter which callbacks should be notified.<br />
/// If set to <see langword="null" /> (default value) all callbacks are notified.
/// </param>
/// <returns>An <see cref="IAwaitableCallback{ChangeDescription}" /> to un-register the callback on dispose.</returns>
IAwaitableCallback<ChangeDescription> OnTriggered(
Action<ChangeDescription>? triggerCallback = null,
Func<ChangeDescription, bool>? predicate = null);
}
8 changes: 7 additions & 1 deletion Source/Testably.Abstractions.Testing/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public sealed class MockFileSystem : IFileSystem
/// </summary>
public INotificationHandler Notify => ChangeHandler;

/// <summary>
/// Get notified of events after they were triggered by a <see cref="IFileSystemWatcher" />.
/// </summary>
public IWatcherTriggeredHandler Watcher => ChangeHandler;

/// <summary>
/// The used random system.
/// </summary>
Expand Down Expand Up @@ -223,7 +228,8 @@ public MockFileSystem WithDrive(string? drive,
Action<IStorageDrive>? driveCallback = null)
{
IStorageDrive driveInfoMock =
drive == null || string.Equals(drive, Execute.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
drive == null || string.Equals(drive,
Execute.Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
? Storage.MainDrive
: Storage.GetOrAddDrive(drive);
driveCallback?.Invoke(driveInfoMock);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ namespace Testably.Abstractions.Testing.FileSystem
bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode, System.IO.FileAccess requestedAccess);
void OnSetUnixFileMode(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -124,6 +128,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ namespace Testably.Abstractions.Testing.FileSystem
{
Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -113,6 +117,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ namespace Testably.Abstractions.Testing.FileSystem
bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode, System.IO.FileAccess requestedAccess);
void OnSetUnixFileMode(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -124,6 +128,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ namespace Testably.Abstractions.Testing.FileSystem
bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode, System.IO.FileAccess requestedAccess);
void OnSetUnixFileMode(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility, System.IO.UnixFileMode mode);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -124,6 +128,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ namespace Testably.Abstractions.Testing.FileSystem
{
Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -111,6 +115,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ namespace Testably.Abstractions.Testing.FileSystem
{
Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle);
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
Testably.Abstractions.Testing.IAwaitableCallback<Testably.Abstractions.Testing.FileSystem.ChangeDescription> OnTriggered(System.Action<Testably.Abstractions.Testing.FileSystem.ChangeDescription>? triggerCallback = null, System.Func<Testably.Abstractions.Testing.FileSystem.ChangeDescription, bool>? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
public NullAccessControlStrategy() { }
Expand Down Expand Up @@ -111,6 +115,7 @@ namespace Testably.Abstractions.Testing
public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; }
public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; }
public Testably.Abstractions.ITimeSystem TimeSystem { get; }
public Testably.Abstractions.Testing.FileSystem.IWatcherTriggeredHandler Watcher { get; }
public override string ToString() { }
public Testably.Abstractions.Testing.MockFileSystem WithAccessControlStrategy(Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy accessControlStrategy) { }
public Testably.Abstractions.Testing.MockFileSystem WithDrive(string? drive, System.Action<Testably.Abstractions.Testing.Storage.IStorageDrive>? driveCallback = null) { }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Synchronous;
using System.IO;
using Testably.Abstractions.Testing.FileSystem;

namespace Testably.Abstractions.Testing.Tests.FileSystem;

Expand Down Expand Up @@ -98,6 +99,39 @@ public async Task ExecuteCallback_ShouldTriggerNotification(
await That(receivedPath).IsEqualTo(FileSystem.Path.GetFullPath(path));
}

[Fact]
public async Task Watcher_ShouldNotTriggerWhenFileSystemWatcherDoesNotMatch()
{
FileSystem.Directory.CreateDirectory("bar");
IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New("bar");
watcher.EnableRaisingEvents = true;

IAwaitableCallback<ChangeDescription> onEvent = FileSystem.Watcher.OnTriggered();

void Act() =>
onEvent.Wait(timeout: 100,
executeWhenWaiting: () => FileSystem.File.WriteAllText(@"foo.txt", "some-text"));

await That(Act).Throws<TimeoutException>();
}

[Fact]
public async Task Watcher_ShouldTriggerWhenFileSystemWatcherSendsNotification()
{
bool isTriggered = false;
FileSystem.InitializeIn(".");
IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New(".");
watcher.Created += (_, _) => isTriggered = true;
watcher.EnableRaisingEvents = true;

IAwaitableCallback<ChangeDescription> onEvent = FileSystem.Watcher.OnTriggered();

onEvent.Wait(timeout: 5000,
executeWhenWaiting: () => FileSystem.File.WriteAllText(@"foo.txt", "some-text"));

await That(isTriggered).IsTrue();
}

#region Helpers

public static
Expand Down
Loading