Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
Expand Down Expand Up @@ -112,6 +114,18 @@ public interface IWaitForContainerOS
[PublicAPI]
IWaitForContainerOS UntilContainerIsHealthy(long failingStreak = 3, Action<IWaitStrategy> waitStrategyModifier = null);

/// <summary>
/// Waits until a connection to the database can be successfully opened.
/// </summary>
/// <param name="dbProviderFactory">The <see cref="DbProviderFactory" /> used to create the database connection.</param>
/// <param name="waitStrategyModifier">The wait strategy modifier to cancel the readiness check.</param>
/// <returns>A configured instance of <see cref="IWaitForContainerOS" />.</returns>
/// <remarks>
/// This wait strategy must only be applied to containers implementing the <see cref="IDatabaseContainer"/> interface.
/// </remarks>
[PublicAPI]
IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action<IWaitStrategy> waitStrategyModifier = null);

/// <summary>
/// Returns a collection with all configured wait strategies.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Data.Common;
using System.Threading.Tasks;
using DotNet.Testcontainers.Containers;

internal class UntilDatabaseIsAvailable : IWaitUntil
{
private readonly DbProviderFactory _dbProviderFactory;

public UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory)
{
_dbProviderFactory = dbProviderFactory;
}

public async Task<bool> 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.");
}

using (var connection = _dbProviderFactory.CreateConnection() ?? throw new InvalidOperationException($"{_dbProviderFactory.GetType().FullName}.CreateConnection() returned null."))
{
connection.ConnectionString = dbContainer.GetConnectionString();
try
{
await connection.OpenAsync();
return true;
}
catch
{
return false;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Configurations
{
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Text.RegularExpressions;

/// <inheritdoc cref="IWaitForContainerOS" />
Expand Down Expand Up @@ -80,6 +81,12 @@ public virtual IWaitForContainerOS UntilContainerIsHealthy(long failingStreak =
return AddCustomWaitStrategy(new UntilContainerIsHealthy(failingStreak), waitStrategyModifier);
}

/// <inheritdoc />
public virtual IWaitForContainerOS UntilDatabaseIsAvailable(DbProviderFactory dbProviderFactory, Action<IWaitStrategy> waitStrategyModifier = null)
{
return AddCustomWaitStrategy(new UntilDatabaseIsAvailable(dbProviderFactory), waitStrategyModifier);
}

/// <inheritdoc />
public IEnumerable<WaitStrategy> Build()
{
Expand Down
40 changes: 24 additions & 16 deletions tests/Testcontainers.Cassandra.Tests/CassandraContainerTest.cs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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();
Expand All @@ -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<CassandraFixture>;

[UsedImplicitly]
public sealed class CassandraContainerWaitForDatabase(CassandraFixtureWaitForDatabase fixture) : CassandraContainerTest(fixture), IClassFixture<CassandraFixtureWaitForDatabase>;

public class CassandraFixture(IMessageSink messageSink) : DbContainerFixture<CassandraBuilder, CassandraContainer>(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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/Testcontainers.Cassandra/Testcontainers.Cassandra.csproj"/>
<ProjectReference Include="../../src/Testcontainers.Xunit/Testcontainers.Xunit.csproj"/>
<ProjectReference Include="../Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
6 changes: 5 additions & 1 deletion tests/Testcontainers.Cassandra.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
global using JetBrains.Annotations;
global using Testcontainers.Xunit;
global using Xunit;
global using Xunit.Abstractions;
38 changes: 23 additions & 15 deletions tests/Testcontainers.ClickHouse.Tests/ClickHouseContainerTest.cs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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<ClickHouseFixture>;

[UsedImplicitly]
public sealed class ClickHouseContainerWaitForDatabase(ClickHouseWaitForDatabaseFixture fixture) : ClickHouseContainerTest(fixture), IClassFixture<ClickHouseWaitForDatabaseFixture>;

public class ClickHouseFixture(IMessageSink messageSink) : DbContainerFixture<ClickHouseBuilder, ClickHouseContainer>(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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/Testcontainers.ClickHouse/Testcontainers.ClickHouse.csproj"/>
<ProjectReference Include="../../src/Testcontainers.Xunit/Testcontainers.Xunit.csproj" />
<ProjectReference Include="../Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
6 changes: 5 additions & 1 deletion tests/Testcontainers.ClickHouse.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
global using JetBrains.Annotations;
global using Testcontainers.Xunit;
global using Xunit;
global using Xunit.Abstractions;
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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<CockroachDbFixture>;

[UsedImplicitly]
public sealed class CockroachDbContainerWaitForDatabase(CockroachDbFixtureWaitForDatabase fixture) : CockroachDbContainerTest(fixture), IClassFixture<CockroachDbFixtureWaitForDatabase>;

public class CockroachDbFixture(IMessageSink messageSink) : DbContainerFixture<CockroachDbBuilder, CockroachDbContainer>(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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/Testcontainers.CockroachDb/Testcontainers.CockroachDb.csproj"/>
<ProjectReference Include="../../src/Testcontainers.Xunit/Testcontainers.Xunit.csproj"/>
<ProjectReference Include="../Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
6 changes: 5 additions & 1 deletion tests/Testcontainers.CockroachDb.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -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;
global using JetBrains.Annotations;
global using Testcontainers.Xunit;
global using Xunit;
global using Xunit.Abstractions;
Loading