Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"CodeAnalysisEnd",
"CodeCoverage",
"Compile",
"DotNetFrameworkUnitTests",
"DotNetUnitTests",
"MutationComment",
"MutationTestPreparation",
Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="5.0.0-preview0012"/>
<PackageVersion Include="AutoFixture.Xunit3" Version="5.0.0-preview0012"/>
<PackageVersion Include="aweXpect" Version="0.17.0"/>
<PackageVersion Include="aweXpect" Version="0.20.0"/>
<PackageVersion Include="aweXpect.Testably" Version="0.3.0"/>
<PackageVersion Include="FluentAssertions" Version="7.0.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
Expand Down
5 changes: 2 additions & 3 deletions Examples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoFixture.Xunit2" />
<PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Xunit.SkippableFact" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void Delete(bool recursive)
recursive);

if (!_fileSystem.Storage.DeleteContainer(
_fileSystem.Storage.GetLocation(FullName), recursive))
_fileSystem.Storage.GetLocation(FullName), FileSystemTypes.Directory, recursive))
{
throw ExceptionFactory.DirectoryNotFound(FullName);
}
Expand All @@ -125,7 +125,7 @@ public override void Delete()
using IDisposable registration = _fileSystem.StatisticsRegistration
.DirectoryInfo.RegisterPathMethod(Location.FullPath, nameof(Delete));

if (!_fileSystem.Storage.DeleteContainer(Location))
if (!_fileSystem.Storage.DeleteContainer(Location, FileSystemTypes.Directory))
{
throw ExceptionFactory.DirectoryNotFound(Location.FullPath);
}
Expand Down
3 changes: 2 additions & 1 deletion Source/Testably.Abstractions.Testing/FileSystem/FileMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ public void Delete(string path)

_fileSystem.Storage.DeleteContainer(
_fileSystem.Storage.GetLocation(
path.EnsureValidFormat(_fileSystem)));
path.EnsureValidFormat(_fileSystem)),
FileSystemTypes.File);
}

/// <inheritdoc cref="IFile.Encrypt(string)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,8 @@ private void OnClose()
if (_options.HasFlag(FileOptions.DeleteOnClose))
{
_fileSystem.Storage.DeleteContainer(
_fileSystem.Storage.GetLocation(Name));
_fileSystem.Storage.GetLocation(Name),
FileSystemTypes.File);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ public void CreateAsSymbolicLink(string pathToTarget)

