diff --git a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs
index d190e6d43..cf17da070 100644
--- a/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystemInitializerExtensions.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
+using System.Reflection;
using Testably.Abstractions.Testing.FileSystemInitializer;
+using Testably.Abstractions.Testing.Helpers;
namespace Testably.Abstractions.Testing;
@@ -56,4 +58,113 @@ public static IDirectoryCleaner SetCurrentDirectoryToEmptyTemporaryDirectory(
{
return new DirectoryCleaner(fileSystem, prefix, logger);
}
+
+ ///
+ ///
+ /// The file system.
+ /// The assembly in which the embedded resource files are located.
+ /// The directory path in which the found resource files are created.
+ /// The relative path of the embedded resources in the .
+ ///
+ /// The search string to match against the names of embedded resources in the under
+ /// .
+ /// This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but it doesn't
+ /// support regular expressions.
+ ///
+ ///
+ /// One of the enumeration values that specifies whether the search operation should include only the
+ /// or should include all subdirectories.
+ /// The default value is .
+ ///
+ public static void InitializeEmbeddedResourcesFromAssembly(this IFileSystem fileSystem,
+ string directoryPath,
+ Assembly assembly,
+ string? relativePath = null,
+ string searchPattern = "*",
+ SearchOption searchOption = SearchOption.AllDirectories)
+ {
+ EnumerationOptions enumerationOptions =
+ EnumerationOptionsHelper.FromSearchOption(searchOption);
+
+ string[] resourcePaths = assembly.GetManifestResourceNames();
+ string assemblyNamePrefix = $"{assembly.GetName().Name ?? ""}.";
+
+ if (relativePath != null)
+ {
+ relativePath = relativePath.Replace(
+ Path.AltDirectorySeparatorChar,
+ Path.DirectorySeparatorChar);
+ relativePath = relativePath.TrimEnd(Path.DirectorySeparatorChar);
+ relativePath += Path.DirectorySeparatorChar;
+ }
+
+ foreach (string resourcePath in resourcePaths)
+ {
+ string fileName = resourcePath;
+ if (fileName.StartsWith(assemblyNamePrefix))
+ {
+ fileName = fileName.Substring(assemblyNamePrefix.Length);
+ }
+
+ fileName = fileName.Replace('.', Path.DirectorySeparatorChar);
+ int lastSeparator = fileName.LastIndexOf(Path.DirectorySeparatorChar);
+ if (lastSeparator > 0)
+ {
+ fileName = fileName.Substring(0, lastSeparator) + "." +
+ fileName.Substring(lastSeparator + 1);
+ }
+
+ if (relativePath != null)
+ {
+ if (!fileName.StartsWith(relativePath))
+ {
+ continue;
+ }
+
+ fileName = fileName.Substring(relativePath.Length);
+ }
+
+ if (!enumerationOptions.RecurseSubdirectories &&
+ fileName.IndexOf(Path.DirectorySeparatorChar) >= 0)
+ {
+ continue;
+ }
+
+ if (EnumerationOptionsHelper.MatchesPattern(enumerationOptions,
+ fileName, searchPattern))
+ {
+ string filePath = fileSystem.Path.Combine(directoryPath, fileName);
+ fileSystem.InitializeFileFromEmbeddedResource(filePath, assembly, resourcePath);
+ }
+ }
+ }
+
+ private static void InitializeFileFromEmbeddedResource(this IFileSystem fileSystem,
+ string path,
+ Assembly assembly,
+ string embeddedResourcePath)
+ {
+ using (Stream? embeddedResourceStream = assembly
+ .GetManifestResourceStream(embeddedResourcePath))
+ {
+ if (embeddedResourceStream == null)
+ {
+ throw new ArgumentException(
+ $"Resource '{embeddedResourcePath}' not found in assembly '{assembly.FullName}'",
+ nameof(embeddedResourcePath));
+ }
+
+ using (BinaryReader streamReader = new(embeddedResourceStream))
+ {
+ byte[] fileData = streamReader.ReadBytes((int)embeddedResourceStream.Length);
+ string? directoryPath = fileSystem.Path.GetDirectoryName(path);
+ if (!string.IsNullOrEmpty(directoryPath))
+ {
+ fileSystem.Directory.CreateDirectory(directoryPath);
+ }
+
+ fileSystem.File.WriteAllBytes(path, fileData);
+ }
+ }
+ }
}
diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs
index a36f69855..0d4d06aae 100644
--- a/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs
+++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializerExtensionsTests.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection;
using Testably.Abstractions.Testing.FileSystemInitializer;
using Testably.Abstractions.Testing.Tests.TestHelpers;
@@ -149,6 +150,73 @@ public void Initialize_WithSubdirectory_ShouldExist(string directoryName)
result.Directory.Exists.Should().BeTrue();
}
+ [Theory]
+ [AutoData]
+ public void
+ InitializeEmbeddedResourcesFromAssembly_ShouldCopyAllMatchingResourceFilesInDirectory(
+ string path)
+ {
+ MockFileSystem fileSystem = new();
+ fileSystem.InitializeIn("foo");
+
+ fileSystem.InitializeEmbeddedResourcesFromAssembly(
+ path,
+ Assembly.GetExecutingAssembly(),
+ searchPattern: "*.txt");
+
+ string[] result = fileSystem.Directory.GetFiles(Path.Combine(path, "TestResources"));
+ string[] result2 =
+ fileSystem.Directory.GetFiles(Path.Combine(path, "TestResources", "SubResource"));
+ result.Length.Should().Be(2);
+ result.Should().Contain(x => x.EndsWith("TestFile1.txt"));
+ result.Should().Contain(x => x.EndsWith("TestFile2.txt"));
+ result2.Length.Should().Be(1);
+ result2.Should().Contain(x => x.EndsWith("SubResourceFile1.txt"));
+ }
+
+ [Theory]
+ [AutoData]
+ public void
+ InitializeEmbeddedResourcesFromAssembly_WithoutRecurseSubdirectories_ShouldOnlyCopyTopmostFilesInRelativePath(
+ string path)
+ {
+ MockFileSystem fileSystem = new();
+ fileSystem.InitializeIn("foo");
+
+ fileSystem.InitializeEmbeddedResourcesFromAssembly(
+ path,
+ Assembly.GetExecutingAssembly(),
+ "TestResources",
+ searchPattern: "*.txt",
+ SearchOption.TopDirectoryOnly);
+
+ string[] result = fileSystem.Directory.GetFiles(path);
+ result.Length.Should().Be(2);
+ result.Should().Contain(x => x.EndsWith("TestFile1.txt"));
+ result.Should().Contain(x => x.EndsWith("TestFile2.txt"));
+ fileSystem.Directory.Exists(Path.Combine(path, "SubResource")).Should().BeFalse();
+ }
+
+ [Theory]
+ [AutoData]
+ public void
+ InitializeEmbeddedResourcesFromAssembly_WithRelativePath_ShouldCopyAllResourceInMatchingPathInDirectory(
+ string path)
+ {
+ MockFileSystem fileSystem = new();
+ fileSystem.InitializeIn("foo");
+
+ fileSystem.InitializeEmbeddedResourcesFromAssembly(
+ path,
+ Assembly.GetExecutingAssembly(),
+ "TestResources/SubResource",
+ searchPattern: "*.txt");
+
+ string[] result = fileSystem.Directory.GetFiles(path);
+ result.Length.Should().Be(1);
+ result.Should().Contain(x => x.EndsWith("SubResourceFile1.txt"));
+ }
+
[SkippableTheory]
[AutoData]
public void InitializeIn_MissingDrive_ShouldCreateDrive(string directoryName)
diff --git a/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.Designer.cs b/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.Designer.cs
new file mode 100644
index 000000000..1f94c3efb
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.Designer.cs
@@ -0,0 +1,93 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Testably.Abstractions.Testing.Tests.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Testably.Abstractions.Testing.Tests.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SubResourceFile1 content
+ ///.
+ ///
+ internal static string SubResourceFile1 {
+ get {
+ return ResourceManager.GetString("SubResourceFile1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to TestFile1 content
+ ///.
+ ///
+ internal static string TestFile1 {
+ get {
+ return ResourceManager.GetString("TestFile1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to TestFile2 content
+ ///.
+ ///
+ internal static string TestFile2 {
+ get {
+ return ResourceManager.GetString("TestFile2", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.resx b/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.resx
new file mode 100644
index 000000000..06cb7107b
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/Properties/Resources.resx
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\TestResources\SubResource\SubResourceFile1.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+
+ ..\TestResources\TestFile1.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+
+ ..\TestResources\TestFile2.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
+
+
diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestResources/SubResource/SubResourceFile1.txt b/Tests/Testably.Abstractions.Testing.Tests/TestResources/SubResource/SubResourceFile1.txt
new file mode 100644
index 000000000..dbf42fd79
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/TestResources/SubResource/SubResourceFile1.txt
@@ -0,0 +1 @@
+SubResourceFile1 content
diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile1.txt b/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile1.txt
new file mode 100644
index 000000000..70a0af585
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile1.txt
@@ -0,0 +1 @@
+TestFile1 content
diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile2.txt b/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile2.txt
new file mode 100644
index 000000000..912884e35
--- /dev/null
+++ b/Tests/Testably.Abstractions.Testing.Tests/TestResources/TestFile2.txt
@@ -0,0 +1 @@
+TestFile2 content
diff --git a/Tests/Testably.Abstractions.Testing.Tests/Testably.Abstractions.Testing.Tests.csproj b/Tests/Testably.Abstractions.Testing.Tests/Testably.Abstractions.Testing.Tests.csproj
index a130ed029..a978f1e65 100644
--- a/Tests/Testably.Abstractions.Testing.Tests/Testably.Abstractions.Testing.Tests.csproj
+++ b/Tests/Testably.Abstractions.Testing.Tests/Testably.Abstractions.Testing.Tests.csproj
@@ -4,9 +4,36 @@
net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+