Skip to content

Commit ccb1fde

Browse files
vbreussvbtig
andcommitted
feat: Allow initialization of the filesystem with files from embedded resources (#256)
Add an extension method similar to [AddFilesFromEmbeddedNamespace](https://github.com/TestableIO/System.IO.Abstractions/blob/b57922c199f2262ccf077a859246937b5143abc1/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs#L278) to copy the embedded resources from a given assembly to the `MockFileSystem`. --------- Co-authored-by: Valentin Breuß <[email protected]>
1 parent 017a4ac commit ccb1fde

File tree

8 files changed

+432
-0
lines changed

8 files changed

+432
-0
lines changed

Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.IO;
3+
using System.Reflection;
34
using Testably.Abstractions.Testing.FileSystemInitializer;
5+
using Testably.Abstractions.Testing.Helpers;
46

57
namespace Testably.Abstractions.Testing;
68

@@ -56,4 +58,113 @@ public static IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(
5658
{
5759
return new DirectoryCleaner(fileSystem, prefix, logger);
5860
}
61+
62+
/// <summary>
63+
/// </summary>
64+
/// <param name="fileSystem">The file system.</param>
65+
/// <param name="assembly">The assembly in which the embedded resource files are located.</param>
66+
/// <param name="directoryPath">The directory path in which the found resource files are created.</param>
67+
/// <param name="relativePath">The relative path of the embedded resources in the <paramref name="assembly" />.</param>
68+
/// <param name="searchPattern">
69+
/// The search string to match against the names of embedded resources in the <paramref name="assembly" /> under
70+
/// <paramref name="relativePath" />.<br />
71+
/// This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't
72+
/// support regular expressions.
73+
/// </param>
74+
/// <param name="searchOption">
75+
/// One of the enumeration values that specifies whether the search operation should include only the
76+
/// <paramref name="relativePath" /> or should include all subdirectories.<br />
77+
/// The default value is <see cref="SearchOption.AllDirectories" />.
78+
/// </param>
79+
public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem fileSystem,
80+
string directoryPath,
81+
Assembly assembly,
82+
string? relativePath = null,
83+
string searchPattern = "*",
84+
SearchOption searchOption = SearchOption.AllDirectories)
85+
{
86+
EnumerationOptions enumerationOptions =
87+
EnumerationOptionsHelper.FromSearchOption(searchOption);
88+
89+
string[] resourcePaths = assembly.GetManifestResourceNames();
90+
string assemblyNamePrefix = $"{assembly.GetName().Name ?? ""}.";
91+
92+
if (relativePath != null)
93+
{
94+
relativePath = relativePath.Replace(
95+
Path.AltDirectorySeparatorChar,
96+
Path.DirectorySeparatorChar);
97+
relativePath = relativePath.TrimEnd(Path.DirectorySeparatorChar);
98+
relativePath += Path.DirectorySeparatorChar;
99+
}
100+
101+
foreach (string resourcePath in resourcePaths)
102+
{
103+
string fileName = resourcePath;
104+
if (fileName.StartsWith(assemblyNamePrefix))
105+
{
106+
fileName = fileName.Substring(assemblyNamePrefix.Length);
107+
}
108+
109+
fileName = fileName.Replace('.', Path.DirectorySeparatorChar);
110+
int lastSeparator = fileName.LastIndexOf(Path.DirectorySeparatorChar);
111+
if (lastSeparator > 0)
112+
{
113+
fileName = fileName.Substring(0, lastSeparator) + "." +
114+
fileName.Substring(lastSeparator + 1);
115+
}
116+
117+
if (relativePath != null)
118+
{
119+
if (!fileName.StartsWith(relativePath))
120+
{
121+
continue;
122+
}
123+
124+
fileName = fileName.Substring(relativePath.Length);
125+
}
126+
127+
if (!enumerationOptions.RecurseSubdirectories &&
128+
fileName.IndexOf(Path.DirectorySeparatorChar) >= 0)
129+
{
130+
continue;
131+
}
132+
133+
if (EnumerationOptionsHelper.MatchesPattern(enumerationOptions,
134+
fileName, searchPattern))
135+
{
136+
string filePath = fileSystem.Path.Combine(directoryPath, fileName);
137+
fileSystem.InitializeFileFromEmbeddedResource(filePath, assembly, resourcePath);
138+
}
139+
}
140+
}
141+
142+
private static void InitializeFileFromEmbeddedResource(this IFileSystem fileSystem,
143+
string path,
144+
Assembly assembly,
145+
string embeddedResourcePath)
146+
{
147+
using (Stream? embeddedResourceStream = assembly
148+
.GetManifestResourceStream(embeddedResourcePath))
149+
{
150+
if (embeddedResourceStream == null)
151+
{
152+
throw new ArgumentException(
153+
$"Resource '{embeddedResourcePath}' not found in assembly '{assembly.FullName}'",
154+
nameof(embeddedResourcePath));
155+
}
156+
157+
using (BinaryReader streamReader = new(embeddedResourceStream))
158+
{
159+
byte[] fileData = streamReader.ReadBytes((int)embeddedResourceStream.Length);
160+
string? directoryPath = fileSystem.Path.GetDirectoryName(path);
161+
if (!string.IsNullOrEmpty(directoryPath))
162+
{
163+
fileSystem.Directory.CreateDirectory(directoryPath);
164+
}
165+
166+
fileSystem.File.WriteAllBytes(path, fileData);
167+
}
168+
}
169+
}
59170
}

Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.IO;
33
using System.Linq;
4+
using System.Reflection;
45
using Testably.Abstractions.Testing.FileSystemInitializer;
56
using Testably.Abstractions.Testing.Tests.TestHelpers;
67

@@ -149,6 +150,73 @@ public void Initialize_WithSubdirectory_ShouldExist(string directoryName)
149150
result.Directory.Exists.Should().BeTrue();
150151
}
151152

