Skip to content

Commit

Permalink
Added output consumer, closes #41.
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn committed Mar 9, 2019
1 parent 30fca26 commit ce84e08
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Choose from existing pre-configured configurations [^1] and start containers wit
- `WithExposedPort` exposes a port inside the container e. g. `--expose=80`.
- `WithPortBinding` publishes a container port to the host e. g. `-p, --publish 80:80`.
- `WithMount` mounts a volume into the container e. g. `-v, --volume .:/tmp`.
- `WithOutputConsumer` redirects `stdout` and `stderr` to capture the Testcontainer output.
- `WithWaitStrategy` sets the wait strategy to complete the Testcontainer start and indicates when it is ready.

## Examples
Expand Down
12 changes: 12 additions & 0 deletions src/DotNet.Testcontainers.Tests/Fixtures/DefaultConsumerFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using System.IO;
using DotNet.Testcontainers.Diagnostics;

public class DefaultConsumerFixture : DefaultConsumer
{
public DefaultConsumerFixture() : base(new MemoryStream(), new MemoryStream())
{
}
}
}
33 changes: 33 additions & 0 deletions src/DotNet.Testcontainers.Tests/Unit/TestcontainersTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,39 @@ public async Task VolumeAndEnvironment()
Assert.Equal(dayOfWeek, text);
}

[Fact]
public async Task OutputConsumer()
{
// Given
using (var output = new DefaultConsumerFixture())
{
// When
var testcontainersBuilder = new TestcontainersBuilder()
.WithImage("nginx")
.WithOutputConsumer(output)
.WithCommand("/bin/bash", "-c", "hostname > /dev/stdout && hostname > /dev/stderr");

// Then
using (var testcontainer = testcontainersBuilder.Build())
{
await testcontainer.StartAsync();
}

output.Stdout.Position = 0;
output.Stderr.Position = 0;

using (var streamReader = new StreamReader(output.Stdout))
{
Assert.NotEmpty(streamReader.ReadToEnd());
}

using (var streamReader = new StreamReader(output.Stderr))
{
Assert.NotEmpty(streamReader.ReadToEnd());
}
}
}

[Fact]
public async Task WaitStrategy()
{
Expand Down
3 changes: 3 additions & 0 deletions src/DotNet.Testcontainers/Clients/ITestcontainersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Clients
{
using System.Threading.Tasks;
using DotNet.Testcontainers.Core.Models;
using DotNet.Testcontainers.Diagnostics;

internal interface ITestcontainersClient
{
Expand All @@ -11,6 +12,8 @@ internal interface ITestcontainersClient

Task RemoveAsync(string id);

Task AttachAsync(string id, IOutputConsumer outputConsumer);

Task<string> RunAsync(TestcontainersConfiguration config);
}
}
30 changes: 25 additions & 5 deletions src/DotNet.Testcontainers/Clients/TestcontainersClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace DotNet.Testcontainers.Clients
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Core.Mapper;
Expand Down Expand Up @@ -52,6 +53,25 @@ await MetaDataClientContainers.Instance.ExistsWithIdAsync(id).ContinueWith(async
});
}

public async Task AttachAsync(string id, IOutputConsumer outputConsumer)
{
if (outputConsumer is null)
{
return;
}

var attachParameters = new ContainerAttachParameters
{
Stdout = true,
Stderr = true,
Stream = true,
};

var stream = await Docker.Containers.AttachContainerAsync(id, false, attachParameters);

await stream.CopyOutputToAsync(null, outputConsumer.Stdout, outputConsumer.Stderr, default(CancellationToken));
}

public async Task<string> RunAsync(TestcontainersConfiguration config)
{
var image = config.Container.Image;
Expand Down Expand Up @@ -85,15 +105,13 @@ public async Task<string> RunAsync(TestcontainersConfiguration config)

var mounts = converter.Mounts;

await pullImageTask;

var hostConfig = new HostConfig
{
PortBindings = portBindings,
Mounts = mounts,
};

var response = await Docker.Containers.CreateContainerAsync(new CreateContainerParameters
var createParameters = new CreateContainerParameters
{
Image = image,
Name = name,
Expand All @@ -104,9 +122,11 @@ public async Task<string> RunAsync(TestcontainersConfiguration config)
Cmd = cmd,
ExposedPorts = exposedPorts,
HostConfig = hostConfig,
});
};

await pullImageTask;

