Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ jobs:
- name: Build and Test
run: ./Build.ps1
shell: pwsh
- name: Test Report
uses: dorny/test-reporter@v2
if: always()
with:
name: Test Report
path: artifacts/*.trx
reporter: dotnet-trx
- name: Push to MyGet
env:
NUGET_URL: https://www.myget.org/F/respawn-ci/api/v3/index.json
Expand Down
2 changes: 1 addition & 1 deletion Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ exec { & dotnet clean --configuration Release }

exec { & dotnet build --configuration Release }

exec { & dotnet test --configuration Release --results-directory $artifacts --no-build --logger trx --verbosity=normal }
exec { & dotnet test --configuration Release --results-directory $artifacts --no-build --verbosity=normal }

exec { & dotnet pack .\Respawn\Respawn.csproj --configuration Release --output $artifacts --no-build }
5 changes: 5 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<VSTestLogger>trx%3BLogFileName=$(MSBuildProjectName)-$(TargetFramework).trx</VSTestLogger>
</PropertyGroup>
</Project>
18 changes: 18 additions & 0 deletions Respawn.DatabaseTests/DB2Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Data.Common;
using DotNet.Testcontainers.Images;
using IBM.Data.Db2;
using NPoco;
using Testcontainers.Db2;
using Xunit.Abstractions;

namespace Respawn.DatabaseTests;

public class DB2Fixture(IMessageSink messageSink) : DbFixture<Db2Builder, Db2Container>(messageSink)
{
public override DbProviderFactory DbProviderFactory => DB2Factory.Instance;

protected override DatabaseType DbType => null;

protected override Db2Builder CreateBuilder() => new Db2Builder(new DockerImage("icr.io/db2_community/db2:12.1.0.0", new Platform("amd64"))).WithAcceptLicenseAgreement(true);
}
47 changes: 9 additions & 38 deletions Respawn.DatabaseTests/DB2Tests.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,21 @@
using Shouldly;
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Images;
using IBM.Data.Db2;
using NPoco;
using Respawn.Graph;
using Testcontainers.Db2;
using Xunit;
using Xunit.Abstractions;

namespace Respawn.DatabaseTests
{
public class DB2Tests : IAsyncLifetime
public class DB2Tests(ITestOutputHelper output, DB2Fixture fixture) : IAsyncLifetime, IClassFixture<DB2Fixture>
{
private Db2Container _sqlContainer;
private DB2Connection _connection;
private readonly ITestOutputHelper _output;

public DB2Tests(ITestOutputHelper output)
{
_output = output;
}

public async Task InitializeAsync()
{
_sqlContainer = new Db2Builder()
.WithAcceptLicenseAgreement(true)
.Build();
await _sqlContainer.StartAsync();

_connection = new DB2Connection(_sqlContainer.GetConnectionString());

await _connection.OpenAsync();
}

public async Task DisposeAsync()
{
_connection?.Close();
_connection?.Dispose();
_connection = null;
public async Task InitializeAsync() => _connection = (DB2Connection)await fixture.OpenConnectionAsync();

await _sqlContainer.StopAsync();
await _sqlContainer.DisposeAsync();
_sqlContainer = null;
}
public async Task DisposeAsync() => await _connection.DisposeAsync();

[SkipOnCI]
public async Task ShouldDeleteData()
Expand Down Expand Up @@ -151,7 +122,7 @@ FOREIGN KEY (FooValue) REFERENCES Foo(Value)
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down Expand Up @@ -221,7 +192,7 @@ ParentId INT NULL
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down Expand Up @@ -275,7 +246,7 @@ ParentId INT NULL
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down Expand Up @@ -359,7 +330,7 @@ public async Task ShouldHandleComplexCycles()
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down Expand Up @@ -429,7 +400,7 @@ public async Task ShouldExcludeSchemas()
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down Expand Up @@ -489,7 +460,7 @@ public async Task ShouldIncludeSchemas()
}
catch
{
_output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
output.WriteLine(checkPoint.DeleteSql ?? string.Empty);
throw;
}

Expand Down
48 changes: 48 additions & 0 deletions Respawn.DatabaseTests/DbFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using NPoco;
using Testcontainers.Xunit;
using Xunit.Abstractions;

namespace Respawn.DatabaseTests;

public abstract class DbFixture<TBuilderEntity, TContainerEntity>(IMessageSink messageSink) : DbContainerFixture<TBuilderEntity, TContainerEntity>(messageSink)
where TBuilderEntity : IContainerBuilder<TBuilderEntity, TContainerEntity, IContainerConfiguration>, new()
where TContainerEntity : IDatabaseContainer
{
public async Task<IDatabase> CreateDatabaseAsync([CallerMemberName] string dbName = "")
{
await ExecuteCreateDatabaseAsync(dbName);

var connectionString = GetConnectionString(dbName);
var database = new Database(connectionString, DbType, DbProviderFactory);
return database.OpenSharedConnection();
}

protected abstract DatabaseType DbType { get; }

protected abstract TBuilderEntity CreateBuilder();

protected override TBuilderEntity Configure() => CreateBuilder().WithWaitStrategy(Wait.ForUnixContainer().UntilDatabaseIsAvailable(DbProviderFactory));

protected virtual async Task ExecuteCreateDatabaseAsync(string dbName)
{
var builder = DbProviderFactory.CreateCommandBuilder();
var sql = $"CREATE DATABASE {builder?.QuotePrefix}{dbName}{builder?.QuoteSuffix}";

await using var command = CreateCommand(sql);
await command.ExecuteNonQueryAsync();
}

protected virtual string GetConnectionString(string dbName)
{
var builder = DbProviderFactory.CreateConnectionStringBuilder() ?? throw new InvalidOperationException($"CreateConnectionStringBuilder returned null for {DbProviderFactory}");
builder.ConnectionString = ConnectionString;
builder["Database"] = dbName;
return builder.ToString();
}
}
2 changes: 1 addition & 1 deletion Respawn.DatabaseTests/EmptyDbTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public EmptyDbTests(ITestOutputHelper output)

public async Task InitializeAsync()
{
_msSqlContainer = new MsSqlBuilder().Build();
_msSqlContainer = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-CU23-ubuntu-22.04").Build();
await _msSqlContainer.StartAsync();

var connString = _msSqlContainer.GetConnectionString();
Expand Down
128 changes: 128 additions & 0 deletions Respawn.DatabaseTests/InformixFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.Data.Common;
using System.IO;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using IBM.Data.Db2;
using NPoco;
using Xunit.Abstractions;

namespace Respawn.DatabaseTests;

public class InformixFixture(IMessageSink messageSink) : DbFixture<InformixBuilder, InformixContainer>(messageSink)
{
public override DbProviderFactory DbProviderFactory => DB2Factory.Instance;

protected override DatabaseType DbType => null;

protected override InformixBuilder CreateBuilder()
{
return new InformixBuilder("ibmcom/informix-developer-database:14.10.FC5DE");
}
}

public sealed class InformixBuilder : ContainerBuilder<InformixBuilder, InformixContainer, ContainerConfiguration>
{
public InformixBuilder()
: this("")
{
throw new NotSupportedException();
}

public InformixBuilder(string image)
: this(new DockerImage(image))
{
}

public InformixBuilder(IImage image)
: this(new ContainerConfiguration())
{
DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration;
}

private InformixBuilder(ContainerConfiguration configuration) : base(configuration)
{
DockerResourceConfiguration = configuration;
}

protected override ContainerConfiguration DockerResourceConfiguration { get; }

protected override InformixBuilder Init()
{
return base.Init()

// = environment:
.WithEnvironment("LICENSE", "accept")
.WithEnvironment("ONCONFIG_FILE", "onconfig")
.WithEnvironment("RUN_FILE_PRE_INIT", "my_post.sh")

// = ports:
.WithPortBinding(9088, assignRandomHostPort: true)
.WithPortBinding(9089, assignRandomHostPort: true)
.WithPortBinding(27017, assignRandomHostPort: true)
.WithPortBinding(27018, assignRandomHostPort: true)
.WithPortBinding(27883, assignRandomHostPort: true)

// = volumes:
.WithBindMount(
source: Path.GetFullPath("./informix-server"),
destination: "/opt/ibm/config",
AccessMode.ReadWrite)

// = privileged: true
.WithPrivileged(true)

// = user: root
//.WithUser("root")

// = tty: true
//.WithTty(true)

// optional: equivalent to "restart: always" but Testcontainers
// does not automatically restart containers (it recreates instead)
// .WithAutoRemove(false)

.WithWaitStrategy(Wait.ForUnixContainer()
.UntilExternalTcpPortIsAvailable(9088)
.UntilExternalTcpPortIsAvailable(9089)
.UntilInternalTcpPortIsAvailable(9088)
.UntilInternalTcpPortIsAvailable(9089)
// This is the last success message
.UntilMessageIsLogged("starting mqtt listener on port 27883")
);
}

public override InformixContainer Build()
{
Validate();
return new InformixContainer(DockerResourceConfiguration);
}

protected override InformixBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new ContainerConfiguration(resourceConfiguration));
}

protected override InformixBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new ContainerConfiguration(resourceConfiguration));
}

protected override InformixBuilder Merge(ContainerConfiguration oldValue, ContainerConfiguration newValue)
{
return new InformixBuilder(new ContainerConfiguration(oldValue, newValue));
}
}

public sealed class InformixContainer(IContainerConfiguration configuration) : DockerContainer(configuration), IDatabaseContainer
{
public string GetConnectionString()
{
var host = Hostname;
var port = GetMappedPublicPort(9089); // SQL port
return $"Server={host}:{port};Database=sysadmin;UID=informix;Password=in4mix;Persist Security Info=True;Authentication=Server;";
}
}
Loading