Skip to content

Commit fcbd4df

Browse files
committed
feat(#569): Support relative base directories other than the working directory with WithDockerfileDirectory
1 parent 0eaea8b commit fcbd4df

File tree

5 files changed

+178
-1
lines changed

5 files changed

+178
-1
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
- 528 Do not require the Docker host configuration (`DockerEndpointAuthConfig`) on `TestcontainersSettings` initialization
1212
- 538 Support optional username and password in MongoDB connection string (@the-avid-engineer)
1313
- 540 Add Docker registry authentication provider for `DOCKER_AUTH_CONFIG` environment variable (@vova-lantsov-dev)
14-
- 541 Allow MsSqlTestcontainerConfiguration custom database names (@enginexon)
14+
- 541 Allow `MsSqlTestcontainerConfiguration` custom database names (@enginexon)
15+
- 558 Support relative base directories other than the working directory with `WithDockerfileDirectory`.
1516

1617
### Changed
1718

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
namespace DotNet.Testcontainers.Builders
2+
{
3+
using System;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Runtime.CompilerServices;
7+
using JetBrains.Annotations;
8+
9+
/// <summary>
10+
/// Resolves common directory paths.
11+
/// </summary>
12+
[PublicAPI]
13+
public readonly struct CommonDirectoryPath
14+
{
15+
private static readonly string WorkingDirectoryPath = Directory.GetCurrentDirectory();
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="CommonDirectoryPath" /> struct.
19+
/// </summary>
20+
/// <param name="directoryPath">The directory path.</param>
21+
[PublicAPI]
22+
public CommonDirectoryPath(string directoryPath)
23+
{
24+
this.DirectoryPath = directoryPath;
25+
}
26+
27+
/// <summary>
28+
/// Gets the directory path.
29+
/// </summary>
30+
[PublicAPI]
31+
public string DirectoryPath { get; }
32+
33+
/// <summary>
34+
/// Resolves the first bin directory upwards the directory tree.
35+
/// </summary>
36+
/// <returns>The first bin directory upwards the directory tree.</returns>
37+
/// <exception cref="DirectoryNotFoundException">Thrown when the bin directory was not found upwards the directory tree.</exception>
38+
[PublicAPI]
39+
public static CommonDirectoryPath GetBinDirectory()
40+
{
41+
var indexOfBinDirectory = WorkingDirectoryPath.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase);
42+
43+
if (indexOfBinDirectory > -1)
44+
{
45+
return new CommonDirectoryPath(WorkingDirectoryPath.Substring(0, indexOfBinDirectory));
46+
}
47+
48+
const string message = "Cannot find 'bin' and resolve the base directory in the directory tree.";
49+
throw new DirectoryNotFoundException(message);
50+
}
51+
52+
/// <summary>
53+
/// Resolves the first Git directory upwards the directory tree.
54+
/// </summary>
55+
/// <remarks>
56+
/// Start node is the caller file path directory. End node is the root directory.
57+
/// </remarks>
58+
/// <param name="filePath">The caller file path.</param>
59+
/// <returns>The first Git directory upwards the directory tree.</returns>
60+
/// <exception cref="DirectoryNotFoundException">Thrown when the Git directory was not found upwards the directory tree.</exception>
61+
[PublicAPI]
62+
public static CommonDirectoryPath GetGitDirectory([CallerFilePath, NotNull] string filePath = "")
63+
{
64+
return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), ".git"));
65+
}
66+
67+
/// <summary>
68+
/// Resolves the first Visual Studio solution file upwards the directory tree.
69+
/// </summary>
70+
/// <remarks>
71+
/// Start node is the caller file path directory. End node is the root directory.
72+
/// </remarks>
73+
/// <param name="filePath">The caller file path.</param>
74+
/// <returns>The first Visual Studio solution file upwards the directory tree.</returns>
75+
/// <exception cref="DirectoryNotFoundException">Thrown when the Visual Studio solution file was not found upwards the directory tree.</exception>
76+
[PublicAPI]
77+
public static CommonDirectoryPath GetSolutionDirectory([CallerFilePath, NotNull] string filePath = "")
78+
{
79+
return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.sln"));
80+
}
81+
82+
/// <summary>
83+
/// Resolves the first CSharp project file upwards the directory tree.
84+
/// </summary>
85+
/// <remarks>
86+
/// Start node is the caller file path directory. End node is the root directory.
87+
/// </remarks>
88+
/// <param name="filePath">The caller file path.</param>
89+
/// <returns>The first CSharp project file upwards the directory tree.</returns>
90+
/// <exception cref="DirectoryNotFoundException">Thrown when the CSharp project file was not found upwards the directory tree.</exception>
91+
[PublicAPI]
92+
public static CommonDirectoryPath GetProjectDirectory([CallerFilePath, NotNull] string filePath = "")
93+
{
94+
return new CommonDirectoryPath(GetDirectoryPath(Path.GetDirectoryName(filePath), "*.csproj"));
95+
}
96+
97+
/// <summary>
98+
/// Resolves the caller file path directory.
99+
/// </summary>
100+
/// <param name="filePath">The caller file path.</param>
101+
/// <returns>The caller file path directory.</returns>
102+
[PublicAPI]
103+
public static CommonDirectoryPath GetCallerFileDirectory([CallerFilePath, NotNull] string filePath = "")
104+
{
105+
return new CommonDirectoryPath(Path.GetDirectoryName(filePath));
106+
}
107+
108+
private static string GetDirectoryPath(string path, string searchPattern)
109+
{
110+
return GetDirectoryPath(Directory.Exists(path) ? new DirectoryInfo(path) : null, searchPattern);
111+
}
112+
113+
private static string GetDirectoryPath(DirectoryInfo path, string searchPattern)
114+
{
115+
if (path != null)
116+
{
117+
return path.EnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly).Any() ? path.FullName : GetDirectoryPath(path.Parent, searchPattern);
118+
}
119+
120+
var message = $"Cannot find '{searchPattern}' and resolve the base directory in the directory tree.";
121+
throw new DirectoryNotFoundException(message);
122+
}
123+
}
124+
}

