Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement GetTempFileName for simulated Path #570

Merged
merged 4 commits into from
Apr 20, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ internal partial class Execute
{
private sealed class MacPath(MockFileSystem fileSystem) : LinuxPath(fileSystem)
{
private readonly MockFileSystem _fileSystem = fileSystem;
private string? _tempPath;

/// <inheritdoc cref="IPath.GetTempPath()" />
public override string GetTempPath()
{
_tempPath ??= $"/var/folders/{RandomString(2)}/{RandomString(2)}_{RandomString(27)}/T/";
_tempPath ??=
$"/var/folders/{RandomString(_fileSystem, 2)}/{RandomString(_fileSystem, 2)}_{RandomString(_fileSystem, 27)}/T/";
return _tempPath;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public string GetRelativePath(string relativeTo, string path)
"Insecure temporary file creation methods should not be used. Use `Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())` instead.")]
#endif
public string GetTempFileName()
=> System.IO.Path.GetTempFileName();
=> CreateTempFileName(fileSystem);

/// <inheritdoc cref="Path.GetTempPath()" />
public string GetTempPath()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
#if FEATURE_FILESYSTEM_NET7
using Testably.Abstractions.Testing.Storage;
Expand Down Expand Up @@ -284,7 +283,7 @@ public ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)

/// <inheritdoc cref="IPath.GetRandomFileName()" />
public string GetRandomFileName()
=> $"{RandomString(8)}.{RandomString(3)}";
=> $"{RandomString(fileSystem, 8)}.{RandomString(fileSystem, 3)}";

#if FEATURE_PATH_RELATIVE
/// <inheritdoc cref="IPath.GetRelativePath(string, string)" />
Expand All @@ -306,7 +305,7 @@ public string GetRelativePath(string relativeTo, string path)
"Insecure temporary file creation methods should not be used. Use `Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())` instead.")]
#endif
public string GetTempFileName()
=> System.IO.Path.GetTempFileName();
=> CreateTempFileName(fileSystem);

/// <inheritdoc cref="IPath.GetTempPath()" />
public abstract string GetTempPath();
Expand Down Expand Up @@ -573,13 +572,6 @@ private string JoinInternal(string?[] paths)

protected abstract string NormalizeDirectorySeparators(string path);

protected string RandomString(int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[fileSystem.RandomSystem.Random.Shared.Next(s.Length)]).ToArray());
}

/// <summary>
/// Remove relative segments from the given path (without combining with a root).
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions Source/Testably.Abstractions.Testing/Helpers/Execute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

namespace Testably.Abstractions.Testing.Helpers;
Expand Down Expand Up @@ -78,4 +80,32 @@ internal Execute(MockFileSystem fileSystem)
: StringComparison.OrdinalIgnoreCase;
Path = new NativePath(fileSystem);
}

internal static string CreateTempFileName(MockFileSystem fileSystem)
{
int i = 0;
string tempPath = fileSystem.Path.GetTempPath();
fileSystem.Directory.CreateDirectory(tempPath);
while (true)
{
string fileName = $"{RandomString(fileSystem, 8)}.tmp";
string path = string.Concat(tempPath, fileName);
try
{
fileSystem.File.Open(path, FileMode.CreateNew, FileAccess.Write).Dispose();
return path;
}
catch (IOException) when (i < 100)
{
i++; // Don't let unforeseen circumstances cause us to loop forever
}
}
}

internal static string RandomString(MockFileSystem fileSystem, int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[fileSystem.RandomSystem.Random.Shared.Next(s.Length)]).ToArray());
}
}
17 changes: 16 additions & 1 deletion Source/Testably.Abstractions.Testing/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using Testably.Abstractions.Testing.FileSystem;
using Testably.Abstractions.Testing.Helpers;
using Testably.Abstractions.Testing.RandomSystem;
using Testably.Abstractions.Testing.Statistics;
using Testably.Abstractions.Testing.Storage;

Expand Down Expand Up @@ -107,7 +108,7 @@ internal MockFileSystem(Action<Initialization> initializationCallback)
: new Execute(this, SimulationMode);
StatisticsRegistration = new FileSystemStatistics(this);
using IDisposable release = StatisticsRegistration.Ignore();
RandomSystem = new MockRandomSystem();
RandomSystem = new MockRandomSystem(initialization.RandomProvider ?? RandomProvider.Default());
TimeSystem = new MockTimeSystem(TimeProvider.Now());
_pathMock = new PathMock(this);
_storage = new InMemoryStorage(this);
Expand Down Expand Up @@ -231,6 +232,11 @@ internal class Initialization
/// </summary>
internal string? CurrentDirectory { get; private set; }

/// <summary>
/// The <see cref="IRandomProvider" /> for the <see cref="RandomSystem" />.
/// </summary>
internal IRandomProvider? RandomProvider { get; private set; }

/// <summary>
/// The simulated operating system.
/// </summary>
Expand Down Expand Up @@ -262,5 +268,14 @@ internal Initialization UseCurrentDirectory()
CurrentDirectory = System.IO.Directory.GetCurrentDirectory();
return this;
}

