diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs
index d8dca45df..c0494badd 100644
--- a/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs
+++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs
@@ -4,12 +4,14 @@ internal partial class Execute
{
private sealed class MacPath(MockFileSystem fileSystem) : LinuxPath(fileSystem)
{
+ private readonly MockFileSystem _fileSystem = fileSystem;
private string? _tempPath;
///
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;
}
}
diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs
index 8f49b0519..f5463a80f 100644
--- a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs
+++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs
@@ -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);
///
public string GetTempPath()
diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs
index 3d2683235..9eba6c1d3 100644
--- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs
+++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs
@@ -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;
@@ -284,7 +283,7 @@ public ReadOnlySpan GetPathRoot(ReadOnlySpan path)
///
public string GetRandomFileName()
- => $"{RandomString(8)}.{RandomString(3)}";
+ => $"{RandomString(fileSystem, 8)}.{RandomString(fileSystem, 3)}";
#if FEATURE_PATH_RELATIVE
///
@@ -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);
///
public abstract string GetTempPath();
@@ -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());
- }
-
///
/// Remove relative segments from the given path (without combining with a root).
///
diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.cs
index 0ed8ec070..3bedf516b 100644
--- a/Source/Testably.Abstractions.Testing/Helpers/Execute.cs
+++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.cs
@@ -1,4 +1,6 @@
using System;
+using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
namespace Testably.Abstractions.Testing.Helpers;
@@ -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());
+ }
}
diff --git a/Source/Testably.Abstractions.Testing/MockFileSystem.cs b/Source/Testably.Abstractions.Testing/MockFileSystem.cs
index c02ab4dba..a33520a48 100644
--- a/Source/Testably.Abstractions.Testing/MockFileSystem.cs
+++ b/Source/Testably.Abstractions.Testing/MockFileSystem.cs
@@ -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;
@@ -107,7 +108,7 @@ internal MockFileSystem(Action 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);
@@ -231,6 +232,11 @@ internal class Initialization
///
internal string? CurrentDirectory { get; private set; }
+ ///
+ /// The for the .
+ ///
+ internal IRandomProvider? RandomProvider { get; private set; }
+
///
/// The simulated operating system.
///
@@ -262,5 +268,14 @@ internal Initialization UseCurrentDirectory()
CurrentDirectory = System.IO.Directory.GetCurrentDirectory();
return this;
}
+
+ ///
+ /// Use the given for the .
+ ///
+ internal Initialization UseRandomProvider(IRandomProvider randomProvider)
+ {
+ RandomProvider = randomProvider;
+ return this;
+ }
}
}
diff --git a/Source/Testably.Abstractions.Testing/MockRandomSystem.cs b/Source/Testably.Abstractions.Testing/MockRandomSystem.cs
index 3e200504b..3cb5c07e8 100644
--- a/Source/Testably.Abstractions.Testing/MockRandomSystem.cs
+++ b/Source/Testably.Abstractions.Testing/MockRandomSystem.cs
@@ -26,11 +26,11 @@ public MockRandomSystem() : this(Testing.RandomProvider.Default())
}
///
- /// Initializes the with the specified .
+ /// Initializes the with the specified .
///
- public MockRandomSystem(IRandomProvider randomProviderProvider)
+ public MockRandomSystem(IRandomProvider randomProvider)
{
- RandomProvider = randomProviderProvider;
+ RandomProvider = randomProvider;
_guidMock = new GuidMock(this);
_randomFactoryMock = new RandomFactoryMock(this);
}
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
index d40ddc537..2c6aaeeca 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
@@ -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; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net7.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net7.0.txt
index bf1e5c5f3..cf20f78ea 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net7.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net7.0.txt
@@ -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; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
index 0ca789e67..d7c837710 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
@@ -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; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
index 7c3ac4cb6..48435f5b5 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
@@ -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; }
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
index 06a0a60e9..c3e4957dd 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
@@ -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; }
diff --git a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs
index a514bce74..fea21b4e1 100644
--- a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs
+++ b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs
@@ -272,6 +272,7 @@ private bool IncludeSimulatedTests(ClassModel @class)
"GetFullPathTests",
"GetPathRootTests",
"GetRandomFileNameTests",
+ "GetTempFileNameTests",
"GetTempPathTests",
"HasExtensionTests",
"IsPathRootedTests",
diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystem/PathMockTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/PathMockTests.cs
new file mode 100644
index 000000000..0b261da4f
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/PathMockTests.cs
@@ -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(() => fixedRandomValue))));
+ string result = fileSystem.Path.GetTempFileName();
+
+ Exception? exception = Record.Exception(() =>
+ {
+ _ = fileSystem.Path.GetTempFileName();
+ });
+
+ exception.Should().BeOfType();
+ fileSystem.File.Exists(result).Should().BeTrue();
+ }
+}
diff --git a/Tests/Testably.Abstractions.Testing.Tests/MockFileSystemInitializationTests.cs b/Tests/Testably.Abstractions.Testing.Tests/MockFileSystemInitializationTests.cs
index 8ca2b3a29..9de14321c 100644
--- a/Tests/Testably.Abstractions.Testing.Tests/MockFileSystemInitializationTests.cs
+++ b/Tests/Testably.Abstractions.Testing.Tests/MockFileSystemInitializationTests.cs
@@ -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
@@ -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(() => fixedRandomValue))));
+
+ List 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 ValidOperatingSystems()
diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Path/GetTempFileNameTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Path/GetTempFileNameTests.cs
new file mode 100644
index 000000000..c49cd568e
--- /dev/null
+++ b/Tests/Testably.Abstractions.Tests/FileSystem/Path/GetTempFileNameTests.cs
@@ -0,0 +1,25 @@
+namespace Testably.Abstractions.Tests.FileSystem.Path;
+
+// ReSharper disable once PartialTypeWithSinglePart
+public abstract partial class GetTempFileNameTests
+ : FileSystemTestBase
+ 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();
+ }
+}