From fcbd4df81c370185ccdc10f948b43949f6ffcf2a Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sun, 4 Sep 2022 16:44:26 +0200 Subject: [PATCH] feat(#569): Support relative base directories other than the working directory with WithDockerfileDirectory --- CHANGELOG.md | 3 +- .../Builders/CommonDirectoryPath.cs | 124 ++++++++++++++++++ .../Builders/IImageFromDockerfileBuilder.cs | 9 ++ .../Builders/ImageFromDockerfileBuilder.cs | 8 ++ .../Unit/Builders/CommonDirectoryPathTest.cs | 35 +++++ 5 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/Testcontainers/Builders/CommonDirectoryPath.cs create mode 100644 tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 15229dc96..ddb6e512d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ - 528 Do not require the Docker host configuration (`DockerEndpointAuthConfig`) on `TestcontainersSettings` initialization - 538 Support optional username and password in MongoDB connection string (@the-avid-engineer) - 540 Add Docker registry authentication provider for `DOCKER_AUTH_CONFIG` environment variable (@vova-lantsov-dev) -- 541 Allow MsSqlTestcontainerConfiguration custom database names (@enginexon) +- 541 Allow `MsSqlTestcontainerConfiguration` custom database names (@enginexon) +- 558 Support relative base directories other than the working directory with `WithDockerfileDirectory`. ### Changed diff --git a/src/Testcontainers/Builders/CommonDirectoryPath.cs b/src/Testcontainers/Builders/CommonDirectoryPath.cs new file mode 100644 index 000000000..2bbdfca2c --- /dev/null +++ b/src/Testcontainers/Builders/CommonDirectoryPath.cs @@ -0,0 +1,124 @@ +namespace DotNet.Testcontainers.Builders +{ + using System; + using System.IO; + using System.Linq; + using System.Runtime.CompilerServices; + using JetBrains.Annotations; + + /// + /// Resolves common directory paths. + /// + [PublicAPI] + public readonly struct CommonDirectoryPath + { + private static readonly string WorkingDirectoryPath = Directory.GetCurrentDirectory(); + + /// + /// Initializes a new instance of the struct. + /// + /// The directory path. + [PublicAPI] + public CommonDirectoryPath(string directoryPath) + { + this.DirectoryPath = directoryPath; + } + + /// + /// Gets the directory path. + /// + [PublicAPI] + public string DirectoryPath { get; } + + /// + /// Resolves the first bin directory upwards the directory tree. + /// + /// The first bin directory upwards the directory tree. + /// Thrown when the bin directory was not found upwards the directory tree. + [PublicAPI] + public static CommonDirectoryPath GetBinDirectory() + { + var indexOfBinDirectory = WorkingDirectoryPath.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase); + + if (indexOfBinDirectory > -1) + { + return new CommonDirectoryPath(WorkingDirectoryPath.Substring(0, indexOfBinDirectory)); + } + + const string message = "Cannot find 'bin' and resolve the base directory in the directory tree."; + throw new DirectoryNotFoundException(message); + } + + /// + /// Resolves the first Git directory upwards the directory tree. + /// + /// + /// Start node is the caller file path directory. End node is the root directory. + /// + /// The caller file path. + /// The first Git directory upwards the directory tree. + /// Thrown when the Git directory was not found upwards the directory tree. + [PublicAPI] + public static CommonDirectoryPath GetGitDirectory([CallerFilePath, NotNull] string filePath = "") + { + return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), ".git")); + } + + /// + /// Resolves the first Visual Studio solution file upwards the directory tree. + /// + /// + /// Start node is the caller file path directory. End node is the root directory. + /// + /// The caller file path. + /// The first Visual Studio solution file upwards the directory tree. + /// Thrown when the Visual Studio solution file was not found upwards the directory tree. + [PublicAPI] + public static CommonDirectoryPath GetSolutionDirectory([CallerFilePath, NotNull] string filePath = "") + { + return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.sln")); + } + + /// + /// Resolves the first CSharp project file upwards the directory tree. + /// + /// + /// Start node is the caller file path directory. End node is the root directory. + /// + /// The caller file path. + /// The first CSharp project file upwards the directory tree. + /// Thrown when the CSharp project file was not found upwards the directory tree. + [PublicAPI] + public static CommonDirectoryPath GetProjectDirectory([CallerFilePath, NotNull] string filePath = "") + { + return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.csproj")); + } + + /// + /// Resolves the caller file path directory. + /// + /// The caller file path. + /// The caller file path directory. + [PublicAPI] + public static CommonDirectoryPath GetCallerFileDirectory([CallerFilePath, NotNull] string filePath = "") + { + return new CommonDirectoryPath(Path.GetDirectoryName(filePath)); + } + + private static string GetDirectoryPath(string path, string searchPattern) + { + return GetDirectoryPath(Directory.Exists(path) ? new DirectoryInfo(path) : null, searchPattern); + } + + private static string GetDirectoryPath(DirectoryInfo path, string searchPattern) + { + if (path != null) + { + return path.EnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly).Any() ? path.FullName : GetDirectoryPath(path.Parent, searchPattern); + } + + var message = $"Cannot find '{searchPattern}' and resolve the base directory in the directory tree."; + throw new DirectoryNotFoundException(message); + } + } +} diff --git a/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs b/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs index 98a0a6c61..2131cd198 100644 --- a/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs +++ b/src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs @@ -42,6 +42,15 @@ public interface IImageFromDockerfileBuilder : IAbstractBuilder + /// Sets the base directory of the Dockerfile. + /// + /// Common directory path that contains the Dockerfile base directory. + /// Dockerfile base directory. + /// A configured instance of . + [PublicAPI] + IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory); + /// /// If true, Testcontainer will remove the existing Docker image. Otherwise, Testcontainer will keep the Docker image. /// diff --git a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs index e0183d6b9..972d4804c 100644 --- a/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs +++ b/src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Builders { using System; using System.Collections.Generic; + using System.IO; using System.Threading.Tasks; using DotNet.Testcontainers.Clients; using DotNet.Testcontainers.Configurations; @@ -59,6 +60,13 @@ public IImageFromDockerfileBuilder WithDockerfileDirectory(string dockerfileDire return this.MergeNewConfiguration(new ImageFromDockerfileConfiguration(dockerfileDirectory: dockerfileDirectory)); } + /// + public IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory) + { + var baseDirectoryPath = Path.Combine(commonDirectoryPath.DirectoryPath, dockerfileDirectory); + return this.WithDockerfileDirectory(baseDirectoryPath); + } + /// public IImageFromDockerfileBuilder WithDeleteIfExists(bool deleteIfExists) { diff --git a/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs b/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs new file mode 100644 index 000000000..ee075f0a1 --- /dev/null +++ b/tests/Testcontainers.Tests/Unit/Builders/CommonDirectoryPathTest.cs @@ -0,0 +1,35 @@ +namespace DotNet.Testcontainers.Tests.Unit +{ + using System; + using System.Collections.Generic; + using System.IO; + using DotNet.Testcontainers.Builders; + using Xunit; + + public sealed class CommonDirectoryPathTest + { + public static IEnumerable CommonDirectoryPaths { get; } + = new[] + { + new[] { (object)CommonDirectoryPath.GetBinDirectory() }, + new[] { (object)CommonDirectoryPath.GetGitDirectory() }, + new[] { (object)CommonDirectoryPath.GetProjectDirectory() }, + new[] { (object)CommonDirectoryPath.GetSolutionDirectory() }, + new[] { (object)CommonDirectoryPath.GetCallerFileDirectory() }, + }; + + [Theory] + [MemberData(nameof(CommonDirectoryPaths))] + public void CommonDirectoryPathExists(CommonDirectoryPath commonDirectoryPath) + { + Assert.True(Directory.Exists(commonDirectoryPath.DirectoryPath)); + } + + [Fact] + public void CommonDirectoryPathNotExists() + { + var callerFilePath = Path.GetPathRoot(Directory.GetCurrentDirectory()); + Assert.Throws(() => CommonDirectoryPath.GetGitDirectory(callerFilePath)); + } + } +}