return response.ID;
return (await Docker.Containers.CreateContainerAsync(createParameters)).ID;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Core.Builder
{
using DotNet.Testcontainers.Core.Containers;
using DotNet.Testcontainers.Core.Images;
using DotNet.Testcontainers.Diagnostics;

public interface ITestcontainersBuilder
{
Expand Down Expand Up @@ -122,6 +123,13 @@ public interface ITestcontainersBuilder
/// <returns>A configured instance of <see cref="ITestcontainersBuilder"/>.</returns>
ITestcontainersBuilder WithCleanUp(bool cleanUp);

/// <summary>
/// Sets the output consumer to capture the Testcontainer stdout and stderr messages.
/// </summary>
/// <param name="outputConsumer">Output consumer to capture stdout and strerr.</param>
/// <returns>A configured instance of <see cref="ITestcontainersBuilder"/>.</returns>
ITestcontainersBuilder WithOutputConsumer(IOutputConsumer outputConsumer);

/// <summary>
/// Sets the wait strategy to complete the Testcontainer asynchronous start task.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace DotNet.Testcontainers.Core.Builder
using DotNet.Testcontainers.Core.Containers;
using DotNet.Testcontainers.Core.Images;
using DotNet.Testcontainers.Core.Models;
using DotNet.Testcontainers.Diagnostics;
using static DotNet.Testcontainers.Core.Models.TestcontainersConfiguration;

public class TestcontainersBuilder : ITestcontainersBuilder
Expand Down Expand Up @@ -132,6 +133,14 @@ public ITestcontainersBuilder WithCleanUp(bool cleanUp)
});
}

public ITestcontainersBuilder WithOutputConsumer(IOutputConsumer outputConsumer)
{
return Build(this, new TestcontainersConfiguration
{
OutputConsumer = outputConsumer,
});
}

public ITestcontainersBuilder WithWaitStrategy(WaitStrategy waitStrategy)
{
return Build(this, new TestcontainersConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ namespace DotNet.Testcontainers.Core.Containers
using Docker.DotNet.Models;
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Core.Models;
using DotNet.Testcontainers.Diagnostics;
using LanguageExt;
using static LanguageExt.Prelude;

public class TestcontainersContainer : IDockerContainer
{
private static readonly DefaultWaitStrategy Wait = new DefaultWaitStrategy();

private bool disposed;

private Option<string> id = None;

private Option<ContainerListResponse> container = None;
Expand Down Expand Up @@ -97,6 +100,11 @@ public void Dispose()

protected virtual void Dispose(bool disposing)
{
if (this.disposed)
{
return;
}

if (this.Configuration.CleanUp)
{
Task.Run(this.CleanUp);
Expand All @@ -105,6 +113,8 @@ protected virtual void Dispose(bool disposing)
{
Task.Run(this.Stop);
}

this.disposed = true;
}

private async Task Create()
Expand All @@ -119,11 +129,13 @@ private async Task Start()
{
await this.id.IfSomeAsync(async id =>
{
var attachConsumerTask = TestcontainersClient.Instance.AttachAsync(id, this.Configuration.OutputConsumer);

var startTask = TestcontainersClient.Instance.StartAsync(id);

var waitTask = this.Configuration.WaitStrategy?.WaitUntil() ?? Wait.ForContainer(id).WaitUntil();

await Task.WhenAll(startTask, waitTask);
await Task.WhenAll(attachConsumerTask, startTask, waitTask);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Core.Models
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using DotNet.Testcontainers.Diagnostics;

internal class TestcontainersConfiguration
{
Expand All @@ -12,6 +13,8 @@ internal class TestcontainersConfiguration

public bool CleanUp { get; set; } = true;

public IOutputConsumer OutputConsumer { get; set; }

public WaitStrategy WaitStrategy { get; set; }

public TestcontainersConfiguration Merge(TestcontainersConfiguration old)
Expand All @@ -38,6 +41,8 @@ public TestcontainersConfiguration Merge(TestcontainersConfiguration old)

this.CleanUp = old.CleanUp;

this.OutputConsumer = Merge(this.OutputConsumer, old.OutputConsumer);

this.WaitStrategy = Merge(this.WaitStrategy, old.WaitStrategy);

return this;
Expand Down
72 changes: 72 additions & 0 deletions src/DotNet.Testcontainers/Diagnostics/DefaultConsumer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace DotNet.Testcontainers.Diagnostics
{
using System;
using System.IO;

public class DefaultConsumer : IOutputConsumer, IDisposable
{
private readonly TextWriter defaultOut;

private readonly TextWriter defaultErr;

private readonly StreamWriter stdout;

private readonly StreamWriter stderr;

private bool disposed;

public DefaultConsumer() : this(Console.OpenStandardOutput(), Console.OpenStandardError())
{
}

protected DefaultConsumer(Stream stdout, Stream stderr)
{
this.defaultOut = Console.Out;
this.defaultErr = Console.Error;

this.stdout = new StreamWriter(stdout)
{
AutoFlush = true,
};

this.stderr = new StreamWriter(stderr)
{
AutoFlush = true,
};

Console.SetOut(this.stdout);
Console.SetError(this.stderr);
}

~DefaultConsumer()
{
this.Dispose(false);
}

public Stream Stdout => this.stdout.BaseStream;

public Stream Stderr => this.stderr.BaseStream;

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (this.disposed)
{
return;
}

Console.SetOut(this.defaultOut);
Console.SetError(this.defaultErr);

this.stdout.Dispose();
this.stderr.Dispose();

this.disposed = true;
}
}
}
11 changes: 11 additions & 0 deletions src/DotNet.Testcontainers/Diagnostics/IOutputConsumer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace DotNet.Testcontainers.Diagnostics
{
using System.IO;

public interface IOutputConsumer
{
Stream Stdout { get; }

Stream Stderr { get; }
}
}

0 comments on commit ce84e08

Please sign in to comment.