Skip to content

Commit dd52691

Browse files
authored
Merge branch 'develop' into docs-ch
2 parents 7b37884 + 2a09374 commit dd52691

File tree

48 files changed

+504
-57
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+504
-57
lines changed

build/Parameters.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static BuildParameters Instance(ICakeContext context)
5252
IsReleaseBuild = !buildInformation.IsLocalBuild && buildInformation.IsReleaseBuild,
5353
IsPullRequest = buildInformation.IsPullRequest,
5454
ShouldPublish = !buildInformation.IsLocalBuild && buildInformation.ShouldPublish,
55-
Verbosity = DotNetVerbosity.Quiet,
55+
Verbosity = DotNetVerbosity.Minimal,
5656
CodeSigningCertificateCredentials = BuildCredentials.GetCodeSigningCertificateCredentials(context),
5757
SonarQubeCredentials = SonarQubeCredentials.GetSonarQubeCredentials(context),
5858
NuGetCredentials = NuGetCredentials.GetNuGetCredentials(context),

src/Testcontainers/Builders/AbstractBuilder`4.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ namespace DotNet.Testcontainers.Builders
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
57
using DotNet.Testcontainers.Clients;
68
using DotNet.Testcontainers.Configurations;
79
using DotNet.Testcontainers.Containers;
@@ -141,9 +143,8 @@ protected virtual void Validate()
141143
_ = Guard.Argument(DockerResourceConfiguration.Logger, nameof(IResourceConfiguration<TCreateResourceEntity>.Logger))
142144
.NotNull();
143145

144-
const string containerRuntimeNotFound = "Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured. You can customize your configuration using either the environment variables or the ~/.testcontainers.properties file. For more information, visit:\nhttps://dotnet.testcontainers.org/custom_configuration/";
145146
_ = Guard.Argument(DockerResourceConfiguration.DockerEndpointAuthConfig, nameof(IResourceConfiguration<TCreateResourceEntity>.DockerEndpointAuthConfig))
146-
.ThrowIf(argument => argument.Value == null, argument => new ArgumentException(containerRuntimeNotFound, argument.Name));
147+
.ThrowIf(argument => argument.Value == null, CreateDockerUnavailableException);
147148

148149
const string reuseNotSupported = "Reuse cannot be used in conjunction with WithCleanUp(true).";
149150
_ = Guard.Argument(DockerResourceConfiguration, nameof(IResourceConfiguration<TCreateResourceEntity>.Reuse))
@@ -164,5 +165,23 @@ protected virtual void Validate()
164165
/// <param name="newValue">The new Docker resource configuration.</param>
165166
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
166167
protected abstract TBuilderEntity Merge(TConfigurationEntity oldValue, TConfigurationEntity newValue);
168+
169+
private static Exception CreateDockerUnavailableException(Guard.ArgumentInfo<IDockerEndpointAuthenticationConfiguration> argument)
170+
{
171+
var unavailableExceptions = TestcontainersSettings.DockerEndpointAuthProviders
172+
.Select(authProvider => authProvider.LastException)
173+
.Where(exception => exception != null);
174+
175+
var exception = new AggregateException(unavailableExceptions);
176+
177+
var exceptionInfo = new StringBuilder(512);
178+
exceptionInfo.AppendLine("Docker is either not running or misconfigured. Please ensure that Docker is running and that the endpoint is properly configured.");
179+
exceptionInfo.AppendLine("You can customize your configuration using either the environment variables or the ~/.testcontainers.properties file.");
180+
exceptionInfo.AppendLine("For more information, visit: https://dotnet.testcontainers.org/custom_configuration/.");
181+
exceptionInfo.AppendLine(" Details: ");
182+
exceptionInfo.Append(string.Join(Environment.NewLine, exception.InnerExceptions.Select(e => " " + e.Message)));
183+
184+
return new DockerUnavailableException(exceptionInfo.ToString(), exception);
185+
}
167186
}
168187
}

src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ namespace DotNet.Testcontainers.Builders
55
using System.Threading.Tasks;
66
using DotNet.Testcontainers.Configurations;
77
using DotNet.Testcontainers.Containers;
8+
using JetBrains.Annotations;
89

910
/// <inheritdoc cref="IDockerEndpointAuthenticationProvider" />
1011
internal class DockerEndpointAuthenticationProvider : IDockerEndpointAuthenticationProvider
1112
{
1213
private static readonly TaskFactory TaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
1314

15+
[CanBeNull]
16+
private Exception _cachedException;
17+
18+
/// <summary>
19+
/// Exposes the exception that occurred during the last Docker availability check.
20+
/// </summary>
21+
[CanBeNull]
22+
public Exception LastException => _cachedException;
23+
1424
/// <inheritdoc />
1525
public virtual bool IsApplicable()
1626
{
@@ -38,10 +48,15 @@ public virtual bool IsAvailable()
3848
await dockerClient.System.PingAsync()
3949
.ConfigureAwait(false);
4050

51+
_cachedException = null;
52+
4153
return true;
4254
}
43-
catch (Exception)
55+
catch (Exception e)
4456
{
57+
var message = $"Failed to connect to Docker endpoint at '{dockerClientConfiguration.EndpointBaseUri}'.";
58+
_cachedException = new DockerUnavailableException(message, e);
59+
4560
return false;
4661
}
4762
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace DotNet.Testcontainers.Builders
2+
{
3+
using System;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// Represents an exception that is thrown when a connection to the Docker endpoint
8+
/// cannot be established successfully.
9+
/// </summary>
10+
[PublicAPI]
11+
public sealed class DockerUnavailableException : Exception
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="DockerUnavailableException" /> class.
15+
/// </summary>
16+
/// <param name="message">The message that describes the error.</param>
17+
public DockerUnavailableException(string message) : base(message)
18+
{
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="DockerUnavailableException" /> class.
23+
/// </summary>
24+
/// <param name="message">The message that describes the error.</param>
25+
/// <param name="innerException">The exception that is the cause of the current exception.</param>
26+
public DockerUnavailableException(string message, Exception innerException)
27+
: base(message, innerException)
28+
{
29+
}
30+
}
31+
}

src/Testcontainers/Configurations/TestcontainersSettings.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ namespace DotNet.Testcontainers.Configurations
1717
[PublicAPI]
1818
public static class TestcontainersSettings
1919
{
20+
internal static readonly List<DockerEndpointAuthenticationProvider> DockerEndpointAuthProviders
21+
= new List<DockerEndpointAuthenticationProvider>
22+
{
23+
new TestcontainersEndpointAuthenticationProvider(),
24+
new MTlsEndpointAuthenticationProvider(),
25+
new TlsEndpointAuthenticationProvider(),
26+
new EnvironmentEndpointAuthenticationProvider(),
27+
new NpipeEndpointAuthenticationProvider(),
28+
new UnixEndpointAuthenticationProvider(),
29+
new DockerDesktopEndpointAuthenticationProvider(),
30+
new RootlessUnixEndpointAuthenticationProvider(),
31+
};
32+
2033
[CanBeNull]
2134
private static readonly IDockerEndpointAuthenticationProvider DockerEndpointAuthProvider
22-
= new IDockerEndpointAuthenticationProvider[]
23-
{
24-
new TestcontainersEndpointAuthenticationProvider(),
25-
new MTlsEndpointAuthenticationProvider(),
26-
new TlsEndpointAuthenticationProvider(),
27-
new EnvironmentEndpointAuthenticationProvider(),
28-
new NpipeEndpointAuthenticationProvider(),
29-
new UnixEndpointAuthenticationProvider(),
30-
new DockerDesktopEndpointAuthenticationProvider(),
31-
new RootlessUnixEndpointAuthenticationProvider(),
32-
}
33-
.Where(authProvider => authProvider.IsApplicable())
34-
.FirstOrDefault(authProvider => authProvider.IsAvailable());
35+
= DockerEndpointAuthProviders.FirstOrDefault(authProvider => authProvider.IsApplicable() && authProvider.IsAvailable());
3536

3637
[CanBeNull]
3738
private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace DotNet.Testcontainers.Containers
2+
{
3+
using System;
4+
using System.Linq;
5+
using System.Text;
6+
using JetBrains.Annotations;
7+
8+
/// <summary>
9+
/// Represents an exception that is thrown when executing a command inside a
10+
/// running container fails.
11+
/// </summary>
12+
[PublicAPI]
13+
public sealed class ExecFailedException : Exception
14+
{
15+
private static readonly string[] LineEndings = new[] { "\r\n", "\n" };
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ExecFailedException" /> class.
19+
/// </summary>
20+
/// <param name="execResult">The result of the failed command.</param>
21+
public ExecFailedException(ExecResult execResult)
22+
: base(CreateMessage(execResult))
23+
{
24+
ExecResult = execResult;
25+
}
26+
27+
/// <summary>
28+
/// Gets the result of the failed command.
29+
/// </summary>
30+
public ExecResult ExecResult { get; }
31+
32+
private static string CreateMessage(ExecResult execResult)
33+
{
34+
var exceptionInfo = new StringBuilder(256);
35+
exceptionInfo.Append($"Process exited with code {execResult.ExitCode}.");
36+
37+
if (!string.IsNullOrEmpty(execResult.Stdout))
38+
{
39+
var stdoutLines = execResult.Stdout
40+
.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries)
41+
.Select(line => " " + line);
42+
43+
exceptionInfo.AppendLine();
44+
exceptionInfo.AppendLine(" Stdout: ");
45+
exceptionInfo.Append(string.Join(Environment.NewLine, stdoutLines));
46+
}
47+
48+
if (!string.IsNullOrEmpty(execResult.Stderr))
49+
{
50+
var stderrLines = execResult.Stderr
51+
.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries)
52+
.Select(line => " " + line);
53+
54+
exceptionInfo.AppendLine();
55+
exceptionInfo.AppendLine(" Stderr: ");
56+
exceptionInfo.Append(string.Join(Environment.NewLine, stderrLines));
57+
}
58+
59+
return exceptionInfo.ToString();
60+
}
61+
}
62+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace DotNet.Testcontainers.Containers
2+
{
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
/// <summary>
7+
/// Extension methods for working with <see cref="ExecResult" /> instances.
8+
/// </summary>
9+
public static class ExecResultExtensions
10+
{
11+
/// <summary>
12+
/// Awaits the <see cref="Task{ExecResult}" /> and throws an exception if the result's exit code is not successful.
13+
/// </summary>
14+
/// <param name="execTask">The task returning an <see cref="ExecResult" />.</param>
15+
/// <param name="successExitCodes">A list of exit codes that should be treated as successful. If none are provided, only exit code <c>0</c> is treated as successful.</param>
16+
/// <returns>The <see cref="ExecResult" /> if the exit code is in the list of success exit codes.</returns>
17+
/// <exception cref="ExecFailedException">Thrown if the exit code is not in the list of success exit codes.</exception>
18+
public static async Task<ExecResult> ThrowOnFailure(this Task<ExecResult> execTask, params long[] successExitCodes)
19+
{
20+
successExitCodes = successExitCodes == null || successExitCodes.Length == 0 ? new long[] { 0 } : successExitCodes;
21+
22+
var execResult = await execTask
23+
.ConfigureAwait(false);
24+
25+
if (Array.IndexOf(successExitCodes, execResult.ExitCode) < 0)
26+
{
27+
throw new ExecFailedException(execResult);
28+
}
29+
30+
return execResult;
31+
}
32+
}
33+
}

tests/Testcontainers.ActiveMq.Tests/ArtemisContainerTest.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ await _artemisContainer.StartAsync()
2222
.ConfigureAwait(false);
2323
}
2424

25-
public ValueTask DisposeAsync()
25+
public async ValueTask DisposeAsync()
2626
{
27-
return _artemisContainer.DisposeAsync();
27+
await DisposeAsyncCore()
28+
.ConfigureAwait(false);
29+
30+
GC.SuppressFinalize(this);
2831
}
2932
// # --8<-- [end:UseArtemisContainer]
3033

@@ -73,6 +76,11 @@ await producer.SendAsync(producedMessage)
7376
}
7477
// # --8<-- [end:ArtemisContainerEstablishesConnection]
7578

79+
protected virtual ValueTask DisposeAsyncCore()
80+
{
81+
return _artemisContainer.DisposeAsync();
82+
}
83+
7684
// # --8<-- [start:UseArtemisContainerDefaultAuth]
7785
[UsedImplicitly]
7886
public sealed class DefaultCredentialsConfiguration : ArtemisContainerTest

tests/Testcontainers.Azurite.Tests/AzuriteContainerTest.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ await _azuriteContainer.StartAsync()
1515
.ConfigureAwait(false);
1616
}
1717

18-
public ValueTask DisposeAsync()
18+
public async ValueTask DisposeAsync()
1919
{
20-
return _azuriteContainer.DisposeAsync();
20+
await DisposeAsyncCore()
21+
.ConfigureAwait(false);
22+
23+
GC.SuppressFinalize(this);
2124
}
2225

2326
[Fact]
@@ -65,6 +68,11 @@ public async Task EstablishesTableServiceConnection()
6568
Assert.False(HasError(properties));
6669
}
6770

71+
protected virtual ValueTask DisposeAsyncCore()
72+
{
73+
return _azuriteContainer.DisposeAsync();
74+
}
75+
6876
private static bool HasError<TResponseEntity>(NullableResponse<TResponseEntity> response)
6977
{
7078
using (var rawResponse = response.GetRawResponse())
@@ -104,6 +112,7 @@ public AzuriteMemoryLimitConfiguration()
104112
}
105113

106114
[Fact]
115+
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
107116
public async Task MemoryLimitIsConfigured()
108117
{
109118
// Given

tests/Testcontainers.Commons/DockerCli.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public enum DockerResource
4444
public static bool PlatformIsEnabled(DockerPlatform platform)
4545
{
4646
var commandResult = new Command("version", "--format {{.Server.Os}}").Execute();
47-
return 0.Equals(commandResult.ExitCode) && commandResult.Stdout.Contains(platform.ToString().ToLowerInvariant());
47+
return 0.Equals(commandResult.ExitCode) && commandResult.Stdout.Contains(platform.ToString(), StringComparison.OrdinalIgnoreCase);
4848
}
4949

5050
public static bool ResourceExists(DockerResource resource, string id)

0 commit comments

Comments
 (0)