Skip to content

Commit

Permalink
Fix potential NullReferenceException
Browse files Browse the repository at this point in the history
While experimenting with Podman I tried to run tests with the following settings:

```sh
cd testcontainers-dotnet/tests/Testcontainers.PostgreSql.Tests
export DOCKER_HOST=unix://${HOME}/.local/share/containers/podman/machine/podman.sock
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/run/user/501/podman/podman.sock
dotnet test --filter ConnectionStateReturnsOpen
```

This lead to a `NullReferenceException`

```
[xUnit.net 00:00:01.52]     Testcontainers.PostgreSql.PostgreSqlContainerTest.ConnectionStateReturnsOpen [FAIL]
  Failed Testcontainers.PostgreSql.PostgreSqlContainerTest.ConnectionStateReturnsOpen [1 ms]
  Error Message:
   System.NullReferenceException : Object reference not set to an instance of an object.
  Stack Trace:
     at DotNet.Testcontainers.Containers.DockerContainer.get_State() in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 177
   at DotNet.Testcontainers.Configurations.UntilContainerIsRunning.UntilAsync(IContainer container) in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/UntilContainerIsRunning.cs:line 12
   at DotNet.Testcontainers.Configurations.WaitStrategy.UntilAsync(IContainer container, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs:line 102
   at DotNet.Testcontainers.Containers.DockerContainer.CheckReadinessAsync(WaitStrategy waitStrategy, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 534
   at DotNet.Testcontainers.Configurations.WaitStrategy.<>c__DisplayClass24_0.<<WaitUntilAsync>g__UntilAsync|0>d.MoveNext() in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs:line 184
--- End of stack trace from previous location ---
   at DotNet.Testcontainers.Configurations.WaitStrategy.WaitUntilAsync(Func`1 wait, TimeSpan interval, TimeSpan timeout, Int32 retries, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs:line 213
   at DotNet.Testcontainers.Containers.DockerContainer.CheckReadinessAsync(IEnumerable`1 waitStrategies, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 552
   at DotNet.Testcontainers.Containers.DockerContainer.UnsafeStartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 479
   at DotNet.Testcontainers.Containers.DockerContainer.StartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 282
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartNewAsync(Guid sessionId, IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, IImage resourceReaperImage, IMount dockerSocket, ILogger logger, Boolean requiresPrivilegedMode, TimeSpan initTimeout, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 219
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartNewAsync(Guid sessionId, IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, IImage resourceReaperImage, IMount dockerSocket, ILogger logger, Boolean requiresPrivilegedMode, TimeSpan initTimeout, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 243
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartDefaultAsync(IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, ILogger logger, Boolean isWindowsEngineEnabled, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 135
   at DotNet.Testcontainers.Clients.TestcontainersClient.RunAsync(IContainerConfiguration configuration, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Clients/TestcontainersClient.cs:line 294
   at DotNet.Testcontainers.Containers.DockerContainer.UnsafeCreateAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 415
   at DotNet.Testcontainers.Containers.DockerContainer.StartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 279
```

The problem with this configuration is that the reaper container doesn't start but this is undiagnosable because of the swallowed exception. So instead of swallowing the exception in `ByIdAsync` the `DockerApiException` is now caught inside `ExistsWithIdAsync` instead.

Although not obvious, the exception after this commit is better and can put you on the right track to understand that the issue is  about the reaper container not starting properly.

```
[xUnit.net 00:00:01.39]     Testcontainers.PostgreSql.PostgreSqlContainerTest.ConnectionStateReturnsOpen [FAIL]
  Failed Testcontainers.PostgreSql.PostgreSqlContainerTest.ConnectionStateReturnsOpen [1 ms]
  Error Message:
   Docker.DotNet.DockerContainerNotFoundException : Docker API responded with status code=NotFound, response={"cause":"no such container","message":"no container with name or ID \"91ce0224526fddb896f2163e764e03891fce19120059fdbd04c2112cbab076a6\" found: no such container","response":404}

  Stack Trace:
     at Docker.DotNet.ContainerOperations.<>c.<.cctor>b__30_0(HttpStatusCode statusCode, String responseBody)
   at Docker.DotNet.DockerClient.HandleIfErrorResponseAsync(HttpStatusCode statusCode, HttpResponseMessage response, IEnumerable`1 handlers)
   at Docker.DotNet.DockerClient.MakeRequestAsync(IEnumerable`1 errorHandlers, HttpMethod method, String path, IQueryString queryString, IRequestContent body, IDictionary`2 headers, TimeSpan timeout, CancellationToken token)
   at Docker.DotNet.ContainerOperations.InspectContainerAsync(String id, CancellationToken cancellationToken)
   at DotNet.Testcontainers.Clients.DockerContainerOperations.ByIdAsync(String id, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Clients/DockerContainerOperations.cs:line 36
   at DotNet.Testcontainers.Containers.DockerContainer.CheckReadinessAsync(WaitStrategy waitStrategy, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 531
   at DotNet.Testcontainers.Configurations.WaitStrategy.<>c__DisplayClass24_0.<<WaitUntilAsync>g__UntilAsync|0>d.MoveNext() in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs:line 184
--- End of stack trace from previous location ---
   at DotNet.Testcontainers.Configurations.WaitStrategy.WaitUntilAsync(Func`1 wait, TimeSpan interval, TimeSpan timeout, Int32 retries, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Configurations/WaitStrategies/WaitStrategy.cs:line 213
   at DotNet.Testcontainers.Containers.DockerContainer.CheckReadinessAsync(IEnumerable`1 waitStrategies, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 552
   at DotNet.Testcontainers.Containers.DockerContainer.UnsafeStartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 479
   at DotNet.Testcontainers.Containers.DockerContainer.StartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 282
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartNewAsync(Guid sessionId, IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, IImage resourceReaperImage, IMount dockerSocket, ILogger logger, Boolean requiresPrivilegedMode, TimeSpan initTimeout, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 219
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartNewAsync(Guid sessionId, IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, IImage resourceReaperImage, IMount dockerSocket, ILogger logger, Boolean requiresPrivilegedMode, TimeSpan initTimeout, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 243
   at DotNet.Testcontainers.Containers.ResourceReaper.GetAndStartDefaultAsync(IDockerEndpointAuthenticationConfiguration dockerEndpointAuthConfig, ILogger logger, Boolean isWindowsEngineEnabled, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/ResourceReaper.cs:line 135
   at DotNet.Testcontainers.Clients.TestcontainersClient.RunAsync(IContainerConfiguration configuration, CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Clients/TestcontainersClient.cs:line 294
   at DotNet.Testcontainers.Containers.DockerContainer.UnsafeCreateAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 415
   at DotNet.Testcontainers.Containers.DockerContainer.StartAsync(CancellationToken ct) in ~/testcontainers-dotnet/src/Testcontainers/Containers/DockerContainer.cs:line 27
```

By the way, the solution is `export TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED=true`, thank you [Stack Overflow](https://stackoverflow.com/questions/71549856/testcontainers-with-podman-in-java-tests/75110548#75110548)!
  • Loading branch information
0xced committed Sep 4, 2024
1 parent 934d7f0 commit f40eebf
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 13 deletions.
21 changes: 10 additions & 11 deletions src/Testcontainers/Clients/DockerContainerOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,25 @@ public async Task<IEnumerable<ContainerListResponse>> GetAllAsync(FilterByProper
}

public async Task<ContainerInspectResponse> ByIdAsync(string id, CancellationToken ct = default)
{
return await DockerClient.Containers.InspectContainerAsync(id, ct)
.ConfigureAwait(false);
}

public async Task<bool> ExistsWithIdAsync(string id, CancellationToken ct = default)
{
try
{
return await DockerClient.Containers.InspectContainerAsync(id, ct)
await ByIdAsync(id, ct)
.ConfigureAwait(false);
return true;
}
catch (DockerApiException)
catch (DockerContainerNotFoundException)
{
return null;
return false;
}
}

public async Task<bool> ExistsWithIdAsync(string id, CancellationToken ct = default)
{
var response = await ByIdAsync(id, ct)
.ConfigureAwait(false);

return response != null;
}

public async Task<long> GetExitCodeAsync(string id, CancellationToken ct = default)
{
var response = await DockerClient.Containers.WaitContainerAsync(id, ct)
Expand Down
12 changes: 10 additions & 2 deletions src/Testcontainers/Containers/DockerContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace DotNet.Testcontainers.Containers
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Configurations;
Expand Down Expand Up @@ -507,8 +508,15 @@ protected virtual async Task UnsafeStopAsync(CancellationToken ct = default)
await _client.StopAsync(_container.ID, ct)
.ConfigureAwait(false);

_container = await _client.Container.ByIdAsync(_container.ID, ct)
.ConfigureAwait(false);
try
{
_container = await _client.Container.ByIdAsync(_container.ID, ct)
.ConfigureAwait(false);
}
catch (DockerApiException)
{
_container = null;
}

StoppedTime = DateTime.UtcNow;
Stopped?.Invoke(this, EventArgs.Empty);
Expand Down

0 comments on commit f40eebf

Please sign in to comment.