Skip to content

Commit ecb07a5

Browse files
schibu007Samuel Bernhard
andauthored
feat: speed up MockFileSystem constructor (#698)
Co-authored-by: Samuel Bernhard <sbernhard@hamilton.ch>
1 parent 5dd02b7 commit ecb07a5

5 files changed

Lines changed: 56 additions & 13 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using BenchmarkDotNet.Attributes;
2+
using System.Collections.Generic;
3+
using System.IO.Abstractions.TestingHelpers;
4+
using System.Linq;
5+
using XFS = System.IO.Abstractions.TestingHelpers.MockUnixSupport;
6+
7+
namespace System.IO.Abstractions.Benchmarks
8+
{
9+
[RPlotExporter]
10+
[MemoryDiagnoser]
11+
public class MockFileSystemBenchmarks
12+
{
13+
private readonly Dictionary<string, MockFileData> testData = CreateTestData();
14+
15+
private static Dictionary<string, MockFileData> CreateTestData()
16+
{
17+
var filesCount = 100000;
18+
var maxDirectoryDepth = 8;
19+
return Enumerable.Range(0, filesCount).ToDictionary(
20+
i => XFS.Path(@$"C:\{string.Join(@"\", Enumerable.Range(0, i % maxDirectoryDepth + 1).Select(i => i.ToString()))}\{i}.bin"),
21+
i => new MockFileData(i.ToString()));
22+
}
23+
24+
[Benchmark]
25+
public MockFileSystem MockFileSystem_Constructor() => new MockFileSystem(testData);
26+
}
27+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
namespace System.IO.Abstractions.Benchmarks
22
{
33
using BenchmarkDotNet.Running;
4+
using System.Reflection;
45

56
class Program
67
{
78
public static void Main(string[] args)
89
{
9-
BenchmarkRunner.Run<FileSystemAbstractionBenchmarks>();
10+
BenchmarkRunner.Run(typeof(Program).Assembly);
1011
}
1112
}
1213
}

benchmarks/System.IO.Abstractions.Benchmarks/System.IO.Abstractions.Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</PropertyGroup>
1515
<ItemGroup>
1616
<ProjectReference Include="../../src/System.IO.Abstractions/System.IO.Abstractions.csproj" />
17+
<ProjectReference Include="../../src/System.IO.Abstractions.TestingHelpers/System.IO.Abstractions.TestingHelpers.csproj" />
1718
</ItemGroup>
1819
<ItemGroup>
1920
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net461" Version="1.0.0">

src/System.IO.Abstractions.TestingHelpers/MockFileSystem.cs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class MockFileSystem : IFileSystem, IMockFileDataAccessor
1313
private const string DEFAULT_CURRENT_DIRECTORY = @"C:\";
1414
private const string TEMP_DIRECTORY = @"C:\temp";
1515

16-
private readonly IDictionary<string, MockFileData> files;
16+
private readonly IDictionary<string, FileSystemEntry> files;
1717
private readonly PathVerifier pathVerifier;
1818

1919
public MockFileSystem() : this(null) { }
@@ -33,7 +33,7 @@ public MockFileSystem(IDictionary<string, MockFileData> files, string currentDir
3333

3434
StringOperations = new StringOperations(XFS.IsUnixPlatform());
3535
pathVerifier = new PathVerifier(this);
36-
this.files = new Dictionary<string, MockFileData>(StringOperations.Comparer);
36+
this.files = new Dictionary<string, FileSystemEntry>(StringOperations.Comparer);
3737

3838
Path = new MockPath(this, defaultTempDirectory);
3939
File = new MockFile(this);
@@ -101,10 +101,9 @@ private string GetPathWithCorrectDirectoryCapitalization(string fullPath)
101101
int lastSeparator = leftHalf.LastIndexOf(Path.DirectorySeparatorChar);
102102
leftHalf = lastSeparator > 0 ? leftHalf.Substring(0, lastSeparator) : leftHalf;
103103

104-
if (Directory.Exists(leftHalf))
104+
if (DirectoryExistsWithoutFixingPath(leftHalf))
105105
{
106-
leftHalf = Path.GetFullPath(leftHalf).TrimSlashes();
107-
string baseDirectory = AllDirectories.First(dir => StringOperations.Equals(dir, leftHalf));
106+
string baseDirectory = files[leftHalf].Path;
108107
return baseDirectory + Path.DirectorySeparatorChar + rightHalf;
109108
}
110109
}
@@ -121,7 +120,7 @@ public MockFileData GetFile(string path)
121120
private void SetEntry(string path, MockFileData mockFile)
122121
{
123122
path = FixPath(path, true).TrimSlashes();
124-
files[path] = mockFile;
123+
files[path] = new FileSystemEntry { Path = path, Data = mockFile };
125124
}
126125

127126
public void AddFile(string path, MockFileData mockFile)
@@ -145,7 +144,7 @@ public void AddFile(string path, MockFileData mockFile)
145144

146145
var directoryPath = Path.GetDirectoryName(fixedPath);
147146

148-
if (!Directory.Exists(directoryPath))
147+
if (!DirectoryExistsWithoutFixingPath(directoryPath))
149148
{
150149
AddDirectory(directoryPath);
151150
}
@@ -190,7 +189,7 @@ public void AddDirectory(string path)
190189
while ((lastIndex = StringOperations.IndexOf(fixedPath, separator, lastIndex + 1)) > -1)
191190
{
192191
var segment = fixedPath.Substring(0, lastIndex + 1);
193-
if (!Directory.Exists(segment))
192+
if (!DirectoryExistsWithoutFixingPath(segment))
194193
{
195194
SetEntry(segment, new MockDirectoryData());
196195
}
@@ -333,7 +332,7 @@ public IEnumerable<string> AllFiles
333332
{
334333
lock (files)
335334
{
336-
return files.Where(f => !f.Value.IsDirectory).Select(f => f.Key).ToArray();
335+
return files.Where(f => !f.Value.Data.IsDirectory).Select(f => f.Key).ToArray();
337336
}
338337
}
339338
}
@@ -344,7 +343,7 @@ public IEnumerable<string> AllDirectories
344343
{
345344
lock (files)
346345
{
347-
return files.Where(f => f.Value.IsDirectory).Select(f => f.Key).ToArray();
346+
return files.Where(f => f.Value.Data.IsDirectory).Select(f => f.Key).ToArray();
348347
}
349348
}
350349
}
@@ -358,9 +357,23 @@ private MockFileData GetFileWithoutFixingPath(string path)
358357
{
359358
lock (files)
360359
{
361-
files.TryGetValue(path, out var result);
362-
return result;
360+
return files.TryGetValue(path, out var result) ? result.Data : null;
363361
}
364362
}
363+
364+
private bool DirectoryExistsWithoutFixingPath(string path)
365+
{
366+
lock (files)
367+
{
368+
return files.TryGetValue(path, out var result) && result.Data.IsDirectory;
369+
}
370+
}
371+
372+
[Serializable]
373+
private class FileSystemEntry
374+
{
375+
public string Path { get; set; }
376+
public MockFileData Data { get; set; }
377+
}
365378
}
366379
}

tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
33
using System.ComponentModel;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.Reflection;
67
using System.Text;

0 commit comments

Comments
 (0)