/// <summary>
/// Use the given <paramref name="randomProvider" /> for the <see cref="RandomSystem" />.
/// </summary>
internal Initialization UseRandomProvider(IRandomProvider randomProvider)
{
RandomProvider = randomProvider;
return this;
}
}
}
6 changes: 3 additions & 3 deletions Source/Testably.Abstractions.Testing/MockRandomSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ public MockRandomSystem() : this(Testing.RandomProvider.Default())
}

/// <summary>
/// Initializes the <see cref="MockRandomSystem" /> with the specified <paramref name="randomProviderProvider" />.
/// Initializes the <see cref="MockRandomSystem" /> with the specified <paramref name="randomProvider" />.
/// </summary>
public MockRandomSystem(IRandomProvider randomProviderProvider)
public MockRandomSystem(IRandomProvider randomProvider)
{
RandomProvider = randomProviderProvider;
RandomProvider = randomProvider;
_guidMock = new GuidMock(this);
_randomFactoryMock = new RandomFactoryMock(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ namespace Testably.Abstractions.Testing
public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem
{
public MockRandomSystem() { }
public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProviderProvider) { }
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ namespace Testably.Abstractions.Testing
public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem
{
public MockRandomSystem() { }
public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProviderProvider) { }
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ namespace Testably.Abstractions.Testing
public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem
{
public MockRandomSystem() { }
public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProviderProvider) { }
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ namespace Testably.Abstractions.Testing
public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem
{
public MockRandomSystem() { }
public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProviderProvider) { }
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ namespace Testably.Abstractions.Testing
public sealed class MockRandomSystem : Testably.Abstractions.IRandomSystem
{
public MockRandomSystem() { }
public MockRandomSystem(Testably.Abstractions.Testing.RandomSystem.IRandomProvider randomProviderProvider) { }
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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ private bool IncludeSimulatedTests(ClassModel @class)
"GetFullPathTests",
"GetPathRootTests",
"GetRandomFileNameTests",
"GetTempFileNameTests",
"GetTempPathTests",
"HasExtensionTests",
"IsPathRootedTests",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.IO;

namespace Testably.Abstractions.Testing.Tests.FileSystem;

public sealed class PathMockTests
{
[Theory]
[InlineAutoData(SimulationMode.Native)]
[InlineAutoData(SimulationMode.Linux)]
[InlineAutoData(SimulationMode.MacOS)]
[InlineAutoData(SimulationMode.Windows)]
public void GetTempFileName_WithCollisions_ShouldThrowIOException(
SimulationMode simulationMode, int fixedRandomValue)
{
MockFileSystem fileSystem = new(i => i
.SimulatingOperatingSystem(simulationMode)
.UseRandomProvider(RandomProvider.Generate(
intGenerator: new RandomProvider.Generator<int>(() => fixedRandomValue))));
string result = fileSystem.Path.GetTempFileName();

Exception? exception = Record.Exception(() =>
{
_ = fileSystem.Path.GetTempFileName();
});

exception.Should().BeOfType<IOException>();
fileSystem.File.Exists(result).Should().BeTrue();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Testably.Abstractions.Testing.Tests.TestHelpers;
#if NET6_0_OR_GREATER
#endif
Expand Down Expand Up @@ -124,6 +126,22 @@ public void UseCurrentDirectory_WithPath_ShouldUsePathCurrentDirectory(string pa
sut.CurrentDirectory.Should().Be(path);
}

[Theory]
[AutoData]
public void UseRandomProvider_ShouldUseFixedRandomValue(int fixedRandomValue)
{
MockFileSystem fileSystem = new(i => i
.UseRandomProvider(RandomProvider.Generate(
intGenerator: new RandomProvider.Generator<int>(() => fixedRandomValue))));

List<int> results = Enumerable.Range(1, 100)
.Select(_ => fileSystem.RandomSystem.Random.New().Next())
.ToList();
results.Add(fileSystem.RandomSystem.Random.Shared.Next());

results.Should().AllBeEquivalentTo(fixedRandomValue);
}

#region Helpers

public static TheoryData<SimulationMode> ValidOperatingSystems()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Testably.Abstractions.Tests.FileSystem.Path;

// ReSharper disable once PartialTypeWithSinglePart
public abstract partial class GetTempFileNameTests<TFileSystem>
: FileSystemTestBase<TFileSystem>
where TFileSystem : IFileSystem
{
[SkippableFact]
public void GetTempFileName_ShouldBeInTempPath()
{
string tempPath = FileSystem.Path.GetTempPath();

string result = FileSystem.Path.GetTempFileName();

result.Should().StartWith(tempPath);
}

[SkippableFact]
public void GetTempFileName_ShouldExist()
{
string result = FileSystem.Path.GetTempFileName();

FileSystem.File.Exists(result).Should().BeTrue();
}
}
Loading