palantir / docker-compose-rule

A JUnit rule to manage docker containers using docker-compose
Apache License 2.0
425 stars 90 forks source link

External ports are cached between tests #246

Open csokol opened 6 years ago

csokol commented 6 years ago

I'm facing a similar problem reported in #200. After a bit of debugging, it seems like the rule doesn't call stop/start or the containers are cached in ContainerCache between two tests. So if the rule runs docker compose down then docker compose up, the ports might change and this doesn't get reflected in the container objects.

I noticed this problem when trying to create a base class with a shared docker compose rule config.

For example:

docker-compose.yml

version: '2.1'
services:
  redis:
    image: redis:3.2-alpine
    ports:
    - "6379"
public abstract class Base {
  @ClassRule
  public static DockerComposeRule dockerComposeRule = DockerComposeRule.builder()
      .file(new File("./docker-compose.yml")
          .getAbsolutePath())
      .waitingForService("redis", toHaveAllPortsOpen(), Duration.standardSeconds(10))
      .build();
}

public class Foo1Test extends Base {
  @Test
  public void doit() {
    Assert.assertTrue(true);
  }
}

public class Foo2Test extends Base {
  @Test
  public void doit() {
    Assert.assertTrue(true);
  }
}

Here's the output when I run both tests:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running foo.Foo1Test
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.378 sec
Running foo.Foo2Test
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 12.753 sec <<< FAILURE!
foo.Foo2Test  Time elapsed: 12.751 sec  <<< ERROR!
java.lang.IllegalStateException: The cluster failed to pass a startup check: The following ports failed to open: [6379]
    at com.palantir.docker.compose.connection.waiting.ClusterWait.waitUntilReady(ClusterWait.java:50)
    at com.palantir.docker.compose.DockerComposeRule.lambda$before$0(DockerComposeRule.java:155)
    at java.lang.Iterable.forEach(Iterable.java:75)
    at com.palantir.docker.compose.DockerComposeRule.before(DockerComposeRule.java:155)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:46)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

Results :

Tests in error:
  foo.Foo2Test: The cluster failed to pass a startup check: The following ports failed to open: [6379]

Tests run: 2, Failures: 0, Errors: 1, Skipped: 0

Any ideas on how to fix it?

csokol commented 6 years ago

For reference, I was able to find a hacky solution by resetting the container cache through reflection:

public class CustomDockerComposeRule extends ExternalResource {
  private DockerComposeRule delegate;

  public CustomDockerComposeRule(DockerComposeRule delegate) {
    this.delegate = delegate;
  }

  @Override
  protected void before() throws Throwable {
    ContainerCache containerCache = delegate.containers().containerCache();
    FieldUtils.writeField(containerCache, "containers", new HashMap<>(), true);
    delegate.before();
  }

  @Override
  protected void after() {
    delegate.after();
  }
}