Skip to content

Commit

Permalink
refactor: improve mutation score (#547)
Browse files Browse the repository at this point in the history
Refactor some implementation details, to catch uncaught errors (see Stryker.NET):
- Allow `null` in `IStorage.GetOrAddDrive` to simplify the implementation
- Add tests for `Notification.ExecuteWhileWaiting`
- Extract getting the name for an `AssemblyName` to an extension method to allow testing it
- Avoid including the default value for `int` in the choices in the random tests
- Extract getting a shared `IRandom` instance from the `IFileSystem` to an extension method to allow testing it
- Fix the test for changing a disposed timer in .NET 8 or greater (it returns false instead of throwing an `ObjectDisposedException`)
  • Loading branch information
vbreuss authored Apr 7, 2024
1 parent e4c5bb5 commit c8d5d18
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem file
EnumerationOptionsHelper.FromSearchOption(searchOption);

string[] resourcePaths = assembly.GetManifestResourceNames();
string assemblyNamePrefix = $"{assembly.GetName().Name ?? ""}.";
string assemblyNamePrefix = $"{assembly.GetName().GetNameOrDefault()}.";

if (relativePath != null)
{
Expand Down Expand Up @@ -89,13 +89,11 @@ public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem file
fileName = fileName.Substring(relativePath.Length);
}

#pragma warning disable CA2249 // string.Contains with char is not supported on netstandard2.0
if (!enumerationOptions.RecurseSubdirectories &&
fileName.IndexOf(Path.DirectorySeparatorChar) >= 0)
fileName.Contains(Path.DirectorySeparatorChar, StringComparison.Ordinal))
{
continue;
}
#pragma warning restore CA2249

