From 6df9aa54b57cde30c81cc41aa9590f27505f1a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 20 Feb 2025 17:03:28 +0100 Subject: [PATCH 1/9] Introduce a new UntilDatabaseIsAvailable wait strategy --- .../WaitStrategies/IWaitForContainerOS.cs | 21 ++++++ .../UntilDatabaseIsAvailable.cs | 65 +++++++++++++++++++ .../WaitStrategies/WaitForContainerOS.cs | 7 ++ .../CassandraContainerTest.cs | 40 +++++++----- .../Testcontainers.Cassandra.Tests.csproj | 1 + .../Testcontainers.Cassandra.Tests/Usings.cs | 6 +- .../ClickHouseContainerTest.cs | 38 ++++++----- .../Testcontainers.ClickHouse.Tests.csproj | 1 + .../Testcontainers.ClickHouse.Tests/Usings.cs | 6 +- .../CockroachDbContainerTest.cs | 38 ++++++----- .../Testcontainers.CockroachDb.Tests.csproj | 1 + .../Usings.cs | 6 +- .../FirebirdSqlContainerTest.cs | 9 +++ .../Usings.cs | 1 + .../MariaDbContainerTest.cs | 9 +++ tests/Testcontainers.MariaDb.Tests/Usings.cs | 1 + .../MsSqlContainerTest.cs | 9 +++ tests/Testcontainers.MsSql.Tests/Usings.cs | 1 + .../MySqlContainerTest.cs | 9 +++ tests/Testcontainers.MySql.Tests/Usings.cs | 1 + .../OracleContainerTest.cs | 18 +++-- tests/Testcontainers.Oracle.Tests/Usings.cs | 2 + .../PostgreSqlContainerTest.cs | 32 ++++----- .../Testcontainers.PostgreSql.Tests/Usings.cs | 1 + 24 files changed, 253 insertions(+), 70 deletions(-) create mode 100644 src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs index cacefc5dd..7b22c6196 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs @@ -2,7 +2,9 @@ namespace DotNet.Testcontainers.Configurations { using System; using System.Collections.Generic; + using System.Data.Common; using System.Text.RegularExpressions; + using DotNet.Testcontainers.Containers; using JetBrains.Annotations; /// @@ -112,6 +114,25 @@ public interface IWaitForContainerOS [PublicAPI] IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action waitStrategyModifier = null); + /// + /// Waits until a connection to the database can be successfully opened. + /// + /// The used to create the database connection. + /// + /// The retry frequency until either the connection is successfully opened or the is reached. + /// Defaults to 1 second if not specified. + /// + /// + /// The maximum duration to retry. Once reached, a is thrown. + /// Defaults to 5 minutes if not specified. + /// + /// A configured instance of . + /// + /// This wait strategy must only be applied to containers implementing the interface. + /// + [PublicAPI] + IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency = default, TimeSpan timeout = default); + /// /// Returns a collection with all configured wait strategies. /// diff --git a/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs new file mode 100644 index 000000000..129147ac4 --- /dev/null +++ b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs @@ -0,0 +1,65 @@ +namespace DotNet.Testcontainers.Configurations +{ + using System; + using System.Data.Common; + using System.Diagnostics; + using System.Threading.Tasks; + using DotNet.Testcontainers.Containers; + + internal class UntilDatabaseIsAvailable : IWaitUntil + { + private static readonly TimeSpan DefaultFrequency = TimeSpan.FromSeconds(1); + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(5); + + private readonly DbProviderFactory _dbProviderFactory; + private readonly TimeSpan _frequency; + private readonly TimeSpan _timeout; + + public UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency, TimeSpan timeout) + { + _dbProviderFactory = dbProviderFactory; + _frequency = frequency == TimeSpan.Zero ? DefaultFrequency : frequency; + _timeout = timeout == TimeSpan.Zero ? DefaultTimeout : timeout; + } + + public async Task UntilAsync(IContainer container) + { + if (container is not IDatabaseContainer dbContainer) + { + throw new NotSupportedException($"The \"UntilDatabaseIsAvailable\" wait strategy is only available on database containers. " + + $"{container.GetType().FullName} does not implement the {nameof(IDatabaseContainer)} interface."); + } + + var stopwatch = Stopwatch.StartNew(); + + var connectionString = dbContainer.GetConnectionString(); + while (!await IsAvailableAsync(connectionString, stopwatch)) + { + await Task.Delay(_frequency); + } + + return true; + } + + private async Task IsAvailableAsync(string connectionString, Stopwatch stopwatch) + { + using (var connection = _dbProviderFactory.CreateConnection() ?? throw new InvalidOperationException($"{_dbProviderFactory.GetType().FullName}.CreateConnection() returned null.")) + { + connection.ConnectionString = connectionString; + try + { + await connection.OpenAsync(); + return true; + } + catch (Exception exception) + { + if (stopwatch.Elapsed > _timeout) + { + throw new TimeoutException($"The database was not available at \"{connectionString}\" after waiting for {_timeout.TotalSeconds:F0} seconds.", exception); + } + return false; + } + } + } + } +} diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs index c5306d1c2..8ade90be0 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Configurations { using System; using System.Collections.Generic; + using System.Data.Common; using System.Text.RegularExpressions; /// @@ -80,6 +81,12 @@ public virtual IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = return AddCustomWaitStrategy(new UntilContainerIsHealthy(failingStreak), waitStrategyModifier); } + /// + public virtual IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency, TimeSpan timeout) + { + return AddCustomWaitStrategy(new UntilDatabaseIsAvailable(dbProviderFactory, frequency, timeout)); + } + /// public IEnumerable Build() { diff --git a/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs b/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs index e31915dcb..84c2ce181 100644 --- a/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs +++ b/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs @@ -1,26 +1,14 @@ namespace Testcontainers.Cassandra; -public sealed class CassandraContainerTest : IAsyncLifetime +public abstract class CassandraContainerTest(CassandraContainerTest.CassandraFixture fixture) { // # --8<-- [start:UseCassandraContainer] - private readonly CassandraContainer _cassandraContainer = new CassandraBuilder().Build(); - - public Task InitializeAsync() - { - return _cassandraContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _cassandraContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new CqlConnection(_cassandraContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -36,7 +24,7 @@ public void ExecuteCqlStatementReturnsExpectedResult() // Given const string selectFromSystemLocalStatement = "SELECT * FROM system.local WHERE key = ?;"; - using var cluster = Cluster.Builder().WithConnectionString(_cassandraContainer.GetConnectionString()).Build(); + using var cluster = Cluster.Builder().WithConnectionString(fixture.Container.GetConnectionString()).Build(); // When using var session = cluster.Connect(); @@ -61,11 +49,31 @@ public async Task ExecScriptAsyncReturnsSuccess() const string selectFromSystemLocalStatement = "SELECT * FROM system.local;"; // When - var execResult = await _cassandraContainer.ExecScriptAsync(selectFromSystemLocalStatement) + var execResult = await fixture.Container.ExecScriptAsync(selectFromSystemLocalStatement) .ConfigureAwait(true); // Then Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); Assert.Empty(execResult.Stderr); } + + [UsedImplicitly] + public sealed class CassandraContainerDefaultConfiguration(CassandraFixture fixture) : CassandraContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class CassandraContainerWaitForDatabase(CassandraFixtureWaitForDatabase fixture) : CassandraContainerTest(fixture), IClassFixture; + + public class CassandraFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + { + public override DbProviderFactory DbProviderFactory => CqlProviderFactory.Instance; + } + + [UsedImplicitly] + public sealed class CassandraFixtureWaitForDatabase(IMessageSink messageSink) : CassandraFixture(messageSink) + { + protected override CassandraBuilder Configure(CassandraBuilder builder) + { + return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.Cassandra.Tests/Testcontainers.Cassandra.Tests.csproj b/tests/Testcontainers.Cassandra.Tests/Testcontainers.Cassandra.Tests.csproj index 6e7c4262b..217e1ed9f 100644 --- a/tests/Testcontainers.Cassandra.Tests/Testcontainers.Cassandra.Tests.csproj +++ b/tests/Testcontainers.Cassandra.Tests/Testcontainers.Cassandra.Tests.csproj @@ -15,6 +15,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.Cassandra.Tests/Usings.cs b/tests/Testcontainers.Cassandra.Tests/Usings.cs index 3c66e0be2..0ade8d3c1 100644 --- a/tests/Testcontainers.Cassandra.Tests/Usings.cs +++ b/tests/Testcontainers.Cassandra.Tests/Usings.cs @@ -4,5 +4,9 @@ global using System.Threading.Tasks; global using Cassandra; global using Cassandra.Data; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; -global using Xunit; \ No newline at end of file +global using JetBrains.Annotations; +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs b/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs index 7d423065b..139d828b5 100644 --- a/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs +++ b/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs @@ -1,25 +1,13 @@ namespace Testcontainers.ClickHouse; -public sealed class ClickHouseContainerTest : IAsyncLifetime +public abstract class ClickHouseContainerTest(ClickHouseContainerTest.ClickHouseFixture fixture) { - private readonly ClickHouseContainer _clickHouseContainer = new ClickHouseBuilder().Build(); - - public Task InitializeAsync() - { - return _clickHouseContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _clickHouseContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new ClickHouseConnection(_clickHouseContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -36,11 +24,31 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _clickHouseContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); Assert.Empty(execResult.Stderr); } + + [UsedImplicitly] + public sealed class ClickHouseContainerDefaultConfiguration(ClickHouseFixture fixture) : ClickHouseContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class ClickHouseContainerWaitForDatabase(ClickHouseWaitForDatabaseFixture fixture) : ClickHouseContainerTest(fixture), IClassFixture; + + public class ClickHouseFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + { + public override DbProviderFactory DbProviderFactory => ClickHouseConnectionFactory.Instance; + } + + [UsedImplicitly] + public sealed class ClickHouseWaitForDatabaseFixture(IMessageSink messageSink) : ClickHouseFixture(messageSink) + { + protected override ClickHouseBuilder Configure(ClickHouseBuilder builder) + { + return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj b/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj index b357c04c4..43b725e42 100644 --- a/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj +++ b/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.ClickHouse.Tests/Usings.cs b/tests/Testcontainers.ClickHouse.Tests/Usings.cs index 349e20d26..28abe4d11 100644 --- a/tests/Testcontainers.ClickHouse.Tests/Usings.cs +++ b/tests/Testcontainers.ClickHouse.Tests/Usings.cs @@ -2,5 +2,9 @@ global using System.Data.Common; global using System.Threading.Tasks; global using ClickHouse.Client.ADO; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; -global using Xunit; \ No newline at end of file +global using JetBrains.Annotations; +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs b/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs index 9c3f37566..7a4b4f980 100644 --- a/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs +++ b/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs @@ -1,25 +1,13 @@ namespace Testcontainers.CockroachDb; -public sealed class CockroachDbContainerTest : IAsyncLifetime +public abstract class CockroachDbContainerTest(CockroachDbContainerTest.CockroachDbFixture fixture) { - private readonly CockroachDbContainer _cockroachDbContainer = new CockroachDbBuilder().Build(); - - public Task InitializeAsync() - { - return _cockroachDbContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _cockroachDbContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new NpgsqlConnection(_cockroachDbContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -36,11 +24,31 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _cockroachDbContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); Assert.Empty(execResult.Stderr); } + + [UsedImplicitly] + public sealed class CockroachDbContainerDefaultConfiguration(CockroachDbFixture fixture) : CockroachDbContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class CockroachDbContainerWaitForDatabase(CockroachDbFixtureWaitForDatabase fixture) : CockroachDbContainerTest(fixture), IClassFixture; + + public class CockroachDbFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + { + public override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; + } + + [UsedImplicitly] + public sealed class CockroachDbFixtureWaitForDatabase(IMessageSink messageSink) : CockroachDbFixture(messageSink) + { + protected override CockroachDbBuilder Configure(CockroachDbBuilder builder) + { + return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.CockroachDb.Tests/Testcontainers.CockroachDb.Tests.csproj b/tests/Testcontainers.CockroachDb.Tests/Testcontainers.CockroachDb.Tests.csproj index e50fd8d13..8106c3121 100644 --- a/tests/Testcontainers.CockroachDb.Tests/Testcontainers.CockroachDb.Tests.csproj +++ b/tests/Testcontainers.CockroachDb.Tests/Testcontainers.CockroachDb.Tests.csproj @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.CockroachDb.Tests/Usings.cs b/tests/Testcontainers.CockroachDb.Tests/Usings.cs index 576c631bf..703169015 100644 --- a/tests/Testcontainers.CockroachDb.Tests/Usings.cs +++ b/tests/Testcontainers.CockroachDb.Tests/Usings.cs @@ -1,6 +1,10 @@ global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using Npgsql; -global using Xunit; \ No newline at end of file +global using JetBrains.Annotations; +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs index 220b90619..5dc14aff7 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs +++ b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs @@ -93,4 +93,13 @@ public FirebirdSqlSysdba() { } } + + [UsedImplicitly] + public sealed class FirebirdSqlWaitForDatabase : FirebirdSqlContainerTest + { + public FirebirdSqlWaitForDatabase() + : base(new FirebirdSqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(FirebirdClientFactory.Instance)).Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.FirebirdSql.Tests/Usings.cs b/tests/Testcontainers.FirebirdSql.Tests/Usings.cs index 28861332f..d4af6d027 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/Usings.cs +++ b/tests/Testcontainers.FirebirdSql.Tests/Usings.cs @@ -1,6 +1,7 @@ global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using FirebirdSql.Data.FirebirdClient; global using JetBrains.Annotations; diff --git a/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs b/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs index acd8480c5..fbe9293a6 100644 --- a/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs +++ b/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs @@ -66,4 +66,13 @@ public MariaDbRootConfiguration() { } } + + [UsedImplicitly] + public sealed class MariaDbWaitForDatabase : MariaDbContainerTest + { + public MariaDbWaitForDatabase() + : base(new MariaDbBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(MySqlConnectorFactory.Instance)).Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.MariaDb.Tests/Usings.cs b/tests/Testcontainers.MariaDb.Tests/Usings.cs index 5ae65ce1e..a28a4f9a6 100644 --- a/tests/Testcontainers.MariaDb.Tests/Usings.cs +++ b/tests/Testcontainers.MariaDb.Tests/Usings.cs @@ -1,6 +1,7 @@ global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using MySqlConnector; diff --git a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs index 36a144af4..f59996ce5 100644 --- a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs +++ b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs @@ -61,4 +61,13 @@ public MsSqlDefaultConfiguration() } } // # --8<-- [end:CreateMsSqlContainer] + + [UsedImplicitly] + public sealed class MsSqlWaitForDatabase : MsSqlContainerTest + { + public MsSqlWaitForDatabase() + : base(new MsSqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(SqlClientFactory.Instance)).Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.MsSql.Tests/Usings.cs b/tests/Testcontainers.MsSql.Tests/Usings.cs index 493aff2cb..69077516e 100644 --- a/tests/Testcontainers.MsSql.Tests/Usings.cs +++ b/tests/Testcontainers.MsSql.Tests/Usings.cs @@ -1,6 +1,7 @@ global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using Microsoft.Data.SqlClient; diff --git a/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs b/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs index c5e6d5e3a..02a9dca3f 100644 --- a/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs +++ b/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs @@ -76,4 +76,13 @@ public GitHubIssue1142() { } } + + [UsedImplicitly] + public sealed class MySqlWaitForDatabase : MySqlContainerTest + { + public MySqlWaitForDatabase() + : base(new MySqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(MySqlConnectorFactory.Instance)).Build()) + { + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.MySql.Tests/Usings.cs b/tests/Testcontainers.MySql.Tests/Usings.cs index 5ae65ce1e..a28a4f9a6 100644 --- a/tests/Testcontainers.MySql.Tests/Usings.cs +++ b/tests/Testcontainers.MySql.Tests/Usings.cs @@ -1,6 +1,7 @@ global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using MySqlConnector; diff --git a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs index e0bf81335..6dcc80077 100644 --- a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs +++ b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs @@ -32,7 +32,7 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - public abstract class OracleFixture(IMessageSink messageSink, string edition, int? version, string database = null) : DbContainerFixture(messageSink) + public abstract class OracleFixture(IMessageSink messageSink, string edition, int? version, string database = null, bool waitForDatabase = false) : DbContainerFixture(messageSink) { public override DbProviderFactory DbProviderFactory => OracleClientFactory.Instance; @@ -40,11 +40,16 @@ protected override OracleBuilder Configure(OracleBuilder builder) { if (edition == null && version == null) { - return builder; + return Apply(builder, oracle => oracle); } var image = $"gvenzl/oracle-{edition}:{version}-slim-faststart"; - return database == null ? builder.WithImage(image) : builder.WithImage(image).WithDatabase(database); + return database == null ? Apply(builder, oracle => oracle.WithImage(image)) : Apply(builder, oracle => oracle.WithImage(image).WithDatabase(database)); + } + + private OracleBuilder Apply(OracleBuilder builder, Func configure) + { + return waitForDatabase ? configure(builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory))) : configure(builder); } } @@ -56,6 +61,7 @@ protected override OracleBuilder Configure(OracleBuilder builder) #if ORACLE_11 [UsedImplicitly] public sealed class Oracle11(Oracle11Fixture fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle11Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 11); + [UsedImplicitly] public sealed class Oracle11FixtureWaitForDatabase(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 11, waitForDatabase: true); #endif #if ORACLE_18 @@ -63,7 +69,7 @@ protected override OracleBuilder Configure(OracleBuilder builder) [UsedImplicitly] public sealed class Oracle18Default(Oracle18FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle18Scott(Oracle18FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle18Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18); - [UsedImplicitly] public sealed class Oracle18FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "XEPDB1"); + [UsedImplicitly] public sealed class Oracle18FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "XEPDB1", waitForDatabase: true); [UsedImplicitly] public sealed class Oracle18FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "SCOTT"); #endif @@ -72,7 +78,7 @@ protected override OracleBuilder Configure(OracleBuilder builder) [UsedImplicitly] public sealed class Oracle21Default(Oracle21FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle21Scott(Oracle21FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle21Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21); - [UsedImplicitly] public sealed class Oracle21FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "XEPDB1"); + [UsedImplicitly] public sealed class Oracle21FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "XEPDB1", waitForDatabase: true); [UsedImplicitly] public sealed class Oracle21FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "SCOTT"); #endif @@ -81,7 +87,7 @@ protected override OracleBuilder Configure(OracleBuilder builder) [UsedImplicitly] public sealed class Oracle23Default(Oracle23FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle23Scott(Oracle23FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; [UsedImplicitly] public sealed class Oracle23Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23); - [UsedImplicitly] public sealed class Oracle23FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "FREEPDB1"); + [UsedImplicitly] public sealed class Oracle23FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "FREEPDB1", waitForDatabase: true); [UsedImplicitly] public sealed class Oracle23FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "SCOTT"); #endif } \ No newline at end of file diff --git a/tests/Testcontainers.Oracle.Tests/Usings.cs b/tests/Testcontainers.Oracle.Tests/Usings.cs index e1e61a204..17d816308 100644 --- a/tests/Testcontainers.Oracle.Tests/Usings.cs +++ b/tests/Testcontainers.Oracle.Tests/Usings.cs @@ -1,6 +1,8 @@ +global using System; global using System.Data; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using Oracle.ManagedDataAccess.Client; diff --git a/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs b/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs index 7488c8312..067f2d1e6 100644 --- a/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs +++ b/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs @@ -1,26 +1,16 @@ namespace Testcontainers.PostgreSql; -public sealed class PostgreSqlContainerTest : IAsyncLifetime +public abstract class PostgreSqlContainerTest(ITestOutputHelper testOutputHelper) : DbContainerTest(testOutputHelper) { - // # --8<-- [start:UsePostgreSqlContainer] - private readonly PostgreSqlContainer _postgreSqlContainer = new PostgreSqlBuilder().Build(); - - public Task InitializeAsync() - { - return _postgreSqlContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _postgreSqlContainer.DisposeAsync().AsTask(); - } + public override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; + // # --8<-- [start:UsePostgreSqlContainer] [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new NpgsqlConnection(_postgreSqlContainer.GetConnectionString()); + using DbConnection connection = CreateConnection(); // When connection.Open(); @@ -37,7 +27,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _postgreSqlContainer.ExecScriptAsync(scriptContent) + var execResult = await Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -86,4 +76,16 @@ public PostgreSqlFixture(IMessageSink messageSink) { } } + + [UsedImplicitly] + public sealed class PostgreSqlDefaultConfiguration(ITestOutputHelper testOutputHelper) : PostgreSqlContainerTest(testOutputHelper); + + [UsedImplicitly] + public sealed class PostgreSqlWaitForDatabase(ITestOutputHelper testOutputHelper) : PostgreSqlContainerTest(testOutputHelper) + { + protected override PostgreSqlBuilder Configure(PostgreSqlBuilder builder) + { + return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + } + } } \ No newline at end of file diff --git a/tests/Testcontainers.PostgreSql.Tests/Usings.cs b/tests/Testcontainers.PostgreSql.Tests/Usings.cs index 2d2ea46e3..a9392be0b 100644 --- a/tests/Testcontainers.PostgreSql.Tests/Usings.cs +++ b/tests/Testcontainers.PostgreSql.Tests/Usings.cs @@ -3,6 +3,7 @@ global using System.Data.Common; global using System.Threading; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using Npgsql; From f579265e5180a960183027f0db59663a11a17a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 9 Apr 2025 22:30:16 +0200 Subject: [PATCH 2/9] Use the standard wait strategy modifier instead of frequency + timeout --- .../WaitStrategies/IWaitForContainerOS.cs | 11 ++----- .../UntilDatabaseIsAvailable.cs | 31 ++----------------- .../WaitStrategies/WaitForContainerOS.cs | 4 +-- 3 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs index 7b22c6196..5509b4582 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs @@ -118,20 +118,13 @@ public interface IWaitForContainerOS /// Waits until a connection to the database can be successfully opened. /// /// The used to create the database connection. - /// - /// The retry frequency until either the connection is successfully opened or the is reached. - /// Defaults to 1 second if not specified. - /// - /// - /// The maximum duration to retry. Once reached, a is thrown. - /// Defaults to 5 minutes if not specified. - /// + /// The wait strategy modifier to cancel the readiness check. /// A configured instance of . /// /// This wait strategy must only be applied to containers implementing the interface. /// [PublicAPI] - IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency = default, TimeSpan timeout = default); + IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action waitStrategyModifier = null); /// /// Returns a collection with all configured wait strategies. diff --git a/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs index 129147ac4..23b6d8531 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs @@ -2,24 +2,16 @@ namespace DotNet.Testcontainers.Configurations { using System; using System.Data.Common; - using System.Diagnostics; using System.Threading.Tasks; using DotNet.Testcontainers.Containers; internal class UntilDatabaseIsAvailable : IWaitUntil { - private static readonly TimeSpan DefaultFrequency = TimeSpan.FromSeconds(1); - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(5); - private readonly DbProviderFactory _dbProviderFactory; - private readonly TimeSpan _frequency; - private readonly TimeSpan _timeout; - public UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency, TimeSpan timeout) + public UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory) { _dbProviderFactory = dbProviderFactory; - _frequency = frequency == TimeSpan.Zero ? DefaultFrequency : frequency; - _timeout = timeout == TimeSpan.Zero ? DefaultTimeout : timeout; } public async Task UntilAsync(IContainer container) @@ -30,33 +22,16 @@ public async Task UntilAsync(IContainer container) $"{container.GetType().FullName} does not implement the {nameof(IDatabaseContainer)} interface."); } - var stopwatch = Stopwatch.StartNew(); - - var connectionString = dbContainer.GetConnectionString(); - while (!await IsAvailableAsync(connectionString, stopwatch)) - { - await Task.Delay(_frequency); - } - - return true; - } - - private async Task IsAvailableAsync(string connectionString, Stopwatch stopwatch) - { using (var connection = _dbProviderFactory.CreateConnection() ?? throw new InvalidOperationException($"{_dbProviderFactory.GetType().FullName}.CreateConnection() returned null.")) { - connection.ConnectionString = connectionString; + connection.ConnectionString = dbContainer.GetConnectionString(); try { await connection.OpenAsync(); return true; } - catch (Exception exception) + catch { - if (stopwatch.Elapsed > _timeout) - { - throw new TimeoutException($"The database was not available at \"{connectionString}\" after waiting for {_timeout.TotalSeconds:F0} seconds.", exception); - } return false; } } diff --git a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs index 8ade90be0..91777eecd 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/WaitForContainerOS.cs @@ -82,9 +82,9 @@ public virtual IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = } /// - public virtual IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, TimeSpan frequency, TimeSpan timeout) + public virtual IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action waitStrategyModifier = null) { - return AddCustomWaitStrategy(new UntilDatabaseIsAvailable(dbProviderFactory, frequency, timeout)); + return AddCustomWaitStrategy(new UntilDatabaseIsAvailable(dbProviderFactory), waitStrategyModifier); } /// From a15cfc5340c1237da0db2866fa8182a909e19a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 11 Apr 2025 22:07:39 +0200 Subject: [PATCH 3/9] Use DbContainerFixture in Db2ContainerTest --- .../Db2ContainerTest.cs | 25 ++++++++----------- .../Testcontainers.Db2.Tests.csproj | 1 + tests/Testcontainers.Db2.Tests/Usings.cs | 5 +++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs index 3b74470d7..5509a5860 100644 --- a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs +++ b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs @@ -1,26 +1,15 @@ namespace Testcontainers.Db2; -public sealed class Db2ContainerTest : IAsyncLifetime +public sealed class Db2ContainerTest(Db2ContainerTest.Db2Fixture fixture) : IClassFixture { // # --8<-- [start:UseDb2Container] - private readonly Db2Container _db2Container = new Db2Builder().WithAcceptLicenseAgreement(true).Build(); - - public Task InitializeAsync() - { - return _db2Container.StartAsync(); - } - - public Task DisposeAsync() - { - return _db2Container.DisposeAsync().AsTask(); - } [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new DB2Connection(_db2Container.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -37,7 +26,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1 FROM SYSIBM.SYSDUMMY1;"; // When - var execResult = await _db2Container.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -45,4 +34,12 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } // # --8<-- [end:UseDb2Container] + + [UsedImplicitly] + public class Db2Fixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + { + public override DbProviderFactory DbProviderFactory => DB2Factory.Instance; + + protected override Db2Builder Configure(Db2Builder builder) => builder.WithAcceptLicenseAgreement(true); + } } \ No newline at end of file diff --git a/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj b/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj index 54bfc255b..70de0e737 100644 --- a/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj +++ b/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/tests/Testcontainers.Db2.Tests/Usings.cs b/tests/Testcontainers.Db2.Tests/Usings.cs index 2a9a1fa23..ab77283c3 100644 --- a/tests/Testcontainers.Db2.Tests/Usings.cs +++ b/tests/Testcontainers.Db2.Tests/Usings.cs @@ -5,4 +5,7 @@ global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using IBM.Data.Db2; -global using Xunit; \ No newline at end of file +global using JetBrains.Annotations; +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file From ca8a1784e907039cbf09da0cb1fafbbe24bd0d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 11 Apr 2025 22:30:36 +0200 Subject: [PATCH 4/9] Use DbContainerFixture in FirebirdSqlContainerTest --- .../FirebirdSqlContainerTest.cs | 81 +++++++------------ .../Testcontainers.FirebirdSql.Tests.csproj | 1 + .../Usings.cs | 4 +- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs index 5dc14aff7..eabfb81ec 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs +++ b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs @@ -1,30 +1,13 @@ namespace Testcontainers.FirebirdSql; -public abstract class FirebirdSqlContainerTest : IAsyncLifetime +public abstract class FirebirdSqlContainerTest(FirebirdSqlContainerTest.FirebirdSqlFixture fixture) { - private readonly FirebirdSqlContainer _firebirdSqlContainer; - - private FirebirdSqlContainerTest(FirebirdSqlContainer firebirdSqlContainer) - { - _firebirdSqlContainer = firebirdSqlContainer; - } - - public Task InitializeAsync() - { - return _firebirdSqlContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _firebirdSqlContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new FbConnection(_firebirdSqlContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -41,7 +24,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1 FROM RDB$DATABASE;"; // When - var execResult = await _firebirdSqlContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -49,57 +32,53 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - [UsedImplicitly] - public sealed class FirebirdSql25Sc : FirebirdSqlContainerTest + public abstract class FirebirdSqlFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) { - public FirebirdSql25Sc() - : base(new FirebirdSqlBuilder().WithImage("jacobalberty/firebird:2.5-sc").Build()) - { - } + public override DbProviderFactory DbProviderFactory => FirebirdClientFactory.Instance; } [UsedImplicitly] - public sealed class FirebirdSql25Ss : FirebirdSqlContainerTest + public sealed class FirebirdSqlDefault(FirebirdSqlDefaultFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSqlDefaultFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink); + + [UsedImplicitly] + public sealed class FirebirdSql25Sc(FirebirdSql25ScFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSql25ScFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) { - public FirebirdSql25Ss() - : base(new FirebirdSqlBuilder().WithImage("jacobalberty/firebird:2.5-ss").Build()) - { - } + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:2.5-sc"); } [UsedImplicitly] - public sealed class FirebirdSql30 : FirebirdSqlContainerTest + public sealed class FirebirdSql25Ss(FirebirdSql25SsFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSql25SsFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) { - public FirebirdSql30() - : base(new FirebirdSqlBuilder().WithImage("jacobalberty/firebird:v3.0").Build()) - { - } + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:2.5-ss"); } [UsedImplicitly] - public sealed class FirebirdSql40 : FirebirdSqlContainerTest + public sealed class FirebirdSql30(FirebirdSql30Fixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSql30Fixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) { - public FirebirdSql40() - : base(new FirebirdSqlBuilder().WithImage("jacobalberty/firebird:v4.0").Build()) - { - } + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:v3.0"); } [UsedImplicitly] - public sealed class FirebirdSqlSysdba : FirebirdSqlContainerTest + public sealed class FirebirdSqlSysdba(FirebirdSqlSysdbaFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSqlSysdbaFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) { - public FirebirdSqlSysdba() - : base(new FirebirdSqlBuilder().WithUsername("sysdba").WithPassword("some-password").Build()) - { - } + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithUsername("sysdba").WithPassword("some-password"); } [UsedImplicitly] - public sealed class FirebirdSqlWaitForDatabase : FirebirdSqlContainerTest + public sealed class FirebirdSqlWaitForDatabase(FirebirdSqlWaitForDatabaseFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] + public sealed class FirebirdSqlWaitForDatabaseFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) { - public FirebirdSqlWaitForDatabase() - : base(new FirebirdSqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(FirebirdClientFactory.Instance)).Build()) - { - } + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } } \ No newline at end of file diff --git a/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj b/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj index ab085ac17..6dd104ec3 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj +++ b/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.FirebirdSql.Tests/Usings.cs b/tests/Testcontainers.FirebirdSql.Tests/Usings.cs index d4af6d027..b0801edfa 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/Usings.cs +++ b/tests/Testcontainers.FirebirdSql.Tests/Usings.cs @@ -5,4 +5,6 @@ global using DotNet.Testcontainers.Commons; global using FirebirdSql.Data.FirebirdClient; global using JetBrains.Annotations; -global using Xunit; \ No newline at end of file +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file From 6f72798cc669d05a68c8fa6e03fe7f1b6f12d407 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:35:02 +0200 Subject: [PATCH 5/9] chore: Use DbContainerFixture for every ADO .NET compatible container --- src/Testcontainers.EventHubs/Usings.cs | 1 - .../CassandraContainerTest.cs | 29 ++++--- .../ClickHouseContainerTest.cs | 29 ++++--- .../CockroachDbContainerTest.cs | 29 ++++--- .../Usings.cs | 2 +- .../Db2ContainerTest.cs | 26 ++++-- tests/Testcontainers.Db2.Tests/Usings.cs | 1 + .../FirebirdSqlContainerTest.cs | 75 ++++++++++------- .../MariaDbContainerTest.cs | 55 ++++--------- .../Testcontainers.MariaDb.Tests.csproj | 1 + tests/Testcontainers.MariaDb.Tests/Usings.cs | 4 +- .../MsSqlContainerTest.cs | 53 +++++------- .../Testcontainers.MsSql.Tests.csproj | 1 + tests/Testcontainers.MsSql.Tests/Usings.cs | 4 +- .../MySqlContainerTest.cs | 80 +++++++++---------- .../Testcontainers.MySql.Tests.csproj | 1 + tests/Testcontainers.MySql.Tests/Usings.cs | 4 +- .../OracleContainerTest.cs | 6 +- .../PostgreSqlContainerTest.cs | 42 +++++----- tests/Testcontainers.Qdrant.Tests/Usings.cs | 1 - 20 files changed, 227 insertions(+), 217 deletions(-) diff --git a/src/Testcontainers.EventHubs/Usings.cs b/src/Testcontainers.EventHubs/Usings.cs index b68f67969..aac442571 100644 --- a/src/Testcontainers.EventHubs/Usings.cs +++ b/src/Testcontainers.EventHubs/Usings.cs @@ -4,7 +4,6 @@ global using System.Linq; global using System.Text; global using System.Text.Json; -global using System.Threading.Tasks; global using Docker.DotNet.Models; global using DotNet.Testcontainers; global using DotNet.Testcontainers.Builders; diff --git a/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs b/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs index 84c2ce181..d351eab42 100644 --- a/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs +++ b/tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs @@ -1,6 +1,6 @@ namespace Testcontainers.Cassandra; -public abstract class CassandraContainerTest(CassandraContainerTest.CassandraFixture fixture) +public abstract class CassandraContainerTest(CassandraContainerTest.CassandraDefaultFixture fixture) { // # --8<-- [start:UseCassandraContainer] [Fact] @@ -57,23 +57,26 @@ public async Task ExecScriptAsyncReturnsSuccess() Assert.Empty(execResult.Stderr); } - [UsedImplicitly] - public sealed class CassandraContainerDefaultConfiguration(CassandraFixture fixture) : CassandraContainerTest(fixture), IClassFixture; - - [UsedImplicitly] - public sealed class CassandraContainerWaitForDatabase(CassandraFixtureWaitForDatabase fixture) : CassandraContainerTest(fixture), IClassFixture; - - public class CassandraFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + public class CassandraDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public override DbProviderFactory DbProviderFactory => CqlProviderFactory.Instance; + public override DbProviderFactory DbProviderFactory + => CqlProviderFactory.Instance; } [UsedImplicitly] - public sealed class CassandraFixtureWaitForDatabase(IMessageSink messageSink) : CassandraFixture(messageSink) + public class CassandraWaitForDatabaseFixture(IMessageSink messageSink) + : CassandraDefaultFixture(messageSink) { protected override CassandraBuilder Configure(CassandraBuilder builder) - { - return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); - } + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class CassandraDefaultConfiguration(CassandraDefaultFixture fixture) + : CassandraContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class CassandraWaitForDatabaseConfiguration(CassandraWaitForDatabaseFixture fixture) + : CassandraContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs b/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs index 139d828b5..cd3fb97b3 100644 --- a/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs +++ b/tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs @@ -1,6 +1,6 @@ namespace Testcontainers.ClickHouse; -public abstract class ClickHouseContainerTest(ClickHouseContainerTest.ClickHouseFixture fixture) +public abstract class ClickHouseContainerTest(ClickHouseContainerTest.ClickHouseDefaultFixture fixture) { [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] @@ -32,23 +32,26 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - [UsedImplicitly] - public sealed class ClickHouseContainerDefaultConfiguration(ClickHouseFixture fixture) : ClickHouseContainerTest(fixture), IClassFixture; - - [UsedImplicitly] - public sealed class ClickHouseContainerWaitForDatabase(ClickHouseWaitForDatabaseFixture fixture) : ClickHouseContainerTest(fixture), IClassFixture; - - public class ClickHouseFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + public class ClickHouseDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public override DbProviderFactory DbProviderFactory => ClickHouseConnectionFactory.Instance; + public override DbProviderFactory DbProviderFactory + => ClickHouseConnectionFactory.Instance; } [UsedImplicitly] - public sealed class ClickHouseWaitForDatabaseFixture(IMessageSink messageSink) : ClickHouseFixture(messageSink) + public class ClickHouseWaitForDatabaseFixture(IMessageSink messageSink) + : ClickHouseDefaultFixture(messageSink) { protected override ClickHouseBuilder Configure(ClickHouseBuilder builder) - { - return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); - } + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class ClickHouseDefaultConfiguration(ClickHouseDefaultFixture fixture) + : ClickHouseContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class ClickHouseWaitForDatabaseConfiguration(ClickHouseWaitForDatabaseFixture fixture) + : ClickHouseContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs b/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs index 7a4b4f980..f14917b63 100644 --- a/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs +++ b/tests/Testcontainers.CockroachDb.Tests/CockroachDbContainerTest.cs @@ -1,6 +1,6 @@ namespace Testcontainers.CockroachDb; -public abstract class CockroachDbContainerTest(CockroachDbContainerTest.CockroachDbFixture fixture) +public abstract class CockroachDbContainerTest(CockroachDbContainerTest.CockroachDbDefaultFixture fixture) { [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] @@ -31,24 +31,27 @@ public async Task ExecScriptReturnsSuccessful() Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); Assert.Empty(execResult.Stderr); } - - [UsedImplicitly] - public sealed class CockroachDbContainerDefaultConfiguration(CockroachDbFixture fixture) : CockroachDbContainerTest(fixture), IClassFixture; - - [UsedImplicitly] - public sealed class CockroachDbContainerWaitForDatabase(CockroachDbFixtureWaitForDatabase fixture) : CockroachDbContainerTest(fixture), IClassFixture; - public class CockroachDbFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + public class CockroachDbDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; + public override DbProviderFactory DbProviderFactory + => NpgsqlFactory.Instance; } [UsedImplicitly] - public sealed class CockroachDbFixtureWaitForDatabase(IMessageSink messageSink) : CockroachDbFixture(messageSink) + public class CockroachDbWaitForDatabaseFixture(IMessageSink messageSink) + : CockroachDbDefaultFixture(messageSink) { protected override CockroachDbBuilder Configure(CockroachDbBuilder builder) - { - return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); - } + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class CockroachDbDefaultConfiguration(CockroachDbDefaultFixture fixture) + : CockroachDbContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class CockroachDbWaitForDatabaseConfiguration(CockroachDbWaitForDatabaseFixture fixture) + : CockroachDbContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.CockroachDb.Tests/Usings.cs b/tests/Testcontainers.CockroachDb.Tests/Usings.cs index 703169015..5437a7d93 100644 --- a/tests/Testcontainers.CockroachDb.Tests/Usings.cs +++ b/tests/Testcontainers.CockroachDb.Tests/Usings.cs @@ -3,8 +3,8 @@ global using System.Threading.Tasks; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; -global using Npgsql; global using JetBrains.Annotations; +global using Npgsql; global using Testcontainers.Xunit; global using Xunit; global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs index 5509a5860..79a141c8e 100644 --- a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs +++ b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs @@ -1,9 +1,8 @@ namespace Testcontainers.Db2; -public sealed class Db2ContainerTest(Db2ContainerTest.Db2Fixture fixture) : IClassFixture +public abstract class Db2ContainerTest(Db2ContainerTest.Db2DefaultFixture fixture) { // # --8<-- [start:UseDb2Container] - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() @@ -35,11 +34,26 @@ public async Task ExecScriptReturnsSuccessful() } // # --8<-- [end:UseDb2Container] - [UsedImplicitly] - public class Db2Fixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + public class Db2DefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public override DbProviderFactory DbProviderFactory => DB2Factory.Instance; + public override DbProviderFactory DbProviderFactory + => DB2Factory.Instance; + } - protected override Db2Builder Configure(Db2Builder builder) => builder.WithAcceptLicenseAgreement(true); + [UsedImplicitly] + public class Db2WaitForDatabaseFixture(IMessageSink messageSink) + : Db2DefaultFixture(messageSink) + { + protected override Db2Builder Configure(Db2Builder builder) + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class Db2DefaultConfiguration(Db2DefaultFixture fixture) + : Db2ContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class Db2WaitForDatabaseConfiguration(Db2WaitForDatabaseFixture fixture) + : Db2ContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.Db2.Tests/Usings.cs b/tests/Testcontainers.Db2.Tests/Usings.cs index ab77283c3..d6b581c47 100644 --- a/tests/Testcontainers.Db2.Tests/Usings.cs +++ b/tests/Testcontainers.Db2.Tests/Usings.cs @@ -3,6 +3,7 @@ global using System.Text.RegularExpressions; global using System.Data.Common; global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Commons; global using IBM.Data.Db2; global using JetBrains.Annotations; diff --git a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs index eabfb81ec..d29af98a4 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs +++ b/tests/Testcontainers.FirebirdSql.Tests/FirebirdSqlContainerTest.cs @@ -1,6 +1,6 @@ namespace Testcontainers.FirebirdSql; -public abstract class FirebirdSqlContainerTest(FirebirdSqlContainerTest.FirebirdSqlFixture fixture) +public abstract class FirebirdSqlContainerTest(FirebirdSqlContainerTest.FirebirdSqlDefaultFixture fixture) { [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] @@ -32,53 +32,74 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - public abstract class FirebirdSqlFixture(IMessageSink messageSink) : DbContainerFixture(messageSink) + public class FirebirdSqlDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public override DbProviderFactory DbProviderFactory => FirebirdClientFactory.Instance; + public override DbProviderFactory DbProviderFactory + => FirebirdClientFactory.Instance; } [UsedImplicitly] - public sealed class FirebirdSqlDefault(FirebirdSqlDefaultFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; - [UsedImplicitly] - public sealed class FirebirdSqlDefaultFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink); + public class FirebirdSqlWaitForDatabaseFixture(IMessageSink messageSink) + : FirebirdSqlDefaultFixture(messageSink) + { + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + } [UsedImplicitly] - public sealed class FirebirdSql25Sc(FirebirdSql25ScFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; - [UsedImplicitly] - public sealed class FirebirdSql25ScFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) + public class FirebirdSql25ScFixture(IMessageSink messageSink) + : FirebirdSqlDefaultFixture(messageSink) { - protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:2.5-sc"); + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) + => builder.WithImage("jacobalberty/firebird:2.5-sc"); } [UsedImplicitly] - public sealed class FirebirdSql25Ss(FirebirdSql25SsFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; - [UsedImplicitly] - public sealed class FirebirdSql25SsFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) + public class FirebirdSql25SsFixture(IMessageSink messageSink) + : FirebirdSqlDefaultFixture(messageSink) { - protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:2.5-ss"); + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) + => builder.WithImage("jacobalberty/firebird:2.5-ss"); } [UsedImplicitly] - public sealed class FirebirdSql30(FirebirdSql30Fixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; - [UsedImplicitly] - public sealed class FirebirdSql30Fixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) + public class FirebirdSql30Fixture(IMessageSink messageSink) + : FirebirdSqlDefaultFixture(messageSink) { - protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithImage("jacobalberty/firebird:v3.0"); + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) + => builder.WithImage("jacobalberty/firebird:v3.0"); } [UsedImplicitly] - public sealed class FirebirdSqlSysdba(FirebirdSqlSysdbaFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; - [UsedImplicitly] - public sealed class FirebirdSqlSysdbaFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) + public class FirebirdSqlSysdbaFixture(IMessageSink messageSink) + : FirebirdSqlDefaultFixture(messageSink) { - protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithUsername("sysdba").WithPassword("some-password"); + protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) + => builder.WithUsername("sysdba").WithPassword("some-password"); } [UsedImplicitly] - public sealed class FirebirdSqlWaitForDatabase(FirebirdSqlWaitForDatabaseFixture fixture) : FirebirdSqlContainerTest(fixture), IClassFixture; + public sealed class FirebirdSqlDefaultConfiguration(FirebirdSqlDefaultFixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; + [UsedImplicitly] - public sealed class FirebirdSqlWaitForDatabaseFixture(IMessageSink messageSink) : FirebirdSqlFixture(messageSink) - { - protected override FirebirdSqlBuilder Configure(FirebirdSqlBuilder builder) => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); - } + public sealed class FirebirdSqlWaitForDatabaseConfiguration(FirebirdSqlWaitForDatabaseFixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class FirebirdSql25ScConfiguration(FirebirdSql25ScFixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class FirebirdSql25SsConfiguration(FirebirdSql25SsFixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class FirebirdSql30Configuration(FirebirdSql30Fixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class FirebirdSqlSysdbaConfiguration(FirebirdSqlSysdbaFixture fixture) + : FirebirdSqlContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs b/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs index fbe9293a6..ae0056bac 100644 --- a/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs +++ b/tests/Testcontainers.MariaDb.Tests/MariaDbContainerTest.cs @@ -1,30 +1,13 @@ namespace Testcontainers.MariaDb; -public abstract class MariaDbContainerTest : IAsyncLifetime +public abstract class MariaDbContainerTest(MariaDbContainerTest.MariaDbDefaultFixture fixture) { - private readonly MariaDbContainer _mariaDbContainer; - - protected MariaDbContainerTest(MariaDbContainer mariaDbContainer) - { - _mariaDbContainer = mariaDbContainer; - } - - public Task InitializeAsync() - { - return _mariaDbContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _mariaDbContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new MySqlConnection(_mariaDbContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -41,7 +24,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _mariaDbContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -49,30 +32,26 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - [UsedImplicitly] - public sealed class MariaDbUserConfiguration : MariaDbContainerTest + public class MariaDbDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public MariaDbUserConfiguration() - : base(new MariaDbBuilder().Build()) - { - } + public override DbProviderFactory DbProviderFactory + => MySqlConnectorFactory.Instance; } [UsedImplicitly] - public sealed class MariaDbRootConfiguration : MariaDbContainerTest + public class MariaDbWaitForDatabaseFixture(IMessageSink messageSink) + : MariaDbDefaultFixture(messageSink) { - public MariaDbRootConfiguration() - : base(new MariaDbBuilder().WithUsername("root").Build()) - { - } + protected override MariaDbBuilder Configure(MariaDbBuilder builder) + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } [UsedImplicitly] - public sealed class MariaDbWaitForDatabase : MariaDbContainerTest - { - public MariaDbWaitForDatabase() - : base(new MariaDbBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(MySqlConnectorFactory.Instance)).Build()) - { - } - } + public sealed class MariaDbDefaultConfiguration(MariaDbDefaultFixture fixture) + : MariaDbContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class MariaDbWaitForDatabaseConfiguration(MariaDbWaitForDatabaseFixture fixture) + : MariaDbContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.MariaDb.Tests/Testcontainers.MariaDb.Tests.csproj b/tests/Testcontainers.MariaDb.Tests/Testcontainers.MariaDb.Tests.csproj index bb56ee175..7de9413cc 100644 --- a/tests/Testcontainers.MariaDb.Tests/Testcontainers.MariaDb.Tests.csproj +++ b/tests/Testcontainers.MariaDb.Tests/Testcontainers.MariaDb.Tests.csproj @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.MariaDb.Tests/Usings.cs b/tests/Testcontainers.MariaDb.Tests/Usings.cs index a28a4f9a6..63dd23a10 100644 --- a/tests/Testcontainers.MariaDb.Tests/Usings.cs +++ b/tests/Testcontainers.MariaDb.Tests/Usings.cs @@ -5,4 +5,6 @@ global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using MySqlConnector; -global using Xunit; \ No newline at end of file +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs index f59996ce5..5ef312f25 100644 --- a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs +++ b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs @@ -1,31 +1,13 @@ namespace Testcontainers.MsSql; -public abstract class MsSqlContainerTest : IAsyncLifetime +public abstract class MsSqlContainerTest(MsSqlContainerTest.MsSqlDefaultFixture fixture) { - private readonly MsSqlContainer _msSqlContainer; - - public MsSqlContainerTest(MsSqlContainer msSqlContainer) - { - _msSqlContainer = msSqlContainer; - } - - // # --8<-- [start:UseMsSqlContainer] - public Task InitializeAsync() - { - return _msSqlContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _msSqlContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new SqlConnection(_msSqlContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -42,7 +24,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _msSqlContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -51,23 +33,26 @@ public async Task ExecScriptReturnsSuccessful() } // # --8<-- [end:UseMsSqlContainer] - // # --8<-- [start:CreateMsSqlContainer] - [UsedImplicitly] - public sealed class MsSqlDefaultConfiguration : MsSqlContainerTest + public class MsSqlDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public MsSqlDefaultConfiguration() - : base(new MsSqlBuilder().Build()) - { - } + public override DbProviderFactory DbProviderFactory + => SqlClientFactory.Instance; } - // # --8<-- [end:CreateMsSqlContainer] [UsedImplicitly] - public sealed class MsSqlWaitForDatabase : MsSqlContainerTest + public class MsSqlWaitForDatabaseFixture(IMessageSink messageSink) + : MsSqlDefaultFixture(messageSink) { - public MsSqlWaitForDatabase() - : base(new MsSqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(SqlClientFactory.Instance)).Build()) - { - } + protected override MsSqlBuilder Configure(MsSqlBuilder builder) + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class MsSqlDefaultConfiguration(MsSqlDefaultFixture fixture) + : MsSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class MsSqlWaitForDatabaseConfiguration(MsSqlWaitForDatabaseFixture fixture) + : MsSqlContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.MsSql.Tests/Testcontainers.MsSql.Tests.csproj b/tests/Testcontainers.MsSql.Tests/Testcontainers.MsSql.Tests.csproj index ad2862d61..550fa255e 100644 --- a/tests/Testcontainers.MsSql.Tests/Testcontainers.MsSql.Tests.csproj +++ b/tests/Testcontainers.MsSql.Tests/Testcontainers.MsSql.Tests.csproj @@ -15,6 +15,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.MsSql.Tests/Usings.cs b/tests/Testcontainers.MsSql.Tests/Usings.cs index 69077516e..8f7dd6f0b 100644 --- a/tests/Testcontainers.MsSql.Tests/Usings.cs +++ b/tests/Testcontainers.MsSql.Tests/Usings.cs @@ -5,4 +5,6 @@ global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using Microsoft.Data.SqlClient; -global using Xunit; \ No newline at end of file +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs b/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs index 02a9dca3f..6f93506cd 100644 --- a/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs +++ b/tests/Testcontainers.MySql.Tests/MySqlContainerTest.cs @@ -1,30 +1,13 @@ namespace Testcontainers.MySql; -public abstract class MySqlContainerTest : IAsyncLifetime +public abstract class MySqlContainerTest(MySqlContainerTest.MySqlDefaultFixture fixture) { - private readonly MySqlContainer _mySqlContainer; - - protected MySqlContainerTest(MySqlContainer mySqlContainer) - { - _mySqlContainer = mySqlContainer; - } - - public Task InitializeAsync() - { - return _mySqlContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _mySqlContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new MySqlConnection(_mySqlContainer.GetConnectionString()); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -41,7 +24,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await _mySqlContainer.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -49,40 +32,51 @@ public async Task ExecScriptReturnsSuccessful() Assert.Empty(execResult.Stderr); } - [UsedImplicitly] - public sealed class MySqlUserConfiguration : MySqlContainerTest + public class MySqlDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public MySqlUserConfiguration() - : base(new MySqlBuilder().Build()) - { - } + public override DbProviderFactory DbProviderFactory + => MySqlConnectorFactory.Instance; } [UsedImplicitly] - public sealed class MySqlRootConfiguration : MySqlContainerTest + public class MySqlWaitForDatabaseFixture(IMessageSink messageSink) + : MySqlDefaultFixture(messageSink) { - public MySqlRootConfiguration() - : base(new MySqlBuilder().WithUsername("root").Build()) - { - } + protected override MySqlBuilder Configure(MySqlBuilder builder) + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } [UsedImplicitly] - public sealed class GitHubIssue1142 : MySqlContainerTest + public class MySqlRootFixture(IMessageSink messageSink) + : MySqlDefaultFixture(messageSink) { - // https://github.com/testcontainers/testcontainers-dotnet/issues/1142. - public GitHubIssue1142() - : base(new MySqlBuilder().WithImage("mysql:8.0.28").Build()) - { - } + protected override MySqlBuilder Configure(MySqlBuilder builder) + => builder.WithUsername("root"); } - + [UsedImplicitly] - public sealed class MySqlWaitForDatabase : MySqlContainerTest + public class MySqlGitHubIssue1142Fixture(IMessageSink messageSink) + : MySqlDefaultFixture(messageSink) { - public MySqlWaitForDatabase() - : base(new MySqlBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(MySqlConnectorFactory.Instance)).Build()) - { - } + // https://github.com/testcontainers/testcontainers-dotnet/issues/1142. + protected override MySqlBuilder Configure(MySqlBuilder builder) + => builder.WithImage("mysql:8.0.28"); } + + [UsedImplicitly] + public sealed class MySqlDefaultConfiguration(MySqlDefaultFixture fixture) + : MySqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class MySqlWaitForDatabaseConfiguration(MySqlWaitForDatabaseFixture fixture) + : MySqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class MySqlRootConfiguration(MySqlRootFixture fixture) + : MySqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class MySqlGitHubIssue1142Configuration(MySqlGitHubIssue1142Fixture fixture) + : MySqlContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.MySql.Tests/Testcontainers.MySql.Tests.csproj b/tests/Testcontainers.MySql.Tests/Testcontainers.MySql.Tests.csproj index ef10916ae..fd0c3798e 100644 --- a/tests/Testcontainers.MySql.Tests/Testcontainers.MySql.Tests.csproj +++ b/tests/Testcontainers.MySql.Tests/Testcontainers.MySql.Tests.csproj @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.MySql.Tests/Usings.cs b/tests/Testcontainers.MySql.Tests/Usings.cs index a28a4f9a6..63dd23a10 100644 --- a/tests/Testcontainers.MySql.Tests/Usings.cs +++ b/tests/Testcontainers.MySql.Tests/Usings.cs @@ -5,4 +5,6 @@ global using DotNet.Testcontainers.Commons; global using JetBrains.Annotations; global using MySqlConnector; -global using Xunit; \ No newline at end of file +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs index 6dcc80077..0fcbb099c 100644 --- a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs +++ b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs @@ -1,13 +1,13 @@ namespace Testcontainers.Oracle; -public abstract class OracleContainerTest(OracleContainerTest.OracleFixture oracleFixture) +public abstract class OracleContainerTest(OracleContainerTest.OracleFixture fixture) { [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = oracleFixture.CreateConnection(); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -24,7 +24,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1 FROM DUAL;"; // When - var execResult = await oracleFixture.Container.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then diff --git a/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs b/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs index 067f2d1e6..3a29d6916 100644 --- a/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs +++ b/tests/Testcontainers.PostgreSql.Tests/PostgreSqlContainerTest.cs @@ -1,16 +1,14 @@ namespace Testcontainers.PostgreSql; -public abstract class PostgreSqlContainerTest(ITestOutputHelper testOutputHelper) : DbContainerTest(testOutputHelper) +public abstract class PostgreSqlContainerTest(PostgreSqlContainerTest.PostgreSqlDefaultFixture fixture) { - public override DbProviderFactory DbProviderFactory => NpgsqlFactory.Instance; - // # --8<-- [start:UsePostgreSqlContainer] [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = CreateConnection(); + using DbConnection connection = fixture.CreateConnection(); // When connection.Open(); @@ -27,7 +25,7 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1;"; // When - var execResult = await Container.ExecScriptAsync(scriptContent) + var execResult = await fixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then @@ -36,13 +34,13 @@ public async Task ExecScriptReturnsSuccessful() } // # --8<-- [end:UsePostgreSqlContainer] - public sealed class ReuseContainerTest : IClassFixture, IDisposable + public sealed class ReuseContainerTest : IClassFixture, IDisposable { private readonly CancellationTokenSource _cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - private readonly PostgreSqlFixture _fixture; + private readonly PostgreSqlDefaultFixture _fixture; - public ReuseContainerTest(PostgreSqlFixture fixture) + public ReuseContainerTest(PostgreSqlDefaultFixture fixture) { _fixture = fixture; } @@ -68,24 +66,26 @@ await _fixture.Container.StartAsync(_cts.Token) } } - [UsedImplicitly] - public sealed class PostgreSqlFixture : ContainerFixture + public class PostgreSqlDefaultFixture(IMessageSink messageSink) + : DbContainerFixture(messageSink) { - public PostgreSqlFixture(IMessageSink messageSink) - : base(messageSink) - { - } + public override DbProviderFactory DbProviderFactory + => NpgsqlFactory.Instance; } [UsedImplicitly] - public sealed class PostgreSqlDefaultConfiguration(ITestOutputHelper testOutputHelper) : PostgreSqlContainerTest(testOutputHelper); - - [UsedImplicitly] - public sealed class PostgreSqlWaitForDatabase(ITestOutputHelper testOutputHelper) : PostgreSqlContainerTest(testOutputHelper) + public class PostgreSqlWaitForDatabaseFixture(IMessageSink messageSink) + : PostgreSqlDefaultFixture(messageSink) { protected override PostgreSqlBuilder Configure(PostgreSqlBuilder builder) - { - return builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); - } + => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } + + [UsedImplicitly] + public sealed class PostgreSqlDefaultConfiguration(PostgreSqlDefaultFixture fixture) + : PostgreSqlContainerTest(fixture), IClassFixture; + + [UsedImplicitly] + public sealed class PostgreSqlWaitForDatabaseConfiguration(PostgreSqlWaitForDatabaseFixture fixture) + : PostgreSqlContainerTest(fixture), IClassFixture; } \ No newline at end of file diff --git a/tests/Testcontainers.Qdrant.Tests/Usings.cs b/tests/Testcontainers.Qdrant.Tests/Usings.cs index 4923c28cb..ff80cf90b 100644 --- a/tests/Testcontainers.Qdrant.Tests/Usings.cs +++ b/tests/Testcontainers.Qdrant.Tests/Usings.cs @@ -6,7 +6,6 @@ global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using Grpc.Core; -global using Grpc.Core.Interceptors; global using Grpc.Net.Client; global using Qdrant.Client; global using Qdrant.Client.Grpc; From 6b1e68d8bd022e179c5769eba68d1878cbaaf2fb Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:50:04 +0200 Subject: [PATCH 6/9] fix(Db2ContainerTest): Accept license agreement --- tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs index 79a141c8e..9b55cca82 100644 --- a/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs +++ b/tests/Testcontainers.Db2.Tests/Db2ContainerTest.cs @@ -39,6 +39,9 @@ public class Db2DefaultFixture(IMessageSink messageSink) { public override DbProviderFactory DbProviderFactory => DB2Factory.Instance; + + protected override Db2Builder Configure(Db2Builder builder) + => builder.WithAcceptLicenseAgreement(true); } [UsedImplicitly] @@ -46,7 +49,7 @@ public class Db2WaitForDatabaseFixture(IMessageSink messageSink) : Db2DefaultFixture(messageSink) { protected override Db2Builder Configure(Db2Builder builder) - => builder.WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); + => base.Configure(builder).WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory)); } [UsedImplicitly] From aa2d3a992fc3826e78a7914f2bd3f30a16685022 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:37:56 +0200 Subject: [PATCH 7/9] fix: Update module docs --- docs/modules/cassandra.md | 10 +++++++++- docs/modules/db2.md | 10 +++++++++- docs/modules/mssql.md | 9 +++++---- docs/modules/postgres.md | 10 +++++++++- docs/modules/pulsar.md | 2 +- docs/modules/qdrant.md | 2 +- .../Testcontainers.ClickHouse.Tests.csproj | 2 +- .../Testcontainers.Db2.Tests.csproj | 2 +- .../Testcontainers.FirebirdSql.Tests.csproj | 2 +- tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs | 1 + 10 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/modules/cassandra.md b/docs/modules/cassandra.md index 7e5b70abf..e2a72c55b 100644 --- a/docs/modules/cassandra.md +++ b/docs/modules/cassandra.md @@ -8,7 +8,15 @@ Add the following dependency to your project file: dotnet add package Testcontainers.Cassandra ``` -You can start an Apache Cassandra container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +You can start an Apache Cassandra container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below: + +=== "Start a Cassandra container" + ```csharp + var cassandraContainer = new CassandraBuilder().Build(); + await cassandraContainer.StartAsync(); + ``` + +The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class. === "Usage Example" ```csharp diff --git a/docs/modules/db2.md b/docs/modules/db2.md index 8df65e230..e5eddb59d 100644 --- a/docs/modules/db2.md +++ b/docs/modules/db2.md @@ -12,7 +12,15 @@ dotnet add package Testcontainers.Db2 The Linux client dependency, [Net.IBM.Data.Db2-lnx](https://www.nuget.org/packages/Net.IBM.Data.Db2-lnx), requires additional configurations. We use the [Testcontainers.Db2.Tests.targets](https://github.com/testcontainers/testcontainers-dotnet/blob/develop/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.targets) file to configure the environment variables: `LD_LIBRARY_PATH`, `PATH`, `DB2_CLI_DRIVER_INSTALL_PATH`, at runtime. -You can start an Db2 container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +You can start a Db2 container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below: + +=== "Start a Db2 container" + ```csharp + var db2Container = new Db2Builder().Build(); + await db2Container.StartAsync(); + ``` + +The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class. === "Usage Example" ```csharp diff --git a/docs/modules/mssql.md b/docs/modules/mssql.md index 64b52faa5..93e1aa26c 100644 --- a/docs/modules/mssql.md +++ b/docs/modules/mssql.md @@ -8,14 +8,15 @@ Add the following dependency to your project file: dotnet add package Testcontainers.MsSql ``` -You can start a MSSQL container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. +You can start a MSSQL container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below: -=== "Create Container Instance" +=== "Start a MSSQL container" ```csharp - --8<-- "tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs:CreateMsSqlContainer" + var msSqlContainer = new MsSqlBuilder().Build(); + await msSqlContainer.StartAsync(); ``` -This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class. === "Usage Example" ```csharp diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md index b38611e4d..376056cf3 100644 --- a/docs/modules/postgres.md +++ b/docs/modules/postgres.md @@ -8,7 +8,15 @@ Add the following dependency to your project file: dotnet add package Testcontainers.PostgreSql ``` -You can start an PostgreSQL container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +You can start a PostgreSQL container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below: + +=== "Start a PostgreSQL container" + ```csharp + var postgreSqlContainer = new PostgreSqlBuilder().Build(); + await postgreSqlContainer.StartAsync(); + ``` + +The following example utilizes the [xUnit.net](/test_frameworks/xunit_net/) module to reduce overhead by automatically managing the lifecycle of the dependent container instance. It creates and starts the container using the module-specific builder and injects it as a shared class fixture into the test class. === "Usage Example" ```csharp diff --git a/docs/modules/pulsar.md b/docs/modules/pulsar.md index f1c5627ff..e9e5ce454 100644 --- a/docs/modules/pulsar.md +++ b/docs/modules/pulsar.md @@ -8,7 +8,7 @@ Add the following dependency to your project file: dotnet add package Testcontainers.Pulsar ``` -You can start a Apache Pulsar container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. +You can start an Apache Pulsar container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. === "Create Container Instance" ```csharp diff --git a/docs/modules/qdrant.md b/docs/modules/qdrant.md index 5de677a2a..9da592fdb 100644 --- a/docs/modules/qdrant.md +++ b/docs/modules/qdrant.md @@ -8,7 +8,7 @@ Add the following dependency to your project file: dotnet add package Testcontainers.Qdrant ``` -You can start an Qdrant container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +You can start a Qdrant container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. === "Usage Example" ```csharp diff --git a/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj b/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj index 43b725e42..8f0461dc2 100644 --- a/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj +++ b/tests/Testcontainers.ClickHouse.Tests/Testcontainers.ClickHouse.Tests.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj b/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj index 70de0e737..66f24de5d 100644 --- a/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj +++ b/tests/Testcontainers.Db2.Tests/Testcontainers.Db2.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj b/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj index 6dd104ec3..902518923 100644 --- a/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj +++ b/tests/Testcontainers.FirebirdSql.Tests/Testcontainers.FirebirdSql.Tests.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs index 5ef312f25..affe00681 100644 --- a/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs +++ b/tests/Testcontainers.MsSql.Tests/MsSqlContainerTest.cs @@ -2,6 +2,7 @@ namespace Testcontainers.MsSql; public abstract class MsSqlContainerTest(MsSqlContainerTest.MsSqlDefaultFixture fixture) { + // # --8<-- [start:UseMsSqlContainer] [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() From ac743cef10f28b56ea6c3070c12104d1974c2094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Mon, 28 Apr 2025 21:42:45 +0200 Subject: [PATCH 8/9] Include a unique cluster name in the Cassandra connection string Because the `CqlConnection` class uses a static concurrent dictionary of clusters keyed by cluster name. Using multiple containers (which actually happens in the tests) can fail because the `CqlConnection` will try to connect to a potentially disposed container. --- src/Testcontainers.Cassandra/CassandraContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Testcontainers.Cassandra/CassandraContainer.cs b/src/Testcontainers.Cassandra/CassandraContainer.cs index 3e44d61ce..27096557d 100644 --- a/src/Testcontainers.Cassandra/CassandraContainer.cs +++ b/src/Testcontainers.Cassandra/CassandraContainer.cs @@ -18,7 +18,9 @@ public string GetConnectionString() { var properties = new Dictionary(); properties.Add("Contact Points", Hostname); - properties.Add("Port", GetMappedPublicPort(CassandraBuilder.CqlPort).ToString()); + var port = GetMappedPublicPort(CassandraBuilder.CqlPort).ToString(); + properties.Add("Port", port); + properties.Add("Cluster Name", $"{Hostname}:{port}"); return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value))); } From 39500e08731744d5e9e7cb928a22350599136961 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:16:50 +0200 Subject: [PATCH 9/9] chore: Reuse try-catch --- .../CassandraContainer.cs | 7 ++-- .../WaitStrategies/IWaitForContainerOS.cs | 8 ++--- .../UntilDatabaseIsAvailable.cs | 36 ++++++++++++------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Testcontainers.Cassandra/CassandraContainer.cs b/src/Testcontainers.Cassandra/CassandraContainer.cs index 27096557d..819148014 100644 --- a/src/Testcontainers.Cassandra/CassandraContainer.cs +++ b/src/Testcontainers.Cassandra/CassandraContainer.cs @@ -16,11 +16,12 @@ public CassandraContainer(CassandraConfiguration configuration) /// The Cassandra connection string. public string GetConnectionString() { + var publicPort = GetMappedPublicPort(CassandraBuilder.CqlPort).ToString(); + var properties = new Dictionary(); properties.Add("Contact Points", Hostname); - var port = GetMappedPublicPort(CassandraBuilder.CqlPort).ToString(); - properties.Add("Port", port); - properties.Add("Cluster Name", $"{Hostname}:{port}"); + properties.Add("Port", publicPort); + properties.Add("Cluster Name", $"{Hostname}:{publicPort}"); return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value))); } diff --git a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs index 5509b4582..e34ed7a57 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/IWaitForContainerOS.cs @@ -115,14 +115,14 @@ public interface IWaitForContainerOS IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action waitStrategyModifier = null); /// - /// Waits until a connection to the database can be successfully opened. + /// Waits until a successful connection to the database can be established. /// + /// + /// To use this wait strategy, the container must implement the interface. + /// /// The used to create the database connection. /// The wait strategy modifier to cancel the readiness check. /// A configured instance of . - /// - /// This wait strategy must only be applied to containers implementing the interface. - /// [PublicAPI] IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action waitStrategyModifier = null); diff --git a/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs index 23b6d8531..c36e43bc2 100644 --- a/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs +++ b/src/Testcontainers/Configurations/WaitStrategies/UntilDatabaseIsAvailable.cs @@ -18,22 +18,34 @@ public async Task UntilAsync(IContainer container) { if (container is not IDatabaseContainer dbContainer) { - throw new NotSupportedException($"The \"UntilDatabaseIsAvailable\" wait strategy is only available on database containers. " + - $"{container.GetType().FullName} does not implement the {nameof(IDatabaseContainer)} interface."); + throw new NotSupportedException( + $"The 'UntilDatabaseIsAvailable' wait strategy can only be used with database containers. " + + $"The provided container type '{container.GetType().FullName}' does not implement '{nameof(IDatabaseContainer)}'."); } - using (var connection = _dbProviderFactory.CreateConnection() ?? throw new InvalidOperationException($"{_dbProviderFactory.GetType().FullName}.CreateConnection() returned null.")) + var connection = _dbProviderFactory.CreateConnection(); + if (connection == null) + { + throw new InvalidOperationException( + $"Failed to create a database connection. The factory '{_dbProviderFactory.GetType().FullName}' returned null from 'CreateConnection()'."); + } + + try { connection.ConnectionString = dbContainer.GetConnectionString(); - try - { - await connection.OpenAsync(); - return true; - } - catch - { - return false; - } + + await connection.OpenAsync() + .ConfigureAwait(false); + + return true; + } + catch + { + return false; + } + finally + { + connection.Dispose(); } } }