153+
[Theory]
154+
[AutoData]
155+
public void
156+
InitializeEmbeddedResourcesFromAssembly_ShouldCopyAllMatchingResourceFilesInDirectory(
157+
string path)
158+
{
159+
MockFileSystem fileSystem = new();
160+
fileSystem.InitializeIn("foo");
161+
162+
fileSystem.InitializeEmbeddedResourcesFromAssembly(
163+
path,
164+
Assembly.GetExecutingAssembly(),
165+
searchPattern: "*.txt");
166+
167+
string[] result = fileSystem.Directory.GetFiles(Path.Combine(path, "TestResources"));
168+
string[] result2 =
169+
fileSystem.Directory.GetFiles(Path.Combine(path, "TestResources", "SubResource"));
170+
result.Length.Should().Be(2);
171+
result.Should().Contain(x => x.EndsWith("TestFile1.txt"));
172+
result.Should().Contain(x => x.EndsWith("TestFile2.txt"));
173+
result2.Length.Should().Be(1);
174+
result2.Should().Contain(x => x.EndsWith("SubResourceFile1.txt"));
175+
}
176+
177+
[Theory]
178+
[AutoData]
179+
public void
180+
InitializeEmbeddedResourcesFromAssembly_WithoutRecurseSubdirectories_ShouldOnlyCopyTopmostFilesInRelativePath(
181+
string path)
182+
{
183+
MockFileSystem fileSystem = new();
184+
fileSystem.InitializeIn("foo");
185+
186+
fileSystem.InitializeEmbeddedResourcesFromAssembly(
187+
path,
188+
Assembly.GetExecutingAssembly(),
189+
"TestResources",
190+
searchPattern: "*.txt",
191+
SearchOption.TopDirectoryOnly);
192+
193+
string[] result = fileSystem.Directory.GetFiles(path);
194+
result.Length.Should().Be(2);
195+
result.Should().Contain(x => x.EndsWith("TestFile1.txt"));
196+
result.Should().Contain(x => x.EndsWith("TestFile2.txt"));
197+
fileSystem.Directory.Exists(Path.Combine(path, "SubResource")).Should().BeFalse();
198+
}
199+
200+
[Theory]
201+
[AutoData]
202+
public void
203+
InitializeEmbeddedResourcesFromAssembly_WithRelativePath_ShouldCopyAllResourceInMatchingPathInDirectory(
204+
string path)
205+
{
206+
MockFileSystem fileSystem = new();
207+
fileSystem.InitializeIn("foo");
208+
209+
fileSystem.InitializeEmbeddedResourcesFromAssembly(
210+
path,
211+
Assembly.GetExecutingAssembly(),
212+
"TestResources/SubResource",
213+
searchPattern: "*.txt");
214+
215+
string[] result = fileSystem.Directory.GetFiles(path);
216+
result.Length.Should().Be(1);
217+
result.Should().Contain(x => x.EndsWith("SubResourceFile1.txt"));
218+
}
219+
152220
[SkippableTheory]
153221
[AutoData]
154222
public void InitializeIn_MissingDrive_ShouldCreateDrive(string directoryName)

Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.Designer.cs

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)