if (EnumerationOptionsHelper.MatchesPattern(
fileSystem.ExecuteOrDefault(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Reflection;

namespace Testably.Abstractions.Testing.Helpers;

internal static class AssemblyNameExtensions
{
/// <summary>
/// Returns the name of the <paramref name="assemblyName" /> or the <paramref name="defaultName" />, if it is
/// <see langword="null" />.
/// </summary>
internal static string GetNameOrDefault(this AssemblyName assemblyName, string defaultName = "")
{
return assemblyName.Name ?? defaultName;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.IO;
using Testably.Abstractions.RandomSystem;
using Testably.Abstractions.Testing.Statistics;
using Testably.Abstractions.Testing.Storage;

Expand All @@ -8,7 +9,8 @@ namespace Testably.Abstractions.Testing.Helpers;
internal static class FileSystemExtensions
{
/// <summary>
/// Ignores all registrations on the <see cref="MockFileSystem.Statistics" /> until the return value is disposed.
/// Returns the <see cref="Execute" /> from the <paramref name="fileSystem" />, if it is a
/// <see cref="MockFileSystem" />, otherwise a default <see cref="Execute" />.
/// </summary>
internal static Execute ExecuteOrDefault(this IFileSystem fileSystem)
{
Expand Down Expand Up @@ -121,4 +123,18 @@ internal static IDisposable IgnoreStatistics(this IFileSystem fileSystem)

return new NoOpDisposable();
}

/// <summary>
/// Returns the shared <see cref="IRandom" /> instance from the <paramref name="fileSystem" />, if it is a
/// <see cref="MockFileSystem" />, otherwise <see cref="RandomFactory.Shared" />.
/// </summary>
internal static IRandom RandomOrDefault(this IFileSystem fileSystem)
{
if (fileSystem is MockFileSystem mockFileSystem)
{
return mockFileSystem.RandomSystem.Random.Shared;
}

return RandomFactory.Shared;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ public IFileSystemInitializer<TFileSystem> With(
public IFileSystemFileInitializer<TFileSystem> WithAFile(string? extension = null)
{
using IDisposable release = FileSystem.IgnoreStatistics();
IRandom random = (FileSystem as MockFileSystem)?
.RandomSystem.Random.Shared ?? RandomFactory.Shared;
IRandom random = FileSystem.RandomOrDefault();
string fileName;
do
{
Expand All @@ -88,8 +87,7 @@ public IFileSystemFileInitializer<TFileSystem> WithAFile(string? extension = nul
public IFileSystemDirectoryInitializer<TFileSystem> WithASubdirectory()
{
using IDisposable release = FileSystem.IgnoreStatistics();
IRandom random = (FileSystem as MockFileSystem)?
.RandomSystem.Random.Shared ?? RandomFactory.Shared;
IRandom random = FileSystem.RandomOrDefault();
string directoryName;
do
{
Expand Down Expand Up @@ -148,13 +146,10 @@ private IDirectoryInfo WithDirectory(DirectoryDescription directory)

FileSystem.Directory.CreateDirectory(directoryInfo.FullName);

if (directory.Children.Length > 0)
FileSystemInitializer<TFileSystem> subdirectoryInitializer = new(this, directoryInfo);
foreach (FileSystemInfoDescription children in directory.Children)
{
FileSystemInitializer<TFileSystem> subdirectoryInitializer = new(this, directoryInfo);
foreach (FileSystemInfoDescription children in directory.Children)
{
subdirectoryInitializer.WithFileOrDirectory(children);
}
subdirectoryInitializer.WithFileOrDirectory(children);
}

_initializedFileSystemInfos.Add(
Expand Down
6 changes: 1 addition & 5 deletions Source/Testably.Abstractions.Testing/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,7 @@ private void InitializeFileSystem(Initialization initialization)
}

string? root = Execute.Path.GetPathRoot(Directory.GetCurrentDirectory());
if (root != null &&
root[0] != _storage.MainDrive.Name[0])
{
Storage.GetOrAddDrive(root);
}
Storage.GetOrAddDrive(root);
}
catch (IOException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public void Shuffle<T>(Span<T> values)
{
int n = values.Length;

for (int i = 0; i < n - 1; i++)
for (int i = 0; i < n; i++)
{
int j = Next(i, n);

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 @@ -112,7 +112,8 @@ IEnumerable<IStorageLocation> EnumerateLocations(
/// <summary>
/// Returns the drives that are present.
/// </summary>
IStorageDrive GetOrAddDrive(string driveName);
[return: NotNullIfNotNull("driveName")]
IStorageDrive? GetOrAddDrive(string? driveName);

/// <summary>
/// Returns an existing container at <paramref name="location" />.<br />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,14 @@ public IEnumerable<IStorageDrive> GetDrives()
}

/// <inheritdoc cref="IStorage.GetOrAddDrive(string)" />
public IStorageDrive GetOrAddDrive(string driveName)
[return: NotNullIfNotNull("driveName")]
public IStorageDrive? GetOrAddDrive(string? driveName)
{
if (driveName == null)
{
return null;
}

DriveInfoMock drive = DriveInfoMock.New(driveName, _fileSystem);
return _drives.GetOrAdd(drive.GetName(), _ => drive);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Reflection;
using Testably.Abstractions.Testing.Helpers;

namespace Testably.Abstractions.Testing.Tests.Helpers;

public class AssemblyNameExtensionsTests
{
[Fact]
public void GetNameOrDefault_ExecutingAssembly_ShouldReturnCorrectString()
{
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();

string result = assemblyName.GetNameOrDefault();

result.Should().Be("Testably.Abstractions.Testing.Tests");
}

[Fact]
public void GetNameOrDefault_NullName_ShouldReturnEmptyString()
{
AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
assemblyName.Name = null;

string result = assemblyName.GetNameOrDefault();

result.Should().Be("");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Testably.Abstractions.Testing.Helpers;
using Testably.Abstractions.RandomSystem;
using Testably.Abstractions.Testing.Helpers;

namespace Testably.Abstractions.Testing.Tests.Helpers;

Expand All @@ -22,4 +23,26 @@ public void GetMoveLocation_LocationNotUnderSource_ShouldThrowNotSupportedExcept
.Should().Contain($"'{sut.Path.GetFullPath(location)}'")
.And.Contain($"'{sut.Path.GetFullPath(source)}'");
}

[Fact]
public void RandomOrDefault_WithMockFileSystem_ShouldUseRandomFromRandomSystem()
{
MockFileSystem fileSystem = new();
IFileSystem sut = fileSystem;

IRandom result = sut.RandomOrDefault();

result.Should().Be(fileSystem.RandomSystem.Random.Shared);
}

[Fact]
public void RandomOrDefault_WithRealFileSystem_ShouldUseSharedRandom()
{
RealFileSystem fileSystem = new();
IFileSystem sut = fileSystem;

IRandom result = sut.RandomOrDefault();

result.Should().Be(RandomFactory.Shared);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ namespace Testably.Abstractions.Testing.Tests;

public class MockFileSystemInitializationTests
{
[Fact]
[SkippableFact]
public void MockFileSystem_WhenSimulatingLinux_ShouldBeLinux()
{
Skip.IfNot(Test.RunsOnLinux,
"TODO: Enable again, once the Path implementation is sufficiently complete!");

MockFileSystem sut = new(o => o
.SimulatingOperatingSystem(OSPlatform.Linux));

Expand All @@ -20,9 +23,12 @@ public void MockFileSystem_WhenSimulatingLinux_ShouldBeLinux()
sut.Execute.IsNetFramework.Should().BeFalse();
}

[Fact]
[SkippableFact]
public void MockFileSystem_WhenSimulatingOSX_ShouldBeMac()
{
Skip.IfNot(Test.RunsOnMac,
"TODO: Enable again, once the Path implementation is sufficiently complete!");

MockFileSystem sut = new(o => o
.SimulatingOperatingSystem(OSPlatform.OSX));

Expand Down
39 changes: 39 additions & 0 deletions Tests/Testably.Abstractions.Testing.Tests/NotificationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,43 @@ public void Execute_WithReturnValue_ShouldBeExecutedAndReturnValue(
actualResult.Should().Be(result);
isExecuted.Should().BeTrue();
}

[Fact]
public void ExecuteWhileWaiting_ShouldExecuteCallback()
{
MockTimeSystem timeSystem = new();
bool isExecuted = false;

timeSystem.On
.ThreadSleep()
.ExecuteWhileWaiting(() =>
{
isExecuted = true;
timeSystem.Thread.Sleep(10);
})
.Wait();

isExecuted.Should().BeTrue();
}

[Theory]
[AutoData]
public void ExecuteWhileWaiting_WithReturnValue_ShouldExecuteCallback(int result)
{
MockTimeSystem timeSystem = new();
bool isExecuted = false;

int actualResult = timeSystem.On
.ThreadSleep()
.ExecuteWhileWaiting(() =>
{
isExecuted = true;
timeSystem.Thread.Sleep(10);
return result;
})
.Wait();

actualResult.Should().Be(result);
isExecuted.Should().BeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ public void GetDrive_NullOrWhitespace_ShouldReturnNull(string? driveName)
result.Should().BeNull();
}

[Fact]
public void GetOrAddDrive_Null_ShouldReturnNull()
{
IStorageDrive? result = Storage.GetOrAddDrive(driveName: null);

result.Should().BeNull();
}

[Fact]
public void GetOrCreateContainer_WithMetadata_ShouldBeKept()
{
Expand Down
Loading

0 comments on commit c8d5d18

Please sign in to comment.