FullName.EnsureValidFormat(_fileSystem);
pathToTarget.ThrowCommonExceptionsIfPathToTargetIsInvalid(_fileSystem);
if (_fileSystem.Storage.TryAddContainer(Location, InMemoryContainer.NewFile,
if (_fileSystem.Storage.TryAddContainer(Location,
FileSystemType == FileSystemTypes.Directory
? InMemoryContainer.NewDirectory
: InMemoryContainer.NewFile,
out IStorageContainer? container))
{
Container = container;
Expand Down Expand Up @@ -136,7 +139,7 @@ public virtual void Delete()
{
using IDisposable registration = RegisterPathMethod(nameof(Delete));

_fileSystem.Storage.DeleteContainer(Location);
_fileSystem.Storage.DeleteContainer(Location, FileSystemType);
ResetCache(!_fileSystem.Execute.IsNetFramework);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ internal static ArgumentException InvalidAccessCombination(
#endif
};

internal static IOException InvalidDirectoryName(string path)
=> new($"The directory name is invalid: '{path}'")
{
#if FEATURE_EXCEPTION_HRESULT
HResult = -2147024629,
#endif
};

internal static ArgumentException InvalidDriveName(string paramName = "driveName")
=> new(
"Drive name must be a root directory (i.e. 'C:\\') or a drive letter ('C').",
Expand Down
3 changes: 2 additions & 1 deletion Source/Testably.Abstractions.Testing/Storage/IStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ internal interface IStorage
/// Deletes the container stored at <paramref name="location" />.
/// </summary>
/// <param name="location">The location at which the container is located.</param>
/// <param name="expectedType">The expected <see cref="FileSystemTypes" /> on the <paramref name="location" />.</param>
/// <param name="recursive">(optional) Is set, also child-containers are deleted recursively.</param>
/// <returns><see langword="true" />, if the container was found and deleted, otherwise <see langword="false" />.</returns>
bool DeleteContainer(IStorageLocation location, bool recursive = false);
bool DeleteContainer(IStorageLocation location, FileSystemTypes expectedType, bool recursive = false);

/// <summary>
/// Enumerate the locations of files or directories stored under <paramref name="location" />.
Expand Down
33 changes: 30 additions & 3 deletions Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@ public InMemoryStorage(MockFileSystem fileSystem)
}
}

/// <inheritdoc cref="IStorage.DeleteContainer(IStorageLocation, bool)" />
public bool DeleteContainer(IStorageLocation location, bool recursive = false)
/// <inheritdoc cref="IStorage.DeleteContainer(IStorageLocation, FileSystemTypes, bool)" />
public bool DeleteContainer(
IStorageLocation location,
FileSystemTypes expectedType,
bool recursive = false)
{
if (!_containers.TryGetValue(location, out IStorageContainer? container))
{
Expand All @@ -136,6 +139,8 @@ public bool DeleteContainer(IStorageLocation location, bool recursive = false)
return false;
}

ValidateContainerType(container.Type, expectedType, _fileSystem.Execute, location);

if (container.Type == FileSystemTypes.Directory)
{
IEnumerable<IStorageLocation> children =
Expand All @@ -144,7 +149,7 @@ public bool DeleteContainer(IStorageLocation location, bool recursive = false)
{
foreach (IStorageLocation key in children)
{
DeleteContainer(key, recursive: true);
DeleteContainer(key, FileSystemTypes.DirectoryOrFile, recursive: true);
}
}
else if (children.Any())
Expand Down Expand Up @@ -1054,6 +1059,28 @@ private static NotifyFilters ToNotifyFilters(FileSystemTypes type)
? NotifyFilters.DirectoryName
: NotifyFilters.FileName;

private static void ValidateContainerType(
FileSystemTypes actualType,
FileSystemTypes expectedType,
Execute execute,
IStorageLocation location)
{
if (actualType != expectedType)
{
if (expectedType == FileSystemTypes.Directory)
{
throw execute.IsWindows
? ExceptionFactory.InvalidDirectoryName(location.FullPath)
: ExceptionFactory.DirectoryNotFound(location.FullPath);
}

if (expectedType == FileSystemTypes.File)
{
throw ExceptionFactory.AccessToPathDenied(location.FullPath);
}
}
}

private static void ValidateExpression(string expression)
{
if (expression.Contains('\0', StringComparison.Ordinal))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,38 @@ public void Delete_ShouldDeleteDirectory(string directoryName)
result.Should().NotExist();
}

[Theory]
[AutoData]
public async Task Delete_WhenFile_ShouldThrowIOException(
string directoryName)
{
FileSystem.File.WriteAllText(directoryName, "");
string expectedPath = FileSystem.Path.Combine(BasePath, directoryName);

void Act()
{
FileSystem.Directory.Delete(directoryName, true);
}

if (Test.IsNetFramework)
{
await That(Act).Throws<IOException>()
.WithMessage("*The directory name is invalid*").AsWildcard();
}
else if (Test.RunsOnWindows)
{
await That(Act).Throws<IOException>()
.WithMessage($"*The directory name is invalid*{expectedPath}*").AsWildcard().And
.WithHResult(-2147024629);
}
else
{
await That(Act).Throws<DirectoryNotFoundException>()
.WithMessage($"*Could not find a part of the path*{expectedPath}*").AsWildcard().And
.WithHResult(-2147024893);
}
}

[Theory]
[AutoData]
public void Delete_WithSimilarNamedFile_ShouldOnlyDeleteDirectory(
Expand Down
16 changes: 16 additions & 0 deletions Tests/Testably.Abstractions.Tests/FileSystem/File/DeleteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ public void Delete_MissingFile_ShouldDoNothing(
exception.Should().BeNull();
}

[Theory]
[AutoData]
public void Delete_WhenDirectory_ShouldThrowUnauthorizedAccessException(
string fileName)
{
FileSystem.Directory.CreateDirectory(fileName);
string expectedPath = FileSystem.Path.Combine(BasePath, fileName);
Exception? exception = Record.Exception(() =>
{
FileSystem.File.Delete(fileName);
});

exception.Should().BeException<UnauthorizedAccessException>($"'{expectedPath}'",
hResult: -2147024891);
}

[Theory]
[AutoData]
public void Delete_WithOpenFile_ShouldThrowIOException_OnWindows(string filename)
Expand Down
4 changes: 4 additions & 0 deletions Tests/Testably.Abstractions.Tests/TestHelpers/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
global using FluentAssertions;
global using System;
global using System.IO.Abstractions;
global using System.Threading.Tasks;
global using Testably.Abstractions.FileSystem;
global using Testably.Abstractions.FluentAssertions;
global using Testably.Abstractions.Testing;
global using Testably.Abstractions.TestHelpers;
global using Testably.Abstractions.Tests.TestHelpers;
global using Xunit;
global using aweXpect;
global using static aweXpect.Expect;
global using Skip = Testably.Abstractions.TestHelpers.Skip;
#if NET48
global using Testably.Abstractions.Polyfills;
#else
Expand Down
Loading