src/Testcontainers/Builders/IImageFromDockerfileBuilder.cs

+9
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ public interface IImageFromDockerfileBuilder : IAbstractBuilder<IImageFromDocker
4242
[PublicAPI]
4343
IImageFromDockerfileBuilder WithDockerfileDirectory(string dockerfileDirectory);
4444

45+
/// <summary>
46+
/// Sets the base directory of the Dockerfile.
47+
/// </summary>
48+
/// <param name="commonDirectoryPath">Common directory path that contains the Dockerfile base directory.</param>
49+
/// <param name="dockerfileDirectory">Dockerfile base directory.</param>
50+
/// <returns>A configured instance of <see cref="IImageFromDockerfileBuilder" />.</returns>
51+
[PublicAPI]
52+
IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory);
53+
4554
/// <summary>
4655
/// If true, Testcontainer will remove the existing Docker image. Otherwise, Testcontainer will keep the Docker image.
4756
/// </summary>

src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Builders
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.IO;
56
using System.Threading.Tasks;
67
using DotNet.Testcontainers.Clients;
78
using DotNet.Testcontainers.Configurations;
@@ -59,6 +60,13 @@ public IImageFromDockerfileBuilder WithDockerfileDirectory(string dockerfileDire
5960
return this.MergeNewConfiguration(new ImageFromDockerfileConfiguration(dockerfileDirectory: dockerfileDirectory));
6061
}
6162

63+
/// <inheritdoc />
64+
public IImageFromDockerfileBuilder WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory)
65+
{
66+
var baseDirectoryPath = Path.Combine(commonDirectoryPath.DirectoryPath, dockerfileDirectory);
67+
return this.WithDockerfileDirectory(baseDirectoryPath);
68+
}
69+
6270
/// <inheritdoc />
6371
public IImageFromDockerfileBuilder WithDeleteIfExists(bool deleteIfExists)
6472
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace DotNet.Testcontainers.Tests.Unit
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using DotNet.Testcontainers.Builders;
7+
using Xunit;
8+
9+
public sealed class CommonDirectoryPathTest
10+
{
11+
public static IEnumerable<object[]> CommonDirectoryPaths { get; }
12+
= new[]
13+
{
14+
new[] { (object)CommonDirectoryPath.GetBinDirectory() },
15+
new[] { (object)CommonDirectoryPath.GetGitDirectory() },
16+
new[] { (object)CommonDirectoryPath.GetProjectDirectory() },
17+
new[] { (object)CommonDirectoryPath.GetSolutionDirectory() },
18+
new[] { (object)CommonDirectoryPath.GetCallerFileDirectory() },
19+
};
20+
21+
[Theory]
22+
[MemberData(nameof(CommonDirectoryPaths))]
23+
public void CommonDirectoryPathExists(CommonDirectoryPath commonDirectoryPath)
24+
{
25+
Assert.True(Directory.Exists(commonDirectoryPath.DirectoryPath));
26+
}
27+
28+
[Fact]
29+
public void CommonDirectoryPathNotExists()
30+
{
31+
var callerFilePath = Path.GetPathRoot(Directory.GetCurrentDirectory());
32+
Assert.Throws<DirectoryNotFoundException>(() => CommonDirectoryPath.GetGitDirectory(callerFilePath));
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)