diff --git a/Directory.Packages.props b/Directory.Packages.props index 66093eba9..e7ae96fd3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - + diff --git a/Feature.Flags.props b/Feature.Flags.props index 222a6244c..2d16c5bf2 100644 --- a/Feature.Flags.props +++ b/Feature.Flags.props @@ -1,9 +1,10 @@ - 1 - 1 - 1 + 1 + 1 + 1 + 1 $(DefineConstants);NETFRAMEWORK $(DefineConstants);CAN_SIMULATE_OTHER_OS @@ -30,6 +31,9 @@ $(DefineConstants);FEATURE_GUID_FORMATPROVIDER $(DefineConstants);FEATURE_RANDOM_ITEMS $(DefineConstants);FEATURE_COMPRESSION_STREAM + $(DefineConstants);FEATURE_PATH_SPAN + $(DefineConstants);FEATURE_FILE_SPAN + $(DefineConstants);FEATURE_GUID_V7 diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index 81054c19a..ec85e9df3 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -13,7 +13,7 @@ - net6.0;net8.0;netstandard2.1;netstandard2.0 + net6.0;net8.0;net9.0;netstandard2.1;netstandard2.0 netstandard2.0 diff --git a/Source/Testably.Abstractions.Interface/Helpers/GuidSystemBase.cs b/Source/Testably.Abstractions.Interface/Helpers/GuidSystemBase.cs index 9cdc33156..e3cbe06ac 100644 --- a/Source/Testably.Abstractions.Interface/Helpers/GuidSystemBase.cs +++ b/Source/Testably.Abstractions.Interface/Helpers/GuidSystemBase.cs @@ -32,6 +32,16 @@ protected GuidSystemBase(IRandomSystem randomSystem) /// public abstract Guid NewGuid(); +#if FEATURE_GUID_V7 + /// + public abstract Guid CreateVersion7(); +#endif + +#if FEATURE_GUID_V7 + /// + public abstract Guid CreateVersion7(DateTimeOffset timestamp); +#endif + #if FEATURE_GUID_PARSE #pragma warning disable MA0011 /// diff --git a/Source/Testably.Abstractions.Interface/RandomSystem/IGuid.cs b/Source/Testably.Abstractions.Interface/RandomSystem/IGuid.cs index 18f8e2a85..61b10d62c 100644 --- a/Source/Testably.Abstractions.Interface/RandomSystem/IGuid.cs +++ b/Source/Testably.Abstractions.Interface/RandomSystem/IGuid.cs @@ -16,6 +16,16 @@ public interface IGuid : IRandomSystemEntity /// Guid NewGuid(); +#if FEATURE_GUID_V7 + /// + Guid CreateVersion7(); +#endif + +#if FEATURE_GUID_V7 + /// + Guid CreateVersion7(DateTimeOffset timestamp); +#endif + #if FEATURE_GUID_PARSE /// Guid Parse(string input); diff --git a/Source/Testably.Abstractions.Testing/FileSystem/FileMock.cs b/Source/Testably.Abstractions.Testing/FileSystem/FileMock.cs index 914d3cf81..4a3be755b 100644 --- a/Source/Testably.Abstractions.Testing/FileSystem/FileMock.cs +++ b/Source/Testably.Abstractions.Testing/FileSystem/FileMock.cs @@ -31,6 +31,74 @@ internal FileMock(MockFileSystem fileSystem) /// public IFileSystem FileSystem => _fileSystem; + +#if FEATURE_FILE_SPAN + /// + public void AppendAllBytes(string path, byte[] bytes) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllBytes), + path, bytes); + + IStorageContainer container = + _fileSystem.Storage.GetOrCreateContainer( + _fileSystem.Storage.GetLocation( + path.EnsureValidFormat(_fileSystem)), + InMemoryContainer.NewFile); + + if (container.Type != FileSystemTypes.File) + { + throw ExceptionFactory.AccessToPathDenied(path); + } + + using (container.RequestAccess( + FileAccess.ReadWrite, + FileStreamFactoryMock.DefaultShare)) + { + container.AppendBytes(bytes); + } + } +#endif + +#if FEATURE_FILE_SPAN + /// + public void AppendAllBytes(string path, ReadOnlySpan bytes) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllBytes), + path, bytes); + + AppendAllBytes(path, bytes.ToArray()); + } +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllBytesAsync), + path, bytes, cancellationToken); + + ThrowIfCancelled(cancellationToken); + AppendAllBytes(path, bytes); + return Task.CompletedTask; + } +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllBytesAsync(string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllBytesAsync), + path, bytes, cancellationToken); + + ThrowIfCancelled(cancellationToken); + AppendAllBytes(path, bytes.ToArray()); + return Task.CompletedTask; + } +#endif /// public void AppendAllLines(string path, IEnumerable contents) @@ -132,6 +200,30 @@ public void AppendAllText(string path, string? contents, Encoding encoding) } } } + +#if FEATURE_FILE_SPAN + /// + public void AppendAllText(string path, ReadOnlySpan contents) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllText), + path, contents); + + AppendAllText(path, contents.ToString(), Encoding.Default); + } +#endif + +#if FEATURE_FILE_SPAN + /// + public void AppendAllText(string path, ReadOnlySpan contents, Encoding encoding) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllText), + path, contents, encoding); + + AppendAllText(path, contents.ToString(), encoding); + } +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -160,6 +252,35 @@ public Task AppendAllTextAsync(string path, string? contents, Encoding encoding, return Task.CompletedTask; } #endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllTextAsync(string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllTextAsync), + path, contents, cancellationToken); + + ThrowIfCancelled(cancellationToken); + AppendAllText(path, contents.ToString(), Encoding.Default); + return Task.CompletedTask; + } +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllTextAsync(string path, ReadOnlyMemory contents, Encoding encoding, + CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(AppendAllTextAsync), + path, contents, encoding, cancellationToken); + + ThrowIfCancelled(cancellationToken); + AppendAllText(path, contents.ToString(), encoding); + return Task.CompletedTask; + } +#endif /// public StreamWriter AppendText(string path) @@ -1241,6 +1362,18 @@ public void WriteAllBytes(string path, byte[] bytes) container.WriteBytes(bytes); } } + +#if FEATURE_FILE_SPAN + /// + public void WriteAllBytes(string path, ReadOnlySpan bytes) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllBytes), + path, bytes); + + WriteAllBytes(path, bytes.ToArray()); + } +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -1256,6 +1389,20 @@ public Task WriteAllBytesAsync(string path, byte[] bytes, return Task.CompletedTask; } #endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllBytesAsync(string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllBytesAsync), + path, bytes, cancellationToken); + + ThrowIfCancelled(cancellationToken); + WriteAllBytes(path, bytes.ToArray()); + return Task.CompletedTask; + } +#endif /// public void WriteAllLines(string path, string[] contents) @@ -1388,6 +1535,31 @@ public void WriteAllText(string path, string? contents, Encoding encoding) } } } + +#if FEATURE_FILE_SPAN + /// + public void WriteAllText(string path, ReadOnlySpan contents) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllText), + path, contents); + + WriteAllText(path, contents.ToString(), Encoding.Default); + } +#endif + +#if FEATURE_FILE_SPAN + + /// + public void WriteAllText(string path, ReadOnlySpan contents, Encoding encoding) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllText), + path, contents, encoding); + + WriteAllText(path, contents.ToString(), encoding); + } +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -1416,6 +1588,35 @@ public Task WriteAllTextAsync(string path, string? contents, Encoding encoding, return Task.CompletedTask; } #endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllTextAsync(string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllTextAsync), + path, contents, cancellationToken); + + ThrowIfCancelled(cancellationToken); + WriteAllText(path, contents.ToString(), Encoding.Default); + return Task.CompletedTask; + } +#endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllTextAsync(string path, ReadOnlyMemory contents, Encoding encoding, + CancellationToken cancellationToken = default) + { + using IDisposable registration = _fileSystem.StatisticsRegistration + .File.RegisterMethod(nameof(WriteAllTextAsync), + path, contents, encoding, cancellationToken); + + ThrowIfCancelled(cancellationToken); + WriteAllText(path, contents.ToString(), encoding); + return Task.CompletedTask; + } +#endif #endregion diff --git a/Source/Testably.Abstractions.Testing/FileSystem/PathMock.cs b/Source/Testably.Abstractions.Testing/FileSystem/PathMock.cs index 174c81425..b7513e3a7 100644 --- a/Source/Testably.Abstractions.Testing/FileSystem/PathMock.cs +++ b/Source/Testably.Abstractions.Testing/FileSystem/PathMock.cs @@ -117,6 +117,18 @@ public string Combine(params string[] paths) return _fileSystem.Execute.Path.Combine(paths); } + +#if FEATURE_PATH_SPAN + /// + public string Combine(params ReadOnlySpan paths) + { + using IDisposable register = _fileSystem.StatisticsRegistration + .Path.RegisterMethod(nameof(Combine), + paths); + + return _fileSystem.Execute.Path.Combine(paths); + } +#endif #if FEATURE_PATH_ADVANCED /// @@ -512,6 +524,18 @@ public string Join(params string?[] paths) return _fileSystem.Execute.Path.Join(paths); } #endif + +#if FEATURE_PATH_SPAN + /// + public string Join(params ReadOnlySpan paths) + { + using IDisposable register = _fileSystem.StatisticsRegistration + .Path.RegisterMethod(nameof(Join), + paths); + + return _fileSystem.Execute.Path.Join(paths); + } +#endif #if FEATURE_PATH_ADVANCED /// diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs index c262e1ae0..27ee67618 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs @@ -54,6 +54,12 @@ public string Combine(string path1, string path2, string path3, string path4) /// public string Combine(params string[] paths) => System.IO.Path.Combine(paths); + +#if FEATURE_PATH_SPAN + /// + public string Combine(params ReadOnlySpan paths) + => System.IO.Path.Combine(paths); +#endif #if FEATURE_PATH_ADVANCED /// @@ -279,6 +285,12 @@ public string Join(string? path1, string? path2, string? path3, string? path4) public string Join(params string?[] paths) => System.IO.Path.Join(paths); #endif + +#if FEATURE_PATH_SPAN + /// + public string Join(params ReadOnlySpan paths) + => System.IO.Path.Join(paths); +#endif #if FEATURE_PATH_ADVANCED /// diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index 4d454ecab..7bb43f883 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -128,6 +128,12 @@ public string Combine(string path1, string path2, string path3, string path4) /// public string Combine(params string[] paths) => CombineInternal(paths); + +#if FEATURE_PATH_SPAN + /// + public string Combine(params ReadOnlySpan paths) + => CombineInternal(paths.ToArray()); +#endif #if FEATURE_PATH_ADVANCED /// @@ -440,6 +446,12 @@ public string Join(string? path1, string? path2, string? path3, string? path4) public string Join(params string?[] paths) => JoinInternal(paths); #endif + +#if FEATURE_PATH_SPAN + /// + public string Join(params ReadOnlySpan paths) + => JoinInternal(paths.ToArray()); +#endif #if FEATURE_PATH_ADVANCED /// diff --git a/Source/Testably.Abstractions.Testing/RandomSystem/GuidMock.cs b/Source/Testably.Abstractions.Testing/RandomSystem/GuidMock.cs index 182b2c8be..47b34a432 100644 --- a/Source/Testably.Abstractions.Testing/RandomSystem/GuidMock.cs +++ b/Source/Testably.Abstractions.Testing/RandomSystem/GuidMock.cs @@ -16,4 +16,16 @@ internal GuidMock(MockRandomSystem randomSystem) : base(randomSystem) /// public override Guid NewGuid() => _mockRandomSystem.RandomProvider.GetGuid(); + +#if FEATURE_GUID_V7 + /// + public override Guid CreateVersion7() + => _mockRandomSystem.RandomProvider.GetGuid(); +#endif + +#if FEATURE_GUID_V7 + /// + public override Guid CreateVersion7(DateTimeOffset timestamp) + => _mockRandomSystem.RandomProvider.GetGuid(); +#endif } diff --git a/Source/Testably.Abstractions.Testing/RandomSystem/RandomProviderMock.cs b/Source/Testably.Abstractions.Testing/RandomSystem/RandomProviderMock.cs index 8d9fd2f03..59caeafca 100644 --- a/Source/Testably.Abstractions.Testing/RandomSystem/RandomProviderMock.cs +++ b/Source/Testably.Abstractions.Testing/RandomSystem/RandomProviderMock.cs @@ -24,7 +24,7 @@ public RandomProviderMock( #region IRandomProvider Members - /// + /// public Guid GetGuid() => _guidGenerator.GetNext(); diff --git a/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs b/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs index 555863a64..4ff863475 100644 --- a/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs +++ b/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs @@ -199,6 +199,45 @@ internal IDisposable RegisterMethod(string name, ReadOnlySpan parameter1 } #endif +#if FEATURE_FILE_SPAN + /// + /// Registers the method with and . + /// + /// A disposable which ignores all registrations, until it is disposed. + internal IDisposable RegisterMethod(string name, T1 parameter1, ReadOnlySpan parameter2) + { + if (_statisticsGate.TryGetLock(out IDisposable release)) + { + int counter = _statisticsGate.GetCounter(); + _methods.Enqueue(new MethodStatistic(counter, name, + ParameterDescription.FromParameter(parameter1), + ParameterDescription.FromParameter(parameter2))); + } + + return release; + } +#endif + +#if FEATURE_FILE_SPAN + /// + /// Registers the method with , and . + /// + /// A disposable which ignores all registrations, until it is disposed. + internal IDisposable RegisterMethod(string name, T1 parameter1, ReadOnlySpan parameter2, T3 parameter3) + { + if (_statisticsGate.TryGetLock(out IDisposable release)) + { + int counter = _statisticsGate.GetCounter(); + _methods.Enqueue(new MethodStatistic(counter, name, + ParameterDescription.FromParameter(parameter1), + ParameterDescription.FromParameter(parameter2), + ParameterDescription.FromParameter(parameter3))); + } + + return release; + } +#endif + #if FEATURE_SPAN /// /// Registers the method with and diff --git a/Source/Testably.Abstractions.Testing/TimeSystem/TimeProviderMock.cs b/Source/Testably.Abstractions.Testing/TimeSystem/TimeProviderMock.cs index c33347633..231037bed 100644 --- a/Source/Testably.Abstractions.Testing/TimeSystem/TimeProviderMock.cs +++ b/Source/Testably.Abstractions.Testing/TimeSystem/TimeProviderMock.cs @@ -9,7 +9,11 @@ internal sealed class TimeProviderMock : ITimeProvider { private DateTime _now; private readonly string _description; +#if NET9_0_OR_GREATER + private readonly System.Threading.Lock _lock = new(); +#else private readonly object _lock = new(); +#endif public TimeProviderMock(DateTime now, string description) { diff --git a/Source/Testably.Abstractions.Testing/TimeSystem/TimerMock.cs b/Source/Testably.Abstractions.Testing/TimeSystem/TimerMock.cs index 7b1fd49f2..c2ef90293 100644 --- a/Source/Testably.Abstractions.Testing/TimeSystem/TimerMock.cs +++ b/Source/Testably.Abstractions.Testing/TimeSystem/TimerMock.cs @@ -16,7 +16,11 @@ internal sealed class TimerMock : ITimerMock private TimeSpan _dueTime; private Exception? _exception; private bool _isDisposed; +#if NET9_0_OR_GREATER + private readonly Lock _lock = new(); +#else private readonly object _lock = new(); +#endif private readonly MockTimeSystem _mockTimeSystem; private Action? _onDispose; private TimeSpan _period; diff --git a/Source/Testably.Abstractions/FileSystem/FileWrapper.cs b/Source/Testably.Abstractions/FileSystem/FileWrapper.cs index a99ba6692..314d36b42 100644 --- a/Source/Testably.Abstractions/FileSystem/FileWrapper.cs +++ b/Source/Testably.Abstractions/FileSystem/FileWrapper.cs @@ -24,6 +24,30 @@ internal FileWrapper(RealFileSystem fileSystem) /// public IFileSystem FileSystem { get; } + +#if FEATURE_FILE_SPAN + /// + public void AppendAllBytes(string path, byte[] bytes) + => File.AppendAllBytes(path, bytes); +#endif + +#if FEATURE_FILE_SPAN + /// + public void AppendAllBytes(string path, ReadOnlySpan bytes) + => File.AppendAllBytes(path, bytes); +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken = default) + => File.AppendAllBytesAsync(path, bytes, cancellationToken); +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllBytesAsync(string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) + => File.AppendAllBytesAsync(path, bytes, cancellationToken); +#endif /// public void AppendAllLines(string path, IEnumerable contents) @@ -59,6 +83,18 @@ public void AppendAllText(string path, string? contents) /// public void AppendAllText(string path, string? contents, Encoding encoding) => File.AppendAllText(path, contents, encoding); + +#if FEATURE_FILE_SPAN + /// + public void AppendAllText(string path, ReadOnlySpan contents) + => File.AppendAllText(path, contents); +#endif + +#if FEATURE_FILE_SPAN + /// + public void AppendAllText(string path, ReadOnlySpan contents, Encoding encoding) + => File.AppendAllText(path, contents, encoding); +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -76,6 +112,19 @@ public Task AppendAllTextAsync(string path, string? contents, Encoding encoding, => File.AppendAllTextAsync(path, contents, encoding, cancellationToken); #endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllTextAsync(string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) + => File.AppendAllTextAsync(path, contents, cancellationToken); +#endif + +#if FEATURE_FILE_SPAN + /// + public Task AppendAllTextAsync(string path, ReadOnlyMemory contents, Encoding encoding, + CancellationToken cancellationToken = default) + => File.AppendAllTextAsync(path, contents, encoding, cancellationToken); +#endif /// public StreamWriter AppendText(string path) @@ -497,6 +546,12 @@ public void SetUnixFileMode(SafeFileHandle fileHandle, UnixFileMode mode) /// public void WriteAllBytes(string path, byte[] bytes) => File.WriteAllBytes(path, bytes); + +#if FEATURE_FILE_SPAN + /// + public void WriteAllBytes(string path, ReadOnlySpan bytes) + => File.WriteAllBytes(path, bytes); +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -505,6 +560,12 @@ public Task WriteAllBytesAsync(string path, byte[] bytes, default) => File.WriteAllBytesAsync(path, bytes, cancellationToken); #endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllBytesAsync(string path, ReadOnlyMemory bytes, CancellationToken cancellationToken = default) + => File.WriteAllBytesAsync(path, bytes, cancellationToken); +#endif /// public void WriteAllLines(string path, string[] contents) @@ -548,6 +609,19 @@ public void WriteAllText(string path, string? contents) /// public void WriteAllText(string path, string? contents, Encoding encoding) => File.WriteAllText(path, contents, encoding); + +#if FEATURE_FILE_SPAN + /// + public void WriteAllText(string path, ReadOnlySpan contents) + => File.WriteAllText(path, contents); +#endif + +#if FEATURE_FILE_SPAN + + /// + public void WriteAllText(string path, ReadOnlySpan contents, Encoding encoding) + => File.WriteAllText(path, contents, encoding); +#endif #if FEATURE_FILESYSTEM_ASYNC /// @@ -565,6 +639,19 @@ public Task WriteAllTextAsync(string path, string? contents, Encoding encoding, => File.WriteAllTextAsync(path, contents, encoding, cancellationToken); #endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllTextAsync(string path, ReadOnlyMemory contents, CancellationToken cancellationToken = default) + => File.WriteAllTextAsync(path, contents, cancellationToken); +#endif + +#if FEATURE_FILE_SPAN + /// + public Task WriteAllTextAsync(string path, ReadOnlyMemory contents, Encoding encoding, + CancellationToken cancellationToken = default) + => File.WriteAllTextAsync(path, contents, encoding, cancellationToken); +#endif #endregion } diff --git a/Source/Testably.Abstractions/FileSystem/PathWrapper.cs b/Source/Testably.Abstractions/FileSystem/PathWrapper.cs index e59104b02..5a6151093 100644 --- a/Source/Testably.Abstractions/FileSystem/PathWrapper.cs +++ b/Source/Testably.Abstractions/FileSystem/PathWrapper.cs @@ -56,6 +56,12 @@ public string Combine(string path1, string path2, string path3, string path4) /// public string Combine(params string[] paths) => Path.Combine(paths); + +#if FEATURE_PATH_SPAN + /// + public string Combine(params ReadOnlySpan paths) + => Path.Combine(paths); +#endif #if FEATURE_PATH_ADVANCED /// @@ -247,6 +253,12 @@ public string Join(string? path1, string? path2, string? path3, string? path4) public string Join(params string?[] paths) => Path.Join(paths); #endif + +#if FEATURE_PATH_SPAN + /// + public string Join(params ReadOnlySpan paths) + => Path.Join(paths); +#endif #if FEATURE_PATH_ADVANCED /// diff --git a/Source/Testably.Abstractions/RandomSystem/GuidWrapper.cs b/Source/Testably.Abstractions/RandomSystem/GuidWrapper.cs index ca96b4455..6010c7b6e 100644 --- a/Source/Testably.Abstractions/RandomSystem/GuidWrapper.cs +++ b/Source/Testably.Abstractions/RandomSystem/GuidWrapper.cs @@ -12,4 +12,16 @@ internal GuidWrapper(RealRandomSystem randomSystem) : base(randomSystem) /// public override Guid NewGuid() => Guid.NewGuid(); + +#if FEATURE_GUID_V7 + /// + public override Guid CreateVersion7() + => Guid.CreateVersion7(); +#endif + +#if FEATURE_GUID_V7 + /// + public override Guid CreateVersion7(DateTimeOffset timestamp) + => Guid.CreateVersion7(timestamp); +#endif } diff --git a/Testably.Abstractions.sln.DotSettings b/Testably.Abstractions.sln.DotSettings index ba93afa1f..60cbae69e 100644 --- a/Testably.Abstractions.sln.DotSettings +++ b/Testably.Abstractions.sln.DotSettings @@ -16,6 +16,7 @@ Required Required Required + True False False False diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.AccessControl_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.AccessControl_net9.0.txt new file mode 100644 index 000000000..bbff50437 --- /dev/null +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.AccessControl_net9.0.txt @@ -0,0 +1,54 @@ +[assembly: System.CLSCompliant(true)] +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Testably/Testably.Abstractions.git")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Testably.Abstractions.AccessControl.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] +namespace Testably.Abstractions +{ + public static class DirectoryAclExtensions + { + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void CreateDirectory(this System.IO.Abstractions.IDirectory directory, string path, System.Security.AccessControl.DirectorySecurity directorySecurity) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.Abstractions.IDirectory directory, string path) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.Abstractions.IDirectory directory, string path, System.Security.AccessControl.AccessControlSections includeSections) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void SetAccessControl(this System.IO.Abstractions.IDirectory directory, string path, System.Security.AccessControl.DirectorySecurity directorySecurity) { } + } + public static class DirectoryInfoAclExtensions + { + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void Create(this System.IO.Abstractions.IDirectoryInfo directoryInfo, System.Security.AccessControl.DirectorySecurity directorySecurity) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.Abstractions.IDirectoryInfo directoryInfo) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.Abstractions.IDirectoryInfo directoryInfo, System.Security.AccessControl.AccessControlSections includeSections) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void SetAccessControl(this System.IO.Abstractions.IDirectoryInfo directoryInfo, System.Security.AccessControl.DirectorySecurity directorySecurity) { } + } + public static class FileAclExtensions + { + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.FileSecurity GetAccessControl(this System.IO.Abstractions.IFile file, string path) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.FileSecurity GetAccessControl(this System.IO.Abstractions.IFile file, string path, System.Security.AccessControl.AccessControlSections includeSections) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void SetAccessControl(this System.IO.Abstractions.IFile file, string path, System.Security.AccessControl.FileSecurity fileSecurity) { } + } + public static class FileInfoAclExtensions + { + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.FileSecurity GetAccessControl(this System.IO.Abstractions.IFileInfo fileInfo) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.FileSecurity GetAccessControl(this System.IO.Abstractions.IFileInfo fileInfo, System.Security.AccessControl.AccessControlSections includeSections) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void SetAccessControl(this System.IO.Abstractions.IFileInfo fileInfo, System.Security.AccessControl.FileSecurity fileSecurity) { } + } + public static class FileStreamAclExtensions + { + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static System.Security.AccessControl.FileSecurity GetAccessControl(this System.IO.Abstractions.FileSystemStream fileStream) { } + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public static void SetAccessControl(this System.IO.Abstractions.FileSystemStream fileStream, System.Security.AccessControl.FileSecurity fileSecurity) { } + } +} \ No newline at end of file diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Compression_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Compression_net9.0.txt new file mode 100644 index 000000000..8cb294b9c --- /dev/null +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Compression_net9.0.txt @@ -0,0 +1,68 @@ +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Testably/Testably.Abstractions.git")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Testably.Abstractions.Compression.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] +namespace Testably.Abstractions +{ + public static class FileSystemExtensions + { + public static Testably.Abstractions.IZipArchiveFactory ZipArchive(this System.IO.Abstractions.IFileSystem fileSystem) { } + public static Testably.Abstractions.IZipFile ZipFile(this System.IO.Abstractions.IFileSystem fileSystem) { } + } + public interface IZipArchive : System.IDisposable, System.IO.Abstractions.IFileSystemEntity + { + string Comment { get; set; } + System.Collections.ObjectModel.ReadOnlyCollection Entries { get; } + System.IO.Compression.ZipArchiveMode Mode { get; } + Testably.Abstractions.IZipArchiveEntry CreateEntry(string entryName); + Testably.Abstractions.IZipArchiveEntry CreateEntry(string entryName, System.IO.Compression.CompressionLevel compressionLevel); + Testably.Abstractions.IZipArchiveEntry CreateEntryFromFile(string sourceFileName, string entryName); + Testably.Abstractions.IZipArchiveEntry CreateEntryFromFile(string sourceFileName, string entryName, System.IO.Compression.CompressionLevel compressionLevel); + void ExtractToDirectory(string destinationDirectoryName); + void ExtractToDirectory(string destinationDirectoryName, bool overwriteFiles); + Testably.Abstractions.IZipArchiveEntry? GetEntry(string entryName); + } + public interface IZipArchiveEntry : System.IO.Abstractions.IFileSystemEntity + { + Testably.Abstractions.IZipArchive Archive { get; } + string Comment { get; set; } + long CompressedLength { get; } + uint Crc32 { get; } + int ExternalAttributes { get; set; } + string FullName { get; } + bool IsEncrypted { get; } + System.DateTimeOffset LastWriteTime { get; set; } + long Length { get; } + string Name { get; } + void Delete(); + void ExtractToFile(string destinationFileName); + void ExtractToFile(string destinationFileName, bool overwrite); + System.IO.Stream Open(); + } + public interface IZipArchiveFactory : System.IO.Abstractions.IFileSystemEntity + { + Testably.Abstractions.IZipArchive New(System.IO.Stream stream); + Testably.Abstractions.IZipArchive New(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode); + Testably.Abstractions.IZipArchive New(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode, bool leaveOpen); + Testably.Abstractions.IZipArchive New(System.IO.Stream stream, System.IO.Compression.ZipArchiveMode mode, bool leaveOpen, System.Text.Encoding? entryNameEncoding); + } + public interface IZipFile : System.IO.Abstractions.IFileSystemEntity + { + void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination); + void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName); + void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory); + void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory); + void CreateFromDirectory(string sourceDirectoryName, System.IO.Stream destination, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding entryNameEncoding); + void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, System.IO.Compression.CompressionLevel compressionLevel, bool includeBaseDirectory, System.Text.Encoding entryNameEncoding); + void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName); + void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName); + void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, bool overwriteFiles); + void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding entryNameEncoding); + void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles); + void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding); + void ExtractToDirectory(System.IO.Stream source, string destinationDirectoryName, System.Text.Encoding entryNameEncoding, bool overwriteFiles); + void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, System.Text.Encoding? entryNameEncoding, bool overwriteFiles); + Testably.Abstractions.IZipArchive Open(string archiveFileName, System.IO.Compression.ZipArchiveMode mode); + Testably.Abstractions.IZipArchive Open(string archiveFileName, System.IO.Compression.ZipArchiveMode mode, System.Text.Encoding? entryNameEncoding); + Testably.Abstractions.IZipArchive OpenRead(string archiveFileName); + } +} \ No newline at end of file diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt new file mode 100644 index 000000000..a261d7ec3 --- /dev/null +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Interface_net9.0.txt @@ -0,0 +1,160 @@ +[assembly: System.CLSCompliant(true)] +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Testably/Testably.Abstractions.git")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] +namespace Testably.Abstractions.Helpers +{ + public abstract class GuidSystemBase : Testably.Abstractions.RandomSystem.IGuid, Testably.Abstractions.RandomSystem.IRandomSystemEntity + { + protected GuidSystemBase(Testably.Abstractions.IRandomSystem randomSystem) { } + public System.Guid Empty { get; } + public Testably.Abstractions.IRandomSystem RandomSystem { get; } + public abstract System.Guid CreateVersion7(); + public abstract System.Guid CreateVersion7(System.DateTimeOffset timestamp); + public abstract System.Guid NewGuid(); + public System.Guid Parse(System.ReadOnlySpan input) { } + public System.Guid Parse(string input) { } + public System.Guid Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { } + public System.Guid Parse(string s, System.IFormatProvider? provider) { } + public System.Guid ParseExact(System.ReadOnlySpan input, System.ReadOnlySpan format) { } + public System.Guid ParseExact(string input, string format) { } + public bool TryParse(System.ReadOnlySpan input, out System.Guid result) { } + public bool TryParse(string? input, out System.Guid result) { } + public bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Guid result) { } + public bool TryParse(string? s, System.IFormatProvider? provider, out System.Guid result) { } + public bool TryParseExact(System.ReadOnlySpan input, System.ReadOnlySpan format, out System.Guid result) { } + public bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? format, out System.Guid result) { } + } + public interface IFileSystemExtensibility + { + T? RetrieveMetadata(string key); + void StoreMetadata(string key, T? value); + bool TryGetWrappedInstance([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? wrappedInstance); + } + public sealed class RandomWrapper : Testably.Abstractions.RandomSystem.IRandom + { + public RandomWrapper(System.Random instance) { } + public T[] GetItems(System.ReadOnlySpan choices, int length) { } + public void GetItems(System.ReadOnlySpan choices, System.Span destination) { } + public T[] GetItems(T[] choices, int length) { } + public int Next() { } + public int Next(int maxValue) { } + public int Next(int minValue, int maxValue) { } + public void NextBytes(byte[] buffer) { } + public void NextBytes(System.Span buffer) { } + public double NextDouble() { } + public long NextInt64() { } + public long NextInt64(long maxValue) { } + public long NextInt64(long minValue, long maxValue) { } + public float NextSingle() { } + public void Shuffle(System.Span values) { } + public void Shuffle(T[] values) { } + } +} +namespace Testably.Abstractions +{ + public interface IRandomSystem + { + Testably.Abstractions.RandomSystem.IGuid Guid { get; } + Testably.Abstractions.RandomSystem.IRandomFactory Random { get; } + } + public interface ITimeSystem + { + Testably.Abstractions.TimeSystem.IDateTime DateTime { get; } + Testably.Abstractions.TimeSystem.ITask Task { get; } + Testably.Abstractions.TimeSystem.IThread Thread { get; } + Testably.Abstractions.TimeSystem.ITimerFactory Timer { get; } + } +} +namespace Testably.Abstractions.RandomSystem +{ + public interface IGuid : Testably.Abstractions.RandomSystem.IRandomSystemEntity + { + System.Guid Empty { get; } + System.Guid CreateVersion7(); + System.Guid CreateVersion7(System.DateTimeOffset timestamp); + System.Guid NewGuid(); + System.Guid Parse(System.ReadOnlySpan input); + System.Guid Parse(string input); + System.Guid Parse(System.ReadOnlySpan s, System.IFormatProvider? provider); + System.Guid Parse(string s, System.IFormatProvider? provider); + System.Guid ParseExact(System.ReadOnlySpan input, System.ReadOnlySpan format); + System.Guid ParseExact(string input, string format); + bool TryParse(System.ReadOnlySpan input, out System.Guid result); + bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, out System.Guid result); + bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Guid result); + bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.IFormatProvider? provider, out System.Guid result); + bool TryParseExact(System.ReadOnlySpan input, System.ReadOnlySpan format, out System.Guid result); + bool TryParseExact([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? format, out System.Guid result); + } + public interface IRandom + { + T[] GetItems(System.ReadOnlySpan choices, int length); + void GetItems(System.ReadOnlySpan choices, System.Span destination); + T[] GetItems(T[] choices, int length); + int Next(); + int Next(int maxValue); + int Next(int minValue, int maxValue); + void NextBytes(byte[] buffer); + void NextBytes(System.Span buffer); + double NextDouble(); + long NextInt64(); + long NextInt64(long maxValue); + long NextInt64(long minValue, long maxValue); + float NextSingle(); + void Shuffle(System.Span values); + void Shuffle(T[] values); + } + public interface IRandomFactory : Testably.Abstractions.RandomSystem.IRandomSystemEntity + { + Testably.Abstractions.RandomSystem.IRandom Shared { get; } + Testably.Abstractions.RandomSystem.IRandom New(); + Testably.Abstractions.RandomSystem.IRandom New(int seed); + } + public interface IRandomSystemEntity + { + Testably.Abstractions.IRandomSystem RandomSystem { get; } + } +} +namespace Testably.Abstractions.TimeSystem +{ + public interface IDateTime : Testably.Abstractions.TimeSystem.ITimeSystemEntity + { + System.DateTime MaxValue { get; } + System.DateTime MinValue { get; } + System.DateTime Now { get; } + System.DateTime Today { get; } + System.DateTime UnixEpoch { get; } + System.DateTime UtcNow { get; } + } + public interface ITask : Testably.Abstractions.TimeSystem.ITimeSystemEntity + { + System.Threading.Tasks.Task Delay(int millisecondsDelay); + System.Threading.Tasks.Task Delay(System.TimeSpan delay); + System.Threading.Tasks.Task Delay(int millisecondsDelay, System.Threading.CancellationToken cancellationToken); + System.Threading.Tasks.Task Delay(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); + } + public interface IThread : Testably.Abstractions.TimeSystem.ITimeSystemEntity + { + void Sleep(int millisecondsTimeout); + void Sleep(System.TimeSpan timeout); + } + public interface ITimeSystemEntity + { + Testably.Abstractions.ITimeSystem TimeSystem { get; } + } + public interface ITimer : System.IAsyncDisposable, System.IDisposable, Testably.Abstractions.TimeSystem.ITimeSystemEntity + { + bool Change(int dueTime, int period); + bool Change(long dueTime, long period); + bool Change(System.TimeSpan dueTime, System.TimeSpan period); + bool Dispose(System.Threading.WaitHandle notifyObject); + } + public interface ITimerFactory : Testably.Abstractions.TimeSystem.ITimeSystemEntity + { + Testably.Abstractions.TimeSystem.ITimer New(System.Threading.TimerCallback callback); + Testably.Abstractions.TimeSystem.ITimer New(System.Threading.TimerCallback callback, object? state, int dueTime, int period); + Testably.Abstractions.TimeSystem.ITimer New(System.Threading.TimerCallback callback, object? state, long dueTime, long period); + Testably.Abstractions.TimeSystem.ITimer New(System.Threading.TimerCallback callback, object? state, System.TimeSpan dueTime, System.TimeSpan period); + Testably.Abstractions.TimeSystem.ITimer Wrap(System.Threading.Timer timer); + } +} \ No newline at end of file diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt new file mode 100644 index 000000000..1822d8f1b --- /dev/null +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt @@ -0,0 +1,420 @@ +[assembly: System.CLSCompliant(true)] +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Testably/Testably.Abstractions.git")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Testably.Abstractions.Testing.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] +namespace Testably.Abstractions.Testing.FileSystem +{ + public class ChangeDescription + { + public System.IO.WatcherChangeTypes ChangeType { get; } + public Testably.Abstractions.Testing.FileSystemTypes FileSystemType { get; } + public string? Name { get; } + public System.IO.NotifyFilters NotifyFilters { get; } + public string? OldName { get; } + public string? OldPath { get; } + public string Path { get; } + public override string ToString() { } + } + public class DefaultAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy + { + public DefaultAccessControlStrategy(System.Func callback) { } + public bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility) { } + } + public class DefaultSafeFileHandleStrategy : Testably.Abstractions.Testing.FileSystem.ISafeFileHandleStrategy + { + public DefaultSafeFileHandleStrategy(System.Func callback) { } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage(Justification="SafeFileHandle cannot be unit tested.")] + public Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { } + } + public interface IAccessControlStrategy + { + bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility); + } + public interface IInterceptionHandler : System.IO.Abstractions.IFileSystemEntity + { + Testably.Abstractions.Testing.IAwaitableCallback Event(System.Action interceptionCallback, System.Func? predicate = null); + } + public interface INotificationHandler : System.IO.Abstractions.IFileSystemEntity + { + Testably.Abstractions.Testing.IAwaitableCallback OnEvent(System.Action? notificationCallback = null, System.Func? predicate = null); + } + public interface ISafeFileHandleStrategy + { + Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle); + } + public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy + { + public NullAccessControlStrategy() { } + public bool IsAccessGranted(string fullPath, Testably.Abstractions.Helpers.IFileSystemExtensibility extensibility) { } + } + public class NullSafeFileHandleStrategy : Testably.Abstractions.Testing.FileSystem.ISafeFileHandleStrategy + { + public NullSafeFileHandleStrategy() { } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage(Justification="SafeFileHandle cannot be unit tested.")] + public Testably.Abstractions.Testing.FileSystem.SafeFileHandleMock MapSafeFileHandle(Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle) { } + } + public class SafeFileHandleMock + { + public SafeFileHandleMock(string path, System.IO.FileMode mode = 3, System.IO.FileShare share = 0) { } + public System.IO.FileMode Mode { get; } + public string Path { get; } + public System.IO.FileShare Share { get; } + } +} +namespace Testably.Abstractions.Testing +{ + public static class FileSystemInitializerExtensions + { + public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer Initialize(this TFileSystem fileSystem, System.Action? options = null) + where TFileSystem : System.IO.Abstractions.IFileSystem { } + public static void InitializeEmbeddedResourcesFromAssembly(this System.IO.Abstractions.IFileSystem fileSystem, string directoryPath, System.Reflection.Assembly assembly, string? relativePath = null, string searchPattern = "*", System.IO.SearchOption searchOption = 1) { } + public static Testably.Abstractions.Testing.Initializer.IFileSystemInitializer InitializeIn(this TFileSystem fileSystem, string basePath, System.Action? options = null) + where TFileSystem : System.IO.Abstractions.IFileSystem { } + public static Testably.Abstractions.Testing.Initializer.IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(this System.IO.Abstractions.IFileSystem fileSystem, string? prefix = null, System.Action? logger = null) { } + } + public class FileSystemInitializerOptions + { + public FileSystemInitializerOptions() { } + public bool InitializeTempDirectory { get; set; } + } + [System.Flags] + public enum FileSystemTypes + { + Directory = 1, + File = 2, + DirectoryOrFile = 3, + } + public interface IAwaitableCallback : System.IDisposable + { + void Wait(System.Func? filter = null, int timeout = 30000, int count = 1, System.Action? executeWhenWaiting = null); + } + public static class InterceptionHandlerExtensions + { + public static Testably.Abstractions.Testing.IAwaitableCallback Changing(this Testably.Abstractions.Testing.FileSystem.IInterceptionHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action interceptionCallback, string globPattern = "*", System.Func? predicate = null) { } + public static Testably.Abstractions.Testing.IAwaitableCallback Creating(this Testably.Abstractions.Testing.FileSystem.IInterceptionHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action interceptionCallback, string globPattern = "*", System.Func? predicate = null) { } + public static Testably.Abstractions.Testing.IAwaitableCallback Deleting(this Testably.Abstractions.Testing.FileSystem.IInterceptionHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action interceptionCallback, string globPattern = "*", System.Func? predicate = null) { } + } + public sealed class MockFileSystem : System.IO.Abstractions.IFileSystem + { + public MockFileSystem() { } + public MockFileSystem(System.Func options) { } + public System.IO.Abstractions.IDirectory Directory { get; } + public System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } + public System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } + public System.IO.Abstractions.IFile File { get; } + public System.IO.Abstractions.IFileInfoFactory FileInfo { get; } + public System.IO.Abstractions.IFileStreamFactory FileStream { get; } + public System.IO.Abstractions.IFileSystemWatcherFactory FileSystemWatcher { get; } + public System.IO.Abstractions.IFileVersionInfoFactory FileVersionInfo { get; } + public Testably.Abstractions.Testing.FileSystem.IInterceptionHandler Intercept { get; } + public Testably.Abstractions.Testing.FileSystem.INotificationHandler Notify { get; } + public System.IO.Abstractions.IPath Path { get; } + public Testably.Abstractions.IRandomSystem RandomSystem { get; } + public Testably.Abstractions.Testing.SimulationMode SimulationMode { get; } + public Testably.Abstractions.Testing.Statistics.IFileSystemStatistics Statistics { get; } + public Testably.Abstractions.ITimeSystem TimeSystem { 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? driveCallback = null) { } + public Testably.Abstractions.Testing.MockFileSystem WithFileVersionInfo(string globPattern, System.Action fileVersionInfoBuilder) { } + public Testably.Abstractions.Testing.MockFileSystem WithSafeFileHandleStrategy(Testably.Abstractions.Testing.FileSystem.ISafeFileHandleStrategy safeFileHandleStrategy) { } + public class MockFileSystemOptions + { + public MockFileSystemOptions() { } + public Testably.Abstractions.Testing.MockFileSystem.MockFileSystemOptions SimulatingOperatingSystem(Testably.Abstractions.Testing.SimulationMode simulationMode) { } + public Testably.Abstractions.Testing.MockFileSystem.MockFileSystemOptions UseCurrentDirectory() { } + public Testably.Abstractions.Testing.MockFileSystem.MockFileSystemOptions UseCurrentDirectory(string path) { } + public Testably.Abstractions.Testing.MockFileSystem.MockFileSystemOptions UseRandomProvider(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProvider) { } + } + } + public static class MockFileSystemExtensions + { + public static System.IO.Abstractions.IDriveInfo GetDefaultDrive(this Testably.Abstractions.Testing.MockFileSystem mockFileSystem) { } + public static Testably.Abstractions.Testing.MockFileSystem WithDrive(this Testably.Abstractions.Testing.MockFileSystem mockFileSystem, System.Action driveCallback) { } + public static Testably.Abstractions.Testing.MockFileSystem WithUncDrive(this Testably.Abstractions.Testing.MockFileSystem mockFileSystem, string server, System.Action? driveCallback = null) { } + } + public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem + { + public MockRandomSystem() { } + public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProvider) { } + public Testably.Abstractions.RandomSystem.IGuid Guid { get; } + public Testably.Abstractions.RandomSystem.IRandomFactory Random { get; } + public Testably.Abstractions.Testing.RandomSystem.IRandomProvider RandomProvider { get; } + public override string ToString() { } + } + public sealed class MockTimeSystem : Testably.Abstractions.ITimeSystem + { + public MockTimeSystem() { } + public MockTimeSystem(System.DateTime time) { } + public MockTimeSystem(Testably.Abstractions.Testing.TimeSystem.ITimeProvider timeProvider) { } + public Testably.Abstractions.TimeSystem.IDateTime DateTime { get; } + public Testably.Abstractions.Testing.TimeSystem.INotificationHandler On { get; } + public Testably.Abstractions.TimeSystem.ITask Task { get; } + public Testably.Abstractions.TimeSystem.IThread Thread { get; } + public Testably.Abstractions.Testing.TimeSystem.ITimeProvider TimeProvider { get; } + public Testably.Abstractions.TimeSystem.ITimerFactory Timer { get; } + public Testably.Abstractions.Testing.TimeSystem.ITimerHandler TimerHandler { get; } + public override string ToString() { } + public Testably.Abstractions.Testing.MockTimeSystem WithTimerStrategy(Testably.Abstractions.Testing.TimeSystem.ITimerStrategy timerStrategy) { } + } + public static class Notification + { + public static Testably.Abstractions.Testing.IAwaitableCallback ExecuteWhileWaiting(this Testably.Abstractions.Testing.IAwaitableCallback awaitable, System.Action callback) { } + public static Testably.Abstractions.Testing.Notification.IAwaitableCallback ExecuteWhileWaiting(this Testably.Abstractions.Testing.IAwaitableCallback awaitable, System.Func callback) { } + public interface IAwaitableCallback : System.IDisposable, Testably.Abstractions.Testing.IAwaitableCallback + { + TFunc Wait(System.Func? filter = null, int timeout = 30000, int count = 1, System.Action? executeWhenWaiting = null); + } + } + public static class NotificationHandlerExtensions + { + public static Testably.Abstractions.Testing.IAwaitableCallback OnChanged(this Testably.Abstractions.Testing.FileSystem.INotificationHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action? notificationCallback = null, string globPattern = "*", System.Func? predicate = null) { } + public static Testably.Abstractions.Testing.IAwaitableCallback OnCreated(this Testably.Abstractions.Testing.FileSystem.INotificationHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action? notificationCallback = null, string globPattern = "*", System.Func? predicate = null) { } + public static Testably.Abstractions.Testing.IAwaitableCallback OnDeleted(this Testably.Abstractions.Testing.FileSystem.INotificationHandler handler, Testably.Abstractions.Testing.FileSystemTypes fileSystemType, System.Action? notificationCallback = null, string globPattern = "*", System.Func? predicate = null) { } + } + public static class RandomProvider + { + public static Testably.Abstractions.Testing.RandomSystem.IRandomProvider Default() { } + public static Testably.Abstractions.Testing.RandomSystem.IRandomProvider Generate(int seed = -1, Testably.Abstractions.Testing.RandomProvider.Generator? guidGenerator = null, Testably.Abstractions.Testing.RandomProvider.Generator? intGenerator = null, Testably.Abstractions.Testing.RandomProvider.Generator? longGenerator = null, Testably.Abstractions.Testing.RandomProvider.Generator? singleGenerator = null, Testably.Abstractions.Testing.RandomProvider.Generator? doubleGenerator = null, Testably.Abstractions.Testing.RandomProvider.Generator? byteGenerator = null) { } + public abstract class Generator + { + protected Generator() { } + public static Testably.Abstractions.Testing.RandomProvider.Generator FromArray(T[] values) { } + public static Testably.Abstractions.Testing.RandomProvider.Generator FromCallback(System.Func callback) { } + public static Testably.Abstractions.Testing.RandomProvider.Generator FromEnumerable(System.Collections.Generic.IEnumerable enumerable) { } + public static Testably.Abstractions.Testing.RandomProvider.Generator FromValue(T value) { } + } + public sealed class Generator : Testably.Abstractions.Testing.RandomProvider.Generator, System.IDisposable + { + public void Dispose() { } + public T GetNext() { } + public static Testably.Abstractions.Testing.RandomProvider.Generator op_Implicit(System.Func callback) { } + public static Testably.Abstractions.Testing.RandomProvider.Generator op_Implicit(T value) { } + public static Testably.Abstractions.Testing.RandomProvider.Generator op_Implicit(T[] values) { } + } + } + public enum SimulationMode + { + Native = 0, + Linux = 1, + MacOS = 2, + Windows = 3, + } + public static class TimeProvider + { + public static Testably.Abstractions.Testing.TimeSystem.ITimeProvider Now() { } + public static Testably.Abstractions.Testing.TimeSystem.ITimeProvider Random() { } + public static Testably.Abstractions.Testing.TimeSystem.ITimeProvider Use(System.DateTime time) { } + } +} +namespace Testably.Abstractions.Testing.Initializer +{ + public class DirectoryDescription : Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription + { + public DirectoryDescription(string name) { } + public DirectoryDescription(string name, params Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription[] children) { } + public Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription[] Children { get; } + public override Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription this[string path] { get; } + } + public class FileDescription : Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription + { + public FileDescription(string name, byte[] bytes) { } + public FileDescription(string name, string? content = null) { } + public byte[]? Bytes { get; } + public string? Content { get; } + public bool IsReadOnly { get; set; } + public override Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription this[string? path] { get; } + } + public abstract class FileSystemInfoDescription + { + protected FileSystemInfoDescription(string name) { } + public abstract Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription this[string path] { get; } + public string Name { get; } + } + public sealed class FileVersionInfoBuilder + { + public FileVersionInfoBuilder() { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetComments(string? comments) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetCompanyName(string? companyName) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetFileDescription(string? fileDescription) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetFileVersion(string? fileVersion) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetInternalName(string? internalName) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetIsDebug(bool isDebug) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetIsPatched(bool isPatched) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetIsPreRelease(bool isPreRelease) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetIsPrivateBuild(bool isPrivateBuild) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetIsSpecialBuild(bool isSpecialBuild) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetLanguage(string? language) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetLegalCopyright(string? legalCopyright) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetLegalTrademarks(string? legalTrademarks) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetOriginalFilename(string? originalFilename) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetPrivateBuild(string? privateBuild) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetProductName(string? productName) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetProductVersion(string? productVersion) { } + public Testably.Abstractions.Testing.Initializer.FileVersionInfoBuilder SetSpecialBuild(string? specialBuild) { } + } + public interface IDirectoryCleaner : System.IDisposable + { + string BasePath { get; } + } + public interface IFileManipulator : System.IO.Abstractions.IFileSystemEntity + { + System.IO.Abstractions.IFileInfo File { get; } + Testably.Abstractions.Testing.Initializer.IFileManipulator HasBytesContent(byte[] bytes); + Testably.Abstractions.Testing.Initializer.IFileManipulator HasStringContent(string contents); + } + public interface IFileSystemDirectoryInitializer : Testably.Abstractions.Testing.Initializer.IFileSystemInitializer + where out TFileSystem : System.IO.Abstractions.IFileSystem + { + System.IO.Abstractions.IDirectoryInfo Directory { get; } + Testably.Abstractions.Testing.Initializer.IFileSystemDirectoryInitializer Initialized(System.Action> subdirectoryInitializer); + } + public interface IFileSystemFileInitializer : Testably.Abstractions.Testing.Initializer.IFileSystemInitializer + where out TFileSystem : System.IO.Abstractions.IFileSystem + { + System.IO.Abstractions.IFileInfo File { get; } + Testably.Abstractions.Testing.Initializer.IFileSystemFileInitializer Which(System.Action fileManipulation); + } + public interface IFileSystemInitializer + where out TFileSystem : System.IO.Abstractions.IFileSystem + { + System.IO.Abstractions.IDirectoryInfo BaseDirectory { get; } + TFileSystem FileSystem { get; } + System.IO.Abstractions.IFileSystemInfo this[int index] { get; } + Testably.Abstractions.Testing.Initializer.IFileSystemInitializer With(params Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription[] descriptions); + Testably.Abstractions.Testing.Initializer.IFileSystemInitializer With(TDescription[] descriptions) + where TDescription : Testably.Abstractions.Testing.Initializer.FileSystemInfoDescription; + Testably.Abstractions.Testing.Initializer.IFileSystemFileInitializer WithAFile(string? extension = null); + Testably.Abstractions.Testing.Initializer.IFileSystemDirectoryInitializer WithASubdirectory(); + Testably.Abstractions.Testing.Initializer.IFileSystemFileInitializer WithFile(string fileName); + Testably.Abstractions.Testing.Initializer.IFileSystemInitializer WithSubdirectories(params string[] paths); + Testably.Abstractions.Testing.Initializer.IFileSystemDirectoryInitializer WithSubdirectory(string directoryName); + } + public class TestingException : System.Exception + { + public TestingException(string message) { } + public TestingException(string message, System.Exception inner) { } + } +} +namespace Testably.Abstractions.Testing.RandomSystem +{ + public interface IRandomProvider + { + System.Guid GetGuid(); + Testably.Abstractions.RandomSystem.IRandom GetRandom(int seed = -1); + } +} +namespace Testably.Abstractions.Testing.Statistics +{ + public interface IFileSystemStatistics + { + Testably.Abstractions.Testing.Statistics.IStatistics Directory { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics DirectoryInfo { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics DriveInfo { get; } + Testably.Abstractions.Testing.Statistics.IStatistics File { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics FileInfo { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics FileStream { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics FileSystemWatcher { get; } + Testably.Abstractions.Testing.Statistics.IPathStatistics FileVersionInfo { get; } + Testably.Abstractions.Testing.Statistics.IStatistics Path { get; } + int TotalCount { get; } + } + public interface IPathStatistics : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics + { + Testably.Abstractions.Testing.Statistics.IStatistics this[string path] { get; } + } + public interface IStatistics + { + Testably.Abstractions.Testing.Statistics.MethodStatistic[] Methods { get; } + Testably.Abstractions.Testing.Statistics.PropertyStatistic[] Properties { get; } + } + public interface IStatistics : Testably.Abstractions.Testing.Statistics.IStatistics { } + public sealed class MethodStatistic + { + public int Counter { get; } + public string Name { get; } + public Testably.Abstractions.Testing.Statistics.ParameterDescription[] Parameters { get; } + public override string ToString() { } + } + public abstract class ParameterDescription + { + protected ParameterDescription(bool isOutParameter) { } + public bool IsOutParameter { get; } + public bool Is(System.Func comparer) { } + public bool Is(System.ReadOnlySpan value) { } + public bool Is(System.Span value) { } + public bool Is(T value) { } + public bool Is(T[] value) { } + public static Testably.Abstractions.Testing.Statistics.ParameterDescription FromOutParameter(T value) { } + public static Testably.Abstractions.Testing.Statistics.ParameterDescription FromParameter(System.ReadOnlySpan value) { } + public static Testably.Abstractions.Testing.Statistics.ParameterDescription FromParameter(System.Span value) { } + public static Testably.Abstractions.Testing.Statistics.ParameterDescription FromParameter(T value) { } + } + public enum PropertyAccess + { + Get = 0, + Set = 1, + } + public sealed class PropertyStatistic + { + public Testably.Abstractions.Testing.Statistics.PropertyAccess Access { get; } + public int Counter { get; } + public string Name { get; } + public override string ToString() { } + } +} +namespace Testably.Abstractions.Testing.Storage +{ + public interface IStorageDrive : System.IO.Abstractions.IDriveInfo, System.IO.Abstractions.IFileSystemEntity + { + bool IsUncPath { get; } + Testably.Abstractions.Testing.Storage.IStorageDrive ChangeUsedBytes(long usedBytesDelta); + Testably.Abstractions.Testing.Storage.IStorageDrive SetDriveFormat(string driveFormat = "NTFS"); + Testably.Abstractions.Testing.Storage.IStorageDrive SetDriveType(System.IO.DriveType driveType = 3); + Testably.Abstractions.Testing.Storage.IStorageDrive SetIsReady(bool isReady = true); + Testably.Abstractions.Testing.Storage.IStorageDrive SetTotalSize(long totalSize = 1073741824); + } +} +namespace Testably.Abstractions.Testing.TimeSystem +{ + public interface INotificationHandler + { + Testably.Abstractions.Testing.IAwaitableCallback DateTimeRead(System.Action? callback = null, System.Func? predicate = null); + Testably.Abstractions.Testing.IAwaitableCallback TaskDelay(System.Action? callback = null, System.Func? predicate = null); + Testably.Abstractions.Testing.IAwaitableCallback ThreadSleep(System.Action? callback = null, System.Func? predicate = null); + } + public interface ITimeProvider + { + System.DateTime MaxValue { get; set; } + System.DateTime MinValue { get; set; } + System.DateTime UnixEpoch { get; set; } + void AdvanceBy(System.TimeSpan interval); + System.DateTime Read(); + void SetTo(System.DateTime value); + } + public interface ITimerHandler + { + Testably.Abstractions.Testing.TimeSystem.ITimerMock this[int index] { get; } + } + public interface ITimerMock : System.IAsyncDisposable, System.IDisposable, Testably.Abstractions.TimeSystem.ITimeSystemEntity, Testably.Abstractions.TimeSystem.ITimer + { + Testably.Abstractions.Testing.TimeSystem.ITimerMock Wait(int executionCount = 1, int timeout = 10000, System.Action? callback = null); + } + public interface ITimerStrategy + { + Testably.Abstractions.Testing.TimeSystem.TimerMode Mode { get; } + bool SwallowExceptions { get; } + } + public enum TimerMode + { + StartImmediately = 1, + StartOnMockWait = 2, + } + public class TimerStrategy : Testably.Abstractions.Testing.TimeSystem.ITimerStrategy + { + public TimerStrategy(Testably.Abstractions.Testing.TimeSystem.TimerMode mode = 1, bool swallowExceptions = false) { } + public Testably.Abstractions.Testing.TimeSystem.TimerMode Mode { get; } + public bool SwallowExceptions { get; } + public static Testably.Abstractions.Testing.TimeSystem.ITimerStrategy Default { get; } + } +} \ No newline at end of file diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt new file mode 100644 index 000000000..cc57bd753 --- /dev/null +++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions_net9.0.txt @@ -0,0 +1,34 @@ +[assembly: System.CLSCompliant(true)] +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Testably/Testably.Abstractions.git")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Testably.Abstractions.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] +namespace Testably.Abstractions +{ + public sealed class RealFileSystem : System.IO.Abstractions.IFileSystem + { + public RealFileSystem() { } + public System.IO.Abstractions.IDirectory Directory { get; } + public System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } + public System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } + public System.IO.Abstractions.IFile File { get; } + public System.IO.Abstractions.IFileInfoFactory FileInfo { get; } + public System.IO.Abstractions.IFileStreamFactory FileStream { get; } + public System.IO.Abstractions.IFileSystemWatcherFactory FileSystemWatcher { get; } + public System.IO.Abstractions.IFileVersionInfoFactory FileVersionInfo { get; } + public System.IO.Abstractions.IPath Path { get; } + } + public sealed class RealRandomSystem : Testably.Abstractions.IRandomSystem + { + public RealRandomSystem() { } + public Testably.Abstractions.RandomSystem.IGuid Guid { get; } + public Testably.Abstractions.RandomSystem.IRandomFactory Random { get; } + } + public sealed class RealTimeSystem : Testably.Abstractions.ITimeSystem + { + public RealTimeSystem() { } + public Testably.Abstractions.TimeSystem.IDateTime DateTime { get; } + public Testably.Abstractions.TimeSystem.ITask Task { get; } + public Testably.Abstractions.TimeSystem.IThread Thread { get; } + public Testably.Abstractions.TimeSystem.ITimerFactory Timer { get; } + } +} \ No newline at end of file diff --git a/Tests/Directory.Build.props b/Tests/Directory.Build.props index 3590f1f85..9ce367335 100644 --- a/Tests/Directory.Build.props +++ b/Tests/Directory.Build.props @@ -5,8 +5,8 @@ - net6.0;net8.0;net48 - net6.0;net8.0 + net6.0;net8.0;net9.0;net48 + net6.0;net8.0;net9.0 net48 diff --git a/Tests/Helpers/Testably.Abstractions.TestHelpers/AutoDomainDataAttribute.cs b/Tests/Helpers/Testably.Abstractions.TestHelpers/AutoDomainDataAttribute.cs index 557353ca6..7f744b7b1 100644 --- a/Tests/Helpers/Testably.Abstractions.TestHelpers/AutoDomainDataAttribute.cs +++ b/Tests/Helpers/Testably.Abstractions.TestHelpers/AutoDomainDataAttribute.cs @@ -1,35 +1,20 @@ using AutoFixture; -using AutoFixture.Xunit3; using AutoFixture.AutoNSubstitute; +using AutoFixture.Xunit3; using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace Testably.Abstractions.TestHelpers; /// -/// Extension of that uses applies domain-specific customizations. +/// Extension of that uses applies domain-specific customizations. /// public class AutoDomainDataAttribute : AutoDataAttribute { - private Type? _customizeWith; - private readonly DomainFixtureFactory _fixtureFactory; - - /// - /// Extension of that uses applies domain-specific customizations. - /// - public AutoDomainDataAttribute() : this(new DomainFixtureFactory()) - { - } - - private AutoDomainDataAttribute(DomainFixtureFactory fixtureFactory) - : base(fixtureFactory.GetFixtureFactory) - { - _fixtureFactory = fixtureFactory; - } - /// - /// Adds an additional that is applied for only this test. + /// Adds an additional that is applied for only this test. /// public Type? CustomizeWith { @@ -44,31 +29,32 @@ public Type? CustomizeWith } } + private Type? _customizeWith; + private readonly DomainFixtureFactory _fixtureFactory; + + /// + /// Extension of that uses applies domain-specific customizations. + /// + public AutoDomainDataAttribute() : this(new DomainFixtureFactory()) + { + } + + private AutoDomainDataAttribute(DomainFixtureFactory fixtureFactory) + : base(fixtureFactory.GetFixtureFactory) + { + _fixtureFactory = fixtureFactory; + } + private sealed class DomainFixtureFactory { - private ICustomization? _customizeWith; private static Lazy _domainCustomisation { get; } = new(Initialize); - - public Fixture GetFixtureFactory() - { - var fixture = new Fixture(); - fixture.Customize(new AutoNSubstituteCustomization()); - foreach (var domainCustomization in _domainCustomisation.Value) - { - domainCustomization.Customize(fixture); - } - if (_customizeWith != null) - { - fixture.Customize(_customizeWith); - } - return fixture; - } + private ICustomization? _customizeWith; public void CustomizeWith(Type? type) { Type customizationInterface = typeof(ICustomization); if (type != null && - customizationInterface.IsAssignableFrom(type)) + customizationInterface.IsAssignableFrom(type)) { try { @@ -82,28 +68,58 @@ public void CustomizeWith(Type? type) } } + public Fixture GetFixtureFactory() + { + Fixture fixture = new(); + fixture.Customize(new AutoNSubstituteCustomization()); + foreach (ICustomization domainCustomization in _domainCustomisation.Value) + { + domainCustomization.Customize(fixture); + } + + if (_customizeWith != null) + { + fixture.Customize(_customizeWith); + } + + return fixture; + } + private static ICustomization[] Initialize() { List domainCustomizations = new(); Type autoDataCustomizationInterface = typeof(IAutoDataCustomization); - foreach (Type type in AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => a.GetTypes()) - .Where(x => x.IsClass && autoDataCustomizationInterface.IsAssignableFrom(x))) + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { try { - IAutoDataCustomization? domainCustomization = (IAutoDataCustomization?)Activator.CreateInstance(type); - if (domainCustomization != null) + foreach (Type type in assembly.GetTypes() + .Where(x => x.IsClass && + autoDataCustomizationInterface.IsAssignableFrom(x))) { - domainCustomizations.Add(domainCustomization); + try + { + IAutoDataCustomization? domainCustomization = + (IAutoDataCustomization?)Activator.CreateInstance(type); + if (domainCustomization != null) + { + domainCustomizations.Add(domainCustomization); + } + } + catch (Exception ex) + { + throw new InvalidOperationException( + $"Could not instantiate auto data customization '{type.Name}'!", + ex); + } } } - catch (Exception ex) + catch (ReflectionTypeLoadException) { - throw new InvalidOperationException( - $"Could not instantiate auto data customization '{type.Name}'!", ex); + // Ignore assemblies that can't be loaded } } + return domainCustomizations.ToArray(); } } diff --git a/Tests/Settings/Testably.Abstractions.TestSettings/Helper.cs b/Tests/Settings/Testably.Abstractions.TestSettings/Helper.cs index 9b01b87f3..086f91030 100644 --- a/Tests/Settings/Testably.Abstractions.TestSettings/Helper.cs +++ b/Tests/Settings/Testably.Abstractions.TestSettings/Helper.cs @@ -35,7 +35,7 @@ public static TestEnvironment ChangeTestSettings( } private static string GetTestSettingsPath() => - Path.GetFullPath(Path.Combine("..", "..", "..", "..", "test.settings.json")); + Path.GetFullPath(Path.Combine("..", "..", "..", "..", "..", "test.settings.json")); private static TestEnvironment ReadTestSettings() { diff --git a/Tests/Testably.Abstractions.Parity.Tests/Net9ParityTests.cs b/Tests/Testably.Abstractions.Parity.Tests/Net9ParityTests.cs new file mode 100644 index 000000000..80cab2232 --- /dev/null +++ b/Tests/Testably.Abstractions.Parity.Tests/Net9ParityTests.cs @@ -0,0 +1,17 @@ +#if NET9_0 +using System.IO; + +namespace Testably.Abstractions.Parity.Tests; + +// ReSharper disable once UnusedMember.Global +public class Net9ParityTests : ParityTests +{ + public Net9ParityTests(ITestOutputHelper testOutputHelper) + : base(new TestHelpers.Parity(), testOutputHelper) + { + Parity.File.MissingMethods.Add( + typeof(File).GetMethod(nameof(File.OpenHandle))); + } +} + +#endif diff --git a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStatisticsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStatisticsTests.cs index d671f34ff..b8bff1339 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStatisticsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStatisticsTests.cs @@ -17,6 +17,70 @@ namespace Testably.Abstractions.Testing.Tests.Statistics.FileSystem; public sealed class FileStatisticsTests { +#if FEATURE_FILE_SPAN + [Fact] + public void Method_AppendAllBytes_String_ByteArray_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + byte[] bytes = "foo"u8.ToArray(); + + sut.File.AppendAllBytes(path, bytes); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllBytes), + path, bytes); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public void Method_AppendAllBytes_String_ReadOnlySpanByte_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan bytes = new(); + + sut.File.AppendAllBytes(path, bytes); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllBytes), + path, bytes); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_AppendAllBytesAsync_String_ByteArray_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + byte[] bytes = "foo"u8.ToArray(); + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.AppendAllBytesAsync(path, bytes, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllBytesAsync), + path, bytes, cancellationToken); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_AppendAllBytesAsync_String_ReadOnlyMemoryByte_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory bytes = new(); + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.AppendAllBytesAsync(path, bytes, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllBytesAsync), + path, bytes, cancellationToken); + } +#endif + [Fact] public void Method_AppendAllLines_String_IEnumerableString_Encoding_ShouldRegisterCall() { @@ -83,6 +147,37 @@ public async Task } #endif +#if FEATURE_FILE_SPAN + [Fact] + public void Method_AppendAllText_String_ReadOnlySpanChar_Encoding_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan contents = new(); + Encoding encoding = Encoding.UTF8; + + sut.File.AppendAllText(path, contents, encoding); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllText), + path, contents, encoding); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public void Method_AppendAllText_String_ReadOnlySpanChar_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan contents = new(); + + sut.File.AppendAllText(path, contents); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllText), + path, contents); + } +#endif + [Fact] public void Method_AppendAllText_String_String_Encoding_ShouldRegisterCall() { @@ -112,6 +207,41 @@ public void Method_AppendAllText_String_String_ShouldRegisterCall() path, contents); } +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_AppendAllTextAsync_String_ReadOnlyMemoryChar_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory contents = new(); + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.AppendAllTextAsync(path, contents, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllTextAsync), + path, contents, cancellationToken); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_AppendAllTextAsync_String_ReadOnlyMemoryChar_Encoding_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory contents = new(); + Encoding encoding = Encoding.UTF8; + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.AppendAllTextAsync(path, contents, encoding, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.AppendAllTextAsync), + path, contents, encoding, cancellationToken); + } +#endif + #if FEATURE_FILESYSTEM_ASYNC [Fact] public async Task Method_AppendAllTextAsync_String_String_CancellationToken_ShouldRegisterCall() @@ -1299,6 +1429,21 @@ public void Method_WriteAllBytes_String_ByteArray_ShouldRegisterCall() path, bytes); } +#if FEATURE_FILE_SPAN + [Fact] + public void Method_WriteAllBytes_String_ReadOnlySpanByte_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan bytes = new(); + + sut.File.WriteAllBytes(path, bytes); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllBytes), + path, bytes); + } +#endif + #if FEATURE_FILESYSTEM_ASYNC [Fact] public async Task @@ -1317,6 +1462,23 @@ public async Task } #endif +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_WriteAllBytesAsync_String_ReadOnlyMemoryByte_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory bytes = new(); + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.WriteAllBytesAsync(path, bytes, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllBytesAsync), + path, bytes, cancellationToken); + } +#endif + [Fact] public void Method_WriteAllLines_String_IEnumerableString_Encoding_ShouldRegisterCall() { @@ -1412,6 +1574,37 @@ public async Task } #endif +#if FEATURE_FILE_SPAN + [Fact] + public void Method_WriteAllText_String_ReadOnlySpanChar_Encoding_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan contents = new(); + Encoding encoding = Encoding.UTF8; + + sut.File.WriteAllText(path, contents, encoding); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllText), + path, contents, encoding); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public void Method_WriteAllText_String_ReadOnlySpanChar_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlySpan contents = new(); + + sut.File.WriteAllText(path, contents); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllText), + path, contents); + } +#endif + [Fact] public void Method_WriteAllText_String_String_Encoding_ShouldRegisterCall() { @@ -1441,6 +1634,41 @@ public void Method_WriteAllText_String_String_ShouldRegisterCall() path, contents); } +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_WriteAllTextAsync_String_ReadOnlyMemoryChar_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory contents = new(); + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.WriteAllTextAsync(path, contents, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllTextAsync), + path, contents, cancellationToken); + } +#endif + +#if FEATURE_FILE_SPAN + [Fact] + public async Task + Method_WriteAllTextAsync_String_ReadOnlyMemoryChar_Encoding_CancellationToken_ShouldRegisterCall() + { + MockFileSystem sut = new(); + string path = "foo"; + ReadOnlyMemory contents = new(); + Encoding encoding = Encoding.UTF8; + CancellationToken cancellationToken = CancellationToken.None; + + await sut.File.WriteAllTextAsync(path, contents, encoding, cancellationToken); + + sut.Statistics.File.ShouldOnlyContainMethodCall(nameof(IFile.WriteAllTextAsync), + path, contents, encoding, cancellationToken); + } +#endif + #if FEATURE_FILESYSTEM_ASYNC [Fact] public async Task Method_WriteAllTextAsync_String_String_CancellationToken_ShouldRegisterCall() diff --git a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/PathStatisticsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/PathStatisticsTests.cs index 48a7be877..6c4562d38 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/PathStatisticsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/PathStatisticsTests.cs @@ -21,6 +21,19 @@ public void Method_ChangeExtension_String_String_ShouldRegisterCall() sut.Statistics.Path.ShouldOnlyContainMethodCall(nameof(IPath.ChangeExtension), path, extension); } +#if FEATURE_PATH_SPAN + [Fact] + public void Method_Combine_ReadOnlySpanString_ShouldRegisterCall() + { + MockFileSystem sut = new(); + ReadOnlySpan paths = new(); + + sut.Path.Combine(paths); + + sut.Statistics.Path.ShouldOnlyContainMethodCall(nameof(IPath.Combine), + paths); + } +#endif [Fact] public void Method_Combine_String_String_ShouldRegisterCall() @@ -503,6 +516,20 @@ public void Method_Join_ReadOnlySpanChar_ReadOnlySpanChar_ShouldRegisterCall() } #endif +#if FEATURE_PATH_SPAN + [Fact] + public void Method_Join_ReadOnlySpanString_ShouldRegisterCall() + { + MockFileSystem sut = new(); + ReadOnlySpan paths = new(); + + sut.Path.Join(paths); + + sut.Statistics.Path.ShouldOnlyContainMethodCall(nameof(IPath.Join), + paths); + } +#endif + #if FEATURE_PATH_JOIN [Fact] public void Method_Join_String_String_ShouldRegisterCall() diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs index 73f40f448..465c0ad2f 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs @@ -151,6 +151,35 @@ public static void ShouldOnlyContainMethodCall(this IStatist } #endif +#if FEATURE_FILE_SPAN + public static void ShouldOnlyContainMethodCall(this IStatistics statistics, + string name, + T1 parameter1, ReadOnlySpan parameter2) + { + statistics.Methods.Length.Should().Be(1); + MethodStatistic? statistic = statistics.Methods.Should() + .ContainSingle(c => c.Name == name && + c.Parameters.Length == 2).Which; + statistic.Parameters[0].Is(parameter1).Should().BeTrue(); + statistic.Parameters[1].Is(parameter2).Should().BeTrue(); + } +#endif + +#if FEATURE_FILE_SPAN + public static void ShouldOnlyContainMethodCall(this IStatistics statistics, + string name, + T1 parameter1, ReadOnlySpan parameter2, T3 parameter3) + { + statistics.Methods.Length.Should().Be(1); + MethodStatistic? statistic = statistics.Methods.Should() + .ContainSingle(c => c.Name == name && + c.Parameters.Length == 3).Which; + statistic.Parameters[0].Is(parameter1).Should().BeTrue(); + statistic.Parameters[1].Is(parameter2).Should().BeTrue(); + statistic.Parameters[2].Is(parameter3).Should().BeTrue(); + } +#endif + #if FEATURE_SPAN public static void ShouldOnlyContainMethodCall(this IStatistics statistics, string name, Span parameter1) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesAsyncTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesAsyncTests.cs new file mode 100644 index 000000000..985987735 --- /dev/null +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesAsyncTests.cs @@ -0,0 +1,174 @@ +#if FEATURE_FILE_SPAN +using System.IO; +using System.Threading; + +namespace Testably.Abstractions.Tests.FileSystem.File; + +// ReSharper disable MethodHasAsyncOverload +[FileSystemTests] +public partial class AppendAllBytesAsyncTests +{ + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_Cancelled_ShouldThrowTaskCanceledException( + string path, byte[] bytes) + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.AppendAllBytesAsync(path, bytes, cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_ExistingFile_ShouldAppendLinesToFile( + string path, byte[] previousBytes, byte[] bytes) + { + await FileSystem.File.AppendAllBytesAsync(path, previousBytes, + TestContext.Current.CancellationToken); + + await FileSystem.File.AppendAllBytesAsync(path, bytes, + TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent([..previousBytes, ..bytes]); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, byte[] bytes) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + + async Task Act() + { + await FileSystem.File.AppendAllBytesAsync(filePath, bytes, + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_MissingFile_ShouldCreateFile( + string path, byte[] bytes) + { + await FileSystem.File.AppendAllBytesAsync(path, bytes, + TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_ReadOnlyMemory_Cancelled_ShouldThrowTaskCanceledException( + string path, byte[] bytes) + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.AppendAllBytesAsync(path, bytes.AsMemory(), cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_ReadOnlyMemory_ExistingFile_ShouldAppendLinesToFile( + string path, byte[] previousBytes, byte[] bytes) + { + await FileSystem.File.AppendAllBytesAsync(path, previousBytes, + TestContext.Current.CancellationToken); + + await FileSystem.File.AppendAllBytesAsync(path, bytes.AsMemory(), + TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent([..previousBytes, ..bytes]); + } + + [Theory] + [AutoData] + public async Task + AppendAllBytesAsync_ReadOnlyMemory_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, byte[] bytes) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + + async Task Act() + { + await FileSystem.File.AppendAllBytesAsync(filePath, bytes.AsMemory(), + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public async Task AppendAllBytesAsync_ReadOnlyMemory_MissingFile_ShouldCreateFile( + string path, byte[] bytes) + { + await FileSystem.File.AppendAllBytesAsync(path, bytes.AsMemory(), + TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public async Task + AppendAllBytesAsync_ReadOnlyMemory_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, byte[] bytes) + { + FileSystem.Directory.CreateDirectory(path); + + async Task Act() + { + await FileSystem.File.AppendAllBytesAsync(path, bytes.AsMemory(), + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public async Task + AppendAllBytesAsync_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, byte[] bytes) + { + FileSystem.Directory.CreateDirectory(path); + + async Task Act() + { + await FileSystem.File.AppendAllBytesAsync(path, bytes, + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } +} +#endif diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesTests.cs new file mode 100644 index 000000000..b20fb88b2 --- /dev/null +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllBytesTests.cs @@ -0,0 +1,225 @@ +#if FEATURE_FILE_SPAN +using System.IO; + +namespace Testably.Abstractions.Tests.FileSystem.File; + +[FileSystemTests] +public partial class AppendAllBytesTests +{ + [Theory] + [AutoData] + public void AppendAllBytes_ExistingFile_ShouldAppendLinesToFile( + string path, byte[] previousBytes, byte[] bytes) + { + FileSystem.File.AppendAllBytes(path, previousBytes); + + FileSystem.File.AppendAllBytes(path, bytes); + + FileSystem.Should().HaveFile(path) + .Which.HasContent([..previousBytes, ..bytes]); + } + + [Theory] + [AutoData] + public void AppendAllBytes_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, byte[] bytes) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(filePath, bytes); + }); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public void AppendAllBytes_MissingFile_ShouldCreateFile( + string path, byte[] bytes) + { + FileSystem.File.AppendAllBytes(path, bytes); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public void AppendAllBytes_ShouldAdjustTimes(string path, byte[] bytes) + { + SkipIfLongRunningTestsShouldBeSkipped(); + + DateTime creationTimeStart = TimeSystem.DateTime.UtcNow; + FileSystem.File.WriteAllText(path, "foo"); + DateTime creationTimeEnd = TimeSystem.DateTime.UtcNow; + TimeSystem.Thread.Sleep(FileTestHelper.AdjustTimesDelay); + DateTime updateTime = TimeSystem.DateTime.UtcNow; + + FileSystem.File.AppendAllBytes(path, bytes); + + DateTime creationTime = FileSystem.File.GetCreationTimeUtc(path); + DateTime lastAccessTime = FileSystem.File.GetLastAccessTimeUtc(path); + DateTime lastWriteTime = FileSystem.File.GetLastWriteTimeUtc(path); + + if (Test.RunsOnWindows) + { + creationTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + lastAccessTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + else + { + lastAccessTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + } + + lastWriteTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + + [Theory] + [AutoData] + public void AppendAllBytes_Span_ExistingFile_ShouldAppendLinesToFile( + string path, byte[] previousBytes, byte[] bytes) + { + FileSystem.File.AppendAllBytes(path, previousBytes); + + FileSystem.File.AppendAllBytes(path, bytes.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent([..previousBytes, ..bytes]); + } + + [Theory] + [AutoData] + public void AppendAllBytes_Span_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, byte[] bytes) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(filePath, bytes.AsSpan()); + }); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public void AppendAllBytes_Span_MissingFile_ShouldCreateFile( + string path, byte[] bytes) + { + FileSystem.File.AppendAllBytes(path, bytes.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public void AppendAllBytes_Span_ShouldAdjustTimes(string path, byte[] bytes) + { + SkipIfLongRunningTestsShouldBeSkipped(); + + DateTime creationTimeStart = TimeSystem.DateTime.UtcNow; + FileSystem.File.WriteAllText(path, "foo"); + DateTime creationTimeEnd = TimeSystem.DateTime.UtcNow; + TimeSystem.Thread.Sleep(FileTestHelper.AdjustTimesDelay); + DateTime updateTime = TimeSystem.DateTime.UtcNow; + + FileSystem.File.AppendAllBytes(path, bytes.AsSpan()); + + DateTime creationTime = FileSystem.File.GetCreationTimeUtc(path); + DateTime lastAccessTime = FileSystem.File.GetLastAccessTimeUtc(path); + DateTime lastWriteTime = FileSystem.File.GetLastWriteTimeUtc(path); + + if (Test.RunsOnWindows) + { + creationTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + lastAccessTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + else + { + lastAccessTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + } + + lastWriteTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + + [Theory] + [AutoData] + public void + AppendAllBytes_Span_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path) + { + FileSystem.Directory.CreateDirectory(path); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(path, Array.Empty().AsSpan()); + }); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public void AppendAllBytes_Span_WhenFileIsHidden_ShouldNotThrowException( + string path, byte[] bytes) + { + FileSystem.File.WriteAllText(path, "some content"); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(path, bytes.AsSpan()); + }); + + exception.Should().BeNull(); + } + + [Theory] + [AutoData] + public void + AppendAllBytes_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path) + { + FileSystem.Directory.CreateDirectory(path); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(path, Array.Empty()); + }); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public void AppendAllBytes_WhenFileIsHidden_ShouldNotThrowException( + string path, byte[] bytes) + { + FileSystem.File.WriteAllText(path, "some content"); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllBytes(path, bytes); + }); + + exception.Should().BeNull(); + } +} +#endif diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextAsyncTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextAsyncTests.cs index df8f1b0cd..543c6a775 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextAsyncTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextAsyncTests.cs @@ -3,7 +3,6 @@ using System.IO; using System.Text; using System.Threading; -using System.Threading.Tasks; namespace Testably.Abstractions.Tests.FileSystem.File; @@ -127,5 +126,124 @@ public async Task AppendAllTextAsync_WithDifferentEncoding_ShouldNotReturnWritte result.Should().NotBeEquivalentTo(contents, $"{contents} should be different when encoding from {writeEncoding} to {readEncoding}."); } + +#if FEATURE_FILE_SPAN + [Theory] + [AutoData] + public async Task AppendAllTextAsync_ReadOnlyMemory_Cancelled_ShouldThrowTaskCanceledException( + string path, string contents) + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task + AppendAllTextAsync_ReadOnlyMemory_Cancelled_WithEncoding_ShouldThrowTaskCanceledException( + string path, string contents) + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), Encoding.UTF8, cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task AppendAllTextAsync_ReadOnlyMemory_ExistingFile_ShouldAppendLinesToFile( + string path, string previousContents, string contents) + { + await FileSystem.File.AppendAllTextAsync(path, previousContents, TestContext.Current.CancellationToken); + + await FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(previousContents + contents); + } + + [Theory] + [AutoData] + public async Task AppendAllTextAsync_ReadOnlyMemory_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, string contents) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + + async Task Act() + { + await FileSystem.File.AppendAllTextAsync(filePath, contents.AsMemory(), TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public async Task AppendAllTextAsync_ReadOnlyMemory_MissingFile_ShouldCreateFile( + string path, string contents) + { + await FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(contents); + } + + [Theory] + [AutoData] + public async Task AppendAllTextAsync_ReadOnlyMemory_ShouldNotEndWithNewline(string path) + { + string contents = "foo"; + + await FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(contents); + } + + [Theory] + [AutoData] + public async Task + AppendAllTextAsync_ReadOnlyMemory_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, string contents) + { + FileSystem.Directory.CreateDirectory(path); + + async Task Act() + { + await FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [ClassData(typeof(TestDataGetEncodingDifference))] + public async Task AppendAllTextAsync_ReadOnlyMemory_WithDifferentEncoding_ShouldNotReturnWrittenText( + string contents, Encoding writeEncoding, Encoding readEncoding) + { + string path = new Fixture().Create(); + await FileSystem.File.AppendAllTextAsync(path, contents.AsMemory(), writeEncoding, TestContext.Current.CancellationToken); + + string[] result = FileSystem.File.ReadAllLines(path, readEncoding); + + result.Should().NotBeEquivalentTo(contents, + $"{contents} should be different when encoding from {writeEncoding} to {readEncoding}."); + } +#endif } #endif diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextTests.cs index 9ceb472c6..5b838e465 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/AppendAllTextTests.cs @@ -153,4 +153,153 @@ public void AppendAllText_WithDifferentEncoding_ShouldNotReturnWrittenText( result.Should().NotBeEquivalentTo(contents, $"{contents} should be different when encoding from {writeEncoding} to {readEncoding}."); } + +#if FEATURE_FILE_SPAN + [Theory] + [AutoData] + public void AppendAllText_Span_ExistingFile_ShouldAppendLinesToFile( + string path, string previousContents, string contents) + { + FileSystem.File.AppendAllText(path, previousContents); + + FileSystem.File.AppendAllText(path, contents.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(previousContents + contents); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string missingPath, string fileName, string contents) + { + string filePath = FileSystem.Path.Combine(missingPath, fileName); + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllText(filePath, contents.AsSpan()); + }); + + exception.Should().BeException(hResult: -2147024893); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_MissingFile_ShouldCreateFile( + string path, string contents) + { + FileSystem.File.AppendAllText(path, contents.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(contents); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_MissingFile_ShouldCreateFileWithByteOrderMark( + string path) + { + byte[] expectedBytes = [255, 254, 0, 0, 65, 0, 0, 0, 65, 0, 0, 0]; + + FileSystem.File.AppendAllText(path, "AA".AsSpan(), Encoding.UTF32); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(expectedBytes); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_ShouldAdjustTimes(string path, string contents) + { + SkipIfLongRunningTestsShouldBeSkipped(); + + DateTime creationTimeStart = TimeSystem.DateTime.UtcNow; + FileSystem.File.WriteAllText(path, "foo"); + DateTime creationTimeEnd = TimeSystem.DateTime.UtcNow; + TimeSystem.Thread.Sleep(FileTestHelper.AdjustTimesDelay); + DateTime updateTime = TimeSystem.DateTime.UtcNow; + + FileSystem.File.AppendAllText(path, contents.AsSpan()); + + DateTime creationTime = FileSystem.File.GetCreationTimeUtc(path); + DateTime lastAccessTime = FileSystem.File.GetLastAccessTimeUtc(path); + DateTime lastWriteTime = FileSystem.File.GetLastWriteTimeUtc(path); + + if (Test.RunsOnWindows) + { + creationTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + lastAccessTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + else + { + lastAccessTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + } + + lastWriteTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_ShouldNotEndWithNewline(string path) + { + string contents = "foo"; + + FileSystem.File.AppendAllText(path, contents.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(contents); + } + + [Theory] + [AutoData] + public void + AppendAllText_Span_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path) + { + FileSystem.Directory.CreateDirectory(path); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllText(path, "".AsSpan()); + }); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public void AppendAllText_Span_WhenFileIsHidden_ShouldNotThrowException( + string path, string contents) + { + FileSystem.File.WriteAllText(path, "some content"); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.AppendAllText(path, contents.AsSpan()); + }); + + exception.Should().BeNull(); + } + + [Theory] + [ClassData(typeof(TestDataGetEncodingDifference))] + public void AppendAllText_Span_WithDifferentEncoding_ShouldNotReturnWrittenText( + string contents, Encoding writeEncoding, Encoding readEncoding) + { + string path = new Fixture().Create(); + FileSystem.File.AppendAllText(path, contents.AsSpan(), writeEncoding); + + string[] result = FileSystem.File.ReadAllLines(path, readEncoding); + + result.Should().NotBeEquivalentTo(contents, + $"{contents} should be different when encoding from {writeEncoding} to {readEncoding}."); + } +#endif } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/OpenReadTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/OpenReadTests.cs index 7a49f1a2f..858a5e7e3 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/OpenReadTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/OpenReadTests.cs @@ -94,7 +94,7 @@ async Task Act() public async Task OpenRead_WriteAsyncWithMemory_ShouldThrowNotSupportedException( string path, byte[] bytes) { - await FileSystem.File.WriteAllTextAsync(path, null, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); async Task Act() { diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesAsyncTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesAsyncTests.cs index ac0ecb5c3..841a05063 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesAsyncTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesAsyncTests.cs @@ -92,7 +92,7 @@ public async Task { Skip.IfNot(Test.RunsOnWindows); - await FileSystem.File.WriteAllTextAsync(path, null, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); FileSystem.File.SetAttributes(path, FileAttributes.Hidden); async Task Act() @@ -104,5 +104,87 @@ async Task Act() exception.Should().BeException(hResult: -2147024891); } + +#if FEATURE_FILE_SPAN + [Theory] + [AutoData] + public async Task WriteAllBytesAsync_ReadOnlyMemory_Cancelled_ShouldThrowTaskCanceledException( + string path, byte[] bytes) + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.WriteAllBytesAsync(path, bytes.AsMemory(), cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task WriteAllBytesAsync_ReadOnlyMemory_PreviousFile_ShouldOverwriteFileWithBytes( + string path, byte[] bytes) + { + await FileSystem.File.WriteAllBytesAsync(path, Encoding.UTF8.GetBytes("foo"), TestContext.Current.CancellationToken); + + await FileSystem.File.WriteAllBytesAsync(path, bytes.AsMemory(), TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public async Task WriteAllBytesAsync_ReadOnlyMemory_ShouldCreateFileWithBytes( + string path, byte[] bytes) + { + await FileSystem.File.WriteAllBytesAsync(path, bytes.AsMemory(), TestContext.Current.CancellationToken); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public async Task + WriteAllBytesAsync_ReadOnlyMemory_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, byte[] bytes) + { + FileSystem.Directory.CreateDirectory(path); + + async Task Act() + { + await FileSystem.File.WriteAllBytesAsync(path, bytes.AsMemory(), TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public async Task + WriteAllTextAsync_ReadOnlyMemory_WhenFileIsHidden_ShouldThrowUnauthorizedAccessException_OnWindows( + string path, byte[] bytes) + { + Skip.IfNot(Test.RunsOnWindows); + + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + async Task Act() + { + await FileSystem.File.WriteAllBytesAsync(path, bytes.AsMemory(), TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException(hResult: -2147024891); + } +#endif } #endif diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesTests.cs index b58d1e686..b1746d5e7 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllBytesTests.cs @@ -77,4 +77,66 @@ public void WriteAllBytes_WhenFileIsHidden_ShouldThrowUnauthorizedAccessExceptio exception.Should().BeException(hResult: -2147024891); } + +#if FEATURE_FILE_SPAN + [Theory] + [AutoData] + public void WriteAllBytes_Span_PreviousFile_ShouldOverwriteFileWithBytes( + string path, byte[] bytes) + { + FileSystem.File.WriteAllBytes(path, Encoding.UTF8.GetBytes("foo")); + + FileSystem.File.WriteAllBytes(path, bytes.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public void WriteAllBytes_Span_ShouldCreateFileWithBytes(string path, byte[] bytes) + { + FileSystem.File.WriteAllBytes(path, bytes.AsSpan()); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(bytes); + } + + [Theory] + [AutoData] + public void + WriteAllBytes_Span_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, byte[] bytes) + { + FileSystem.Directory.CreateDirectory(path); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.WriteAllBytes(path, bytes.AsSpan()); + }); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public void WriteAllBytes_Span_WhenFileIsHidden_ShouldThrowUnauthorizedAccessException_OnWindows( + string path, byte[] bytes) + { + Skip.IfNot(Test.RunsOnWindows); + + FileSystem.File.WriteAllBytes(path, Array.Empty()); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.WriteAllBytes(path, bytes.AsSpan()); + }); + + exception.Should().BeException(hResult: -2147024891); + } +#endif } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllLinesAsyncTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllLinesAsyncTests.cs index 6b7bb9bd5..ecefea982 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllLinesAsyncTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllLinesAsyncTests.cs @@ -148,7 +148,7 @@ public async Task { Skip.IfNot(Test.RunsOnWindows); - await FileSystem.File.WriteAllTextAsync(path, null, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); FileSystem.File.SetAttributes(path, FileAttributes.Hidden); async Task Act() diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextAsyncTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextAsyncTests.cs index 456fe1a86..de2e0325e 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextAsyncTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextAsyncTests.cs @@ -2,7 +2,6 @@ using System.IO; using System.Text; using System.Threading; -using System.Threading.Tasks; namespace Testably.Abstractions.Tests.FileSystem.File; @@ -44,9 +43,11 @@ public async Task WriteAllTextAsync_PreviousFile_ShouldOverwriteFileWithText( string path, string contents) { await FileSystem.File.WriteAllTextAsync(path, "foo", TestContext.Current.CancellationToken); - await FileSystem.File.WriteAllTextAsync(path, contents, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents, + TestContext.Current.CancellationToken); - string result = await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); result.Should().Be(contents); } @@ -56,9 +57,11 @@ public async Task WriteAllTextAsync_PreviousFile_ShouldOverwriteFileWithText( public async Task WriteAllTextAsync_ShouldCreateFileWithText( string path, string contents) { - await FileSystem.File.WriteAllTextAsync(path, contents, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents, + TestContext.Current.CancellationToken); - string result = await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); result.Should().Be(contents); } @@ -68,7 +71,8 @@ public async Task WriteAllTextAsync_ShouldCreateFileWithText( public async Task WriteAllTextAsync_SpecialCharacters_ShouldReturnSameText( string path) { - char[] specialCharacters = [ + char[] specialCharacters = + [ 'Ä', 'Ö', 'Ü', @@ -80,9 +84,11 @@ public async Task WriteAllTextAsync_SpecialCharacters_ShouldReturnSameText( foreach (char specialCharacter in specialCharacters) { string contents = "_" + specialCharacter; - await FileSystem.File.WriteAllTextAsync(path, contents, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents, + TestContext.Current.CancellationToken); - string result = await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); result.Should().Be(contents, $"{contents} should be encoded and decoded identical."); @@ -95,9 +101,10 @@ public async Task WriteAllTextAsync_WhenContentIsNull_ShouldNotThrowException(st { async Task Act() { - await FileSystem.File.WriteAllTextAsync(path, null, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, (string?)null, + TestContext.Current.CancellationToken); } - + Exception? exception = await Record.ExceptionAsync(Act); exception.Should().BeNull(); } @@ -112,7 +119,8 @@ public async Task async Task Act() { - await FileSystem.File.WriteAllTextAsync(path, contents, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents, + TestContext.Current.CancellationToken); } Exception? exception = await Record.ExceptionAsync(Act); @@ -131,17 +139,151 @@ public async Task { Skip.IfNot(Test.RunsOnWindows); - await FileSystem.File.WriteAllTextAsync(path, null, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); FileSystem.File.SetAttributes(path, FileAttributes.Hidden); async Task Act() { - await FileSystem.File.WriteAllTextAsync(path, contents, TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents, + TestContext.Current.CancellationToken); } Exception? exception = await Record.ExceptionAsync(Act); exception.Should().BeException(hResult: -2147024891); } + +#if FEATURE_FILE_SPAN + [Theory] + [AutoData] + public async Task WriteAllTextAsync_ReadOnlyMemory_Cancelled_ShouldThrowTaskCanceledException( + string path, string? contents) + { + using CancellationTokenSource cts = new(); + await cts.CancelAsync(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task + WriteAllTextAsync_ReadOnlyMemory_Cancelled_WithEncoding_ShouldThrowTaskCanceledException( + string path, string? contents) + { + using CancellationTokenSource cts = new(); + await cts.CancelAsync(); + + Exception? exception = await Record.ExceptionAsync(() => + FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), Encoding.UTF8, cts.Token)); + + exception.Should().BeException(hResult: -2146233029); + } + + [Theory] + [AutoData] + public async Task WriteAllTextAsync_ReadOnlyMemory_PreviousFile_ShouldOverwriteFileWithText( + string path, string contents) + { + await FileSystem.File.WriteAllTextAsync(path, "foo", TestContext.Current.CancellationToken); + await FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), + TestContext.Current.CancellationToken); + + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + + result.Should().Be(contents); + } + + [Theory] + [AutoData] + public async Task WriteAllTextAsync_ReadOnlyMemory_ShouldCreateFileWithText( + string path, string contents) + { + await FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), + TestContext.Current.CancellationToken); + + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + + result.Should().Be(contents); + } + + [Theory] + [AutoData] + public async Task WriteAllTextAsync_ReadOnlyMemory_SpecialCharacters_ShouldReturnSameText( + string path) + { + char[] specialCharacters = + [ + 'Ä', + 'Ö', + 'Ü', + 'ä', + 'ö', + 'ü', + 'ß', + ]; + foreach (char specialCharacter in specialCharacters) + { + string contents = "_" + specialCharacter; + await FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), + TestContext.Current.CancellationToken); + + string result = + await FileSystem.File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); + + result.Should().Be(contents, + $"{contents} should be encoded and decoded identical."); + } + } + + [Theory] + [AutoData] + public async Task + WriteAllTextAsync_ReadOnlyMemory_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path, string contents) + { + FileSystem.Directory.CreateDirectory(path); + + async Task Act() + { + await FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public async Task + WriteAllTextAsync_ReadOnlyMemory_WhenFileIsHidden_ShouldThrowUnauthorizedAccessException_OnWindows( + string path, string contents) + { + Skip.IfNot(Test.RunsOnWindows); + + await FileSystem.File.WriteAllTextAsync(path, "", TestContext.Current.CancellationToken); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + async Task Act() + { + await FileSystem.File.WriteAllTextAsync(path, contents.AsMemory(), + TestContext.Current.CancellationToken); + } + + Exception? exception = await Record.ExceptionAsync(Act); + + exception.Should().BeException(hResult: -2147024891); + } +#endif } #endif diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextTests.cs index 9e3a4901d..5d648d45d 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/WriteAllTextTests.cs @@ -166,4 +166,156 @@ public void WriteAllText_WhenFileIsHidden_ShouldThrowUnauthorizedAccessException exception.Should().BeException(hResult: -2147024891); } + +#if FEATURE_FILE_SPAN + + [Theory] + [AutoData] + public void WriteAllText_Span_MissingDirectory_ShouldThrowDirectoryNotFoundException( + string directory, string path) + { + string fullPath = FileSystem.Path.Combine(directory, path); + Exception? exception = Record.Exception(() => + { + FileSystem.File.WriteAllText(fullPath, "foo".AsSpan()); + }); + + exception.Should().BeException( + hResult: -2147024893, + messageContains: $"'{FileSystem.Path.GetFullPath(fullPath)}'"); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_PreviousFile_ShouldOverwriteFileWithText( + string path, string contents) + { + FileSystem.File.WriteAllText(path, "foo"); + + FileSystem.File.WriteAllText(path, contents.AsSpan()); + + string result = FileSystem.File.ReadAllText(path); + result.Should().Be(contents); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_ShouldAdjustTimes(string path, string contents) + { + SkipIfLongRunningTestsShouldBeSkipped(); + + DateTime creationTimeStart = TimeSystem.DateTime.UtcNow; + FileSystem.File.WriteAllText(path, "foo"); + DateTime creationTimeEnd = TimeSystem.DateTime.UtcNow; + TimeSystem.Thread.Sleep(FileTestHelper.AdjustTimesDelay); + DateTime updateTime = TimeSystem.DateTime.UtcNow; + + FileSystem.File.WriteAllText(path, contents.AsSpan()); + + DateTime creationTime = FileSystem.File.GetCreationTimeUtc(path); + DateTime lastAccessTime = FileSystem.File.GetLastAccessTimeUtc(path); + DateTime lastWriteTime = FileSystem.File.GetLastWriteTimeUtc(path); + + if (Test.RunsOnWindows) + { + creationTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + lastAccessTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + else + { + lastAccessTime.Should() + .BeBetween(creationTimeStart, creationTimeEnd); + } + + lastWriteTime.Should() + .BeOnOrAfter(updateTime.ApplySystemClockTolerance()); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_ShouldCreateFileWithByteOrderMark( + string path) + { + byte[] expectedBytes = [255, 254, 0, 0, 65, 0, 0, 0, 65, 0, 0, 0]; + + FileSystem.File.WriteAllText(path, "AA".AsSpan(), Encoding.UTF32); + + FileSystem.Should().HaveFile(path) + .Which.HasContent(expectedBytes); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_ShouldCreateFileWithText(string path, string contents) + { + FileSystem.File.WriteAllText(path, contents.AsSpan()); + + string result = FileSystem.File.ReadAllText(path); + result.Should().Be(contents); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_SpecialCharacters_ShouldReturnSameText(string path) + { + char[] specialCharacters = + [ + 'Ä', + 'Ö', + 'Ü', + 'ä', + 'ö', + 'ü', + 'ß', + ]; + foreach (char specialCharacter in specialCharacters) + { + string contents = "_" + specialCharacter; + FileSystem.File.WriteAllText(path, contents.AsSpan()); + + string result = FileSystem.File.ReadAllText(path); + + result.Should().Be(contents, + $"{contents} should be encoded and decoded identical."); + } + } + + [Theory] + [AutoData] + public void WriteAllText_Span_WhenDirectoryWithSameNameExists_ShouldThrowUnauthorizedAccessException( + string path) + { + FileSystem.Directory.CreateDirectory(path); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.WriteAllText(path, "".AsSpan()); + }); + + exception.Should().BeException( + hResult: -2147024891); + FileSystem.Should().HaveDirectory(path); + FileSystem.Should().NotHaveFile(path); + } + + [Theory] + [AutoData] + public void WriteAllText_Span_WhenFileIsHidden_ShouldThrowUnauthorizedAccessException_OnWindows( + string path, string contents) + { + Skip.IfNot(Test.RunsOnWindows); + + FileSystem.File.WriteAllText(path, null); + FileSystem.File.SetAttributes(path, FileAttributes.Hidden); + + Exception? exception = Record.Exception(() => + { + FileSystem.File.WriteAllText(path, contents.AsSpan()); + }); + + exception.Should().BeException(hResult: -2147024891); + } +#endif } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/FileStream/DisposeTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/FileStream/DisposeTests.cs index cb436b038..423e3c0b7 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/FileStream/DisposeTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/FileStream/DisposeTests.cs @@ -90,7 +90,9 @@ private static IEnumerable>> .GetAwaiter().GetResult(); #pragma warning disable MA0060 // ReSharper disable once MustUseReturnValue + #pragma warning disable CA2022 yield return fileStream => fileStream.Read(Array.Empty(), 0, 0); + #pragma warning restore CA2022 #pragma warning restore MA0060 yield return fileStream => fileStream.ReadAsync(Array.Empty(), 0, 0, TestContext.Current.CancellationToken) .GetAwaiter().GetResult(); diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Path/CombineTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Path/CombineTests.cs index 4972fb8a0..ff5a5c5f4 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Path/CombineTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Path/CombineTests.cs @@ -282,6 +282,212 @@ public void Combine_ParamPaths_OneEmpty_ShouldReturnCombinationOfOtherParts( { string expectedPath = FileSystem.Path.Combine(path1, path2, path3, path4); + string result1 = + FileSystem.Path.Combine(new[] + { + string.Empty, + path1, + path2, + path3, + path4, + }); + string result2 = + FileSystem.Path.Combine(new[] + { + path1, + string.Empty, + path2, + path3, + path4, + }); + string result3 = + FileSystem.Path.Combine(new[] + { + path1, + path2, + string.Empty, + path3, + path4, + }); + string result4 = + FileSystem.Path.Combine(new[] + { + path1, + path2, + path3, + string.Empty, + path4, + }); + string result5 = + FileSystem.Path.Combine(new[] + { + path1, + path2, + path3, + path4, + string.Empty, + }); + + result1.Should().Be(expectedPath); + result2.Should().Be(expectedPath); + result3.Should().Be(expectedPath); + result4.Should().Be(expectedPath); + result5.Should().Be(expectedPath); + } + + [Theory] + [AutoData] + public void Combine_ParamPaths_OneNull_ShouldThrowArgumentNullException( + string pathA, string pathB, string pathC, string pathD) + { + Exception? exception1 = Record.Exception(() => + FileSystem.Path.Combine(new[] + { + pathA, + pathB, + pathC, + pathD, + null!, + })); + Exception? exception2 = Record.Exception(() => + FileSystem.Path.Combine(new[] + { + null!, + pathA, + pathB, + pathC, + pathD, + })); + Exception? exception3 = Record.Exception(() => + FileSystem.Path.Combine(new[] + { + pathA, + null!, + pathB, + pathC, + pathD, + })); + Exception? exception4 = Record.Exception(() => + FileSystem.Path.Combine(new[] + { + pathA, + pathB, + null!, + pathC, + pathD, + })); + Exception? exception5 = Record.Exception(() => + FileSystem.Path.Combine(new[] + { + pathA, + pathB, + pathC, + null!, + pathD, + })); + + exception1.Should() + .BeException(paramName: "paths", hResult: -2147467261); + exception2.Should() + .BeException(paramName: "paths", hResult: -2147467261); + exception3.Should() + .BeException(paramName: "paths", hResult: -2147467261); + exception4.Should() + .BeException(paramName: "paths", hResult: -2147467261); + exception5.Should() + .BeException(paramName: "paths", hResult: -2147467261); + } + + [Theory] + [AutoData] + public void Combine_ParamPaths_Rooted_ShouldReturnLastRootedPath( + string path1, string path2, string path3, string path4, string path5) + { + path1 = FileSystem.Path.DirectorySeparatorChar + path1; + path2 = FileSystem.Path.DirectorySeparatorChar + path2; + path3 = FileSystem.Path.DirectorySeparatorChar + path3; + path4 = FileSystem.Path.DirectorySeparatorChar + path4; + path5 = FileSystem.Path.DirectorySeparatorChar + path5; + + string result = FileSystem.Path.Combine(new[] + { + path1, + path2, + path3, + path4, + path5, + }); + + result.Should().Be(path5); + } + + [Theory] + [InlineData("", "", "", "", "", "")] + [InlineData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/maeh/")] + [InlineData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "/maeh")] + [InlineData("foo/", "bar", "/baz", "/muh", "/maeh", "/maeh")] + [InlineData("foo", "/bar", "/baz", "/muh", "/maeh", "/maeh")] + [InlineData("foo", "/bar/", "baz/", "muh/", "maeh", "/bar/baz/muh/maeh")] + [InlineData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")] + [InlineData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")] + public void Combine_ParamPaths_ShouldReturnExpectedResult( + string path1, string path2, string path3, string path4, string path5, string expectedResult) + { + path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path5 = path5.Replace('/', FileSystem.Path.DirectorySeparatorChar); + expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar); + + string result = FileSystem.Path.Combine(new[] + { + path1, + path2, + path3, + path4, + path5, + }); + + result.Should().Be(expectedResult); + } + + [Theory] + [InlineAutoData] + [InlineAutoData(" ")] + [InlineAutoData("foo", " ")] + [InlineAutoData("foo", "bar", " ")] + [InlineAutoData("foo", "bar", "baz", " ")] + [InlineAutoData("foo", "bar", "baz", "muh", " ")] + public void Combine_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( + string path1, string path2, string path3, string path4, string path5) + { + string expectedPath = path1 + + FileSystem.Path.DirectorySeparatorChar + path2 + + FileSystem.Path.DirectorySeparatorChar + path3 + + FileSystem.Path.DirectorySeparatorChar + path4 + + FileSystem.Path.DirectorySeparatorChar + path5; + + string result = FileSystem.Path.Combine(new[] + { + path1, + path2, + path3, + path4, + path5, + }); + + result.Should().Be(expectedPath); + } + +#if FEATURE_PATH_SPAN + [Theory] + [AutoData] + public void Combine_ReadOnlySpanPaths_OneEmpty_ShouldReturnCombinationOfOtherParts( + string path1, string path2, string path3, string path4) + { + string expectedPath = FileSystem.Path.Combine(path1, path2, path3, path4); + string result1 = FileSystem.Path.Combine(string.Empty, path1, path2, path3, path4); string result2 = @@ -302,7 +508,7 @@ public void Combine_ParamPaths_OneEmpty_ShouldReturnCombinationOfOtherParts( [Theory] [AutoData] - public void Combine_ParamPaths_OneNull_ShouldThrowArgumentNullException( + public void Combine_ReadOnlySpanPaths_OneNull_ShouldThrowArgumentNullException( string pathA, string pathB, string pathC, string pathD) { Exception? exception1 = Record.Exception(() => @@ -330,7 +536,7 @@ public void Combine_ParamPaths_OneNull_ShouldThrowArgumentNullException( [Theory] [AutoData] - public void Combine_ParamPaths_Rooted_ShouldReturnLastRootedPath( + public void Combine_ReadOnlySpanPaths_Rooted_ShouldReturnLastRootedPath( string path1, string path2, string path3, string path4, string path5) { path1 = FileSystem.Path.DirectorySeparatorChar + path1; @@ -353,7 +559,7 @@ public void Combine_ParamPaths_Rooted_ShouldReturnLastRootedPath( [InlineData("foo", "/bar/", "baz/", "muh/", "maeh", "/bar/baz/muh/maeh")] [InlineData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")] [InlineData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")] - public void Combine_ParamPaths_ShouldReturnExpectedResult( + public void Combine_ReadOnlySpanPaths_ShouldReturnExpectedResult( string path1, string path2, string path3, string path4, string path5, string expectedResult) { path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); @@ -375,7 +581,7 @@ public void Combine_ParamPaths_ShouldReturnExpectedResult( [InlineAutoData("foo", "bar", " ")] [InlineAutoData("foo", "bar", "baz", " ")] [InlineAutoData("foo", "bar", "baz", "muh", " ")] - public void Combine_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( + public void Combine_ReadOnlySpanPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( string path1, string path2, string path3, string path4, string path5) { string expectedPath = path1 @@ -388,4 +594,5 @@ public void Combine_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar result.Should().Be(expectedPath); } +#endif } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Path/JoinTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Path/JoinTests.cs index de1afbfb6..b35bb8be2 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Path/JoinTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Path/JoinTests.cs @@ -212,7 +212,7 @@ public void Join_4Paths_Span_ShouldReturnPathsCombinedByDirectorySeparatorChar( [Fact] public void Join_ParamPaths_Empty_ShouldReturnEmptyString() { - string?[] paths = Array.Empty(); + string?[] paths = []; string result = FileSystem.Path.Join(paths); @@ -239,6 +239,126 @@ public void Join_ParamPaths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts( string expectedPath = $"{path1}{FileSystem.Path.DirectorySeparatorChar}{path2}{FileSystem.Path.DirectorySeparatorChar}{path3}{FileSystem.Path.DirectorySeparatorChar}{path4}"; + string result1 = + FileSystem.Path.Join(new[] + { + missingPath, + path1, + path2, + path3, + path4, + }); + string result2 = + FileSystem.Path.Join(new[] + { + path1, + missingPath, + path2, + path3, + path4, + }); + string result3 = + FileSystem.Path.Join(new[] + { + path1, + path2, + missingPath, + path3, + path4, + }); + string result4 = + FileSystem.Path.Join(new[] + { + path1, + path2, + path3, + missingPath, + path4, + }); + string result5 = + FileSystem.Path.Join(new[] + { + path1, + path2, + path3, + path4, + missingPath, + }); + + result1.Should().Be(expectedPath); + result2.Should().Be(expectedPath); + result3.Should().Be(expectedPath); + result4.Should().Be(expectedPath); + result5.Should().Be(expectedPath); + } + + [Theory] + [InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/foo//bar//baz//muh//maeh/")] + [InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "foo//bar//baz//muh/maeh")] + [InlineAutoData("foo/", "bar", "/baz", "/muh", "/maeh", "foo/bar/baz/muh/maeh")] + [InlineAutoData("foo", "/bar", "/baz", "/muh", "/maeh", "foo/bar/baz/muh/maeh")] + [InlineAutoData("foo", "/bar/", "baz/", "muh/", "maeh", "foo/bar/baz/muh/maeh")] + [InlineAutoData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")] + [InlineAutoData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")] + public void Join_ParamPaths_ShouldReturnExpectedResult( + string path1, string path2, string path3, string path4, string path5, string expectedResult) + { + path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path5 = path5.Replace('/', FileSystem.Path.DirectorySeparatorChar); + expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar); + + string result = FileSystem.Path.Join(new[] + { + path1, + path2, + path3, + path4, + path5, + }); + + result.Should().Be(expectedResult); + } + + [Theory] + [AutoData] + public void Join_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( + string path1, string path2, string path3, string path4, string path5) + { + string expectedResult = path1 + + FileSystem.Path.DirectorySeparatorChar + path2 + + FileSystem.Path.DirectorySeparatorChar + path3 + + FileSystem.Path.DirectorySeparatorChar + path4 + + FileSystem.Path.DirectorySeparatorChar + path5; + + string result = FileSystem.Path.Join(path1, path2, path3, path4, path5); + + result.Should().Be(expectedResult); + } + +#if FEATURE_PATH_SPAN + + [Fact] + public void Join_ReadOnlySpanPaths_Empty_ShouldReturnEmptyString() + { + ReadOnlySpan paths = Array.Empty(); + + string result = FileSystem.Path.Join(paths); + + result.Should().Be(string.Empty); + } + + [Theory] + [InlineAutoData((string?)null)] + [InlineAutoData("")] + public void Join_ReadOnlySpanPaths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts( + string? missingPath, string path1, string path2, string path3, string path4) + { + string expectedPath = + $"{path1}{FileSystem.Path.DirectorySeparatorChar}{path2}{FileSystem.Path.DirectorySeparatorChar}{path3}{FileSystem.Path.DirectorySeparatorChar}{path4}"; + string result1 = FileSystem.Path.Join(missingPath, path1, path2, path3, path4); string result2 = @@ -265,7 +385,7 @@ public void Join_ParamPaths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts( [InlineAutoData("foo", "/bar/", "baz/", "muh/", "maeh", "foo/bar/baz/muh/maeh")] [InlineAutoData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")] [InlineAutoData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")] - public void Join_ParamPaths_ShouldReturnExpectedResult( + public void Join_ReadOnlySpanPaths_ShouldReturnExpectedResult( string path1, string path2, string path3, string path4, string path5, string expectedResult) { path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); @@ -282,7 +402,7 @@ public void Join_ParamPaths_ShouldReturnExpectedResult( [Theory] [AutoData] - public void Join_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( + public void Join_ReadOnlySpanPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( string path1, string path2, string path3, string path4, string path5) { string expectedResult = path1 @@ -295,5 +415,6 @@ public void Join_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar( result.Should().Be(expectedResult); } +#endif } #endif diff --git a/Tests/Testably.Abstractions.Tests/RandomSystem/GuidTests.cs b/Tests/Testably.Abstractions.Tests/RandomSystem/GuidTests.cs index a1fe752d3..2db89a308 100644 --- a/Tests/Testably.Abstractions.Tests/RandomSystem/GuidTests.cs +++ b/Tests/Testably.Abstractions.Tests/RandomSystem/GuidTests.cs @@ -31,6 +31,36 @@ public void NewGuid_ShouldBeThreadSafeAndReturnUniqueItems() results.Should().OnlyHaveUniqueItems(); } +#if FEATURE_GUID_V7 + [Fact] + public void CreateVersion7_ShouldBeThreadSafeAndReturnUniqueItems() + { + ConcurrentBag results = []; + + Parallel.For(0, 100, _ => + { + results.Add(RandomSystem.Guid.CreateVersion7()); + }); + + results.Should().OnlyHaveUniqueItems(); + } +#endif + +#if FEATURE_GUID_V7 + [Fact] + public void CreateVersion7_WithOffset_ShouldBeThreadSafeAndReturnUniqueItems() + { + ConcurrentBag results = []; + + Parallel.For(0, 100, _ => + { + results.Add(RandomSystem.Guid.CreateVersion7(DateTimeOffset.UtcNow)); + }); + + results.Should().OnlyHaveUniqueItems(); + } +#endif + #if FEATURE_GUID_PARSE [Theory] [AutoData]