palantir / docker-compose-rule

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

Proposal for new ShutdownStrategy with a JVM ShutdownHook #719

Open kicktipp opened 2 years ago

kicktipp commented 2 years ago

What happened?

We usually run lots of test together in our IDE with a SpringBootExtension starting up a SpringBoot container for all tests. The container keeps running unless I use @DirtiesContext. The Spring Contexts shuts down with a JVM Shutdown Hook after all tests have been running.

What did you want to happen?

I would like to use the same approach for my docker compose. I know, each test should run in isolation. But its a trade off between fast tests and isolation. So I want my docker-compose infrastructure to be up and running for all tests.

Further more I need to inject the database properties into the Spring context. So if my DockerComposeExtension is shutting down after each test class I need to referesh the Spring boot context all the time which involves running my database migrations with Flyway again and again.

I have managed to get this running with the following code. Maybe it is of interest for someone or a good idea to add this possibility to this project:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(Profil.INTEGRATION)
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ComponentScan(basePackageClasses = IntegrationTest.class)
public abstract class IntegrationTest {

    @Order(1)
    @RegisterExtension
    public static DockerComposeExtension docker = DockerComposeExtension.builder()
            .file("docker-compose.yml")
            .shutdownStrategy(new JvmShutdownStrategy())
            .build();

    @Order(2)
    @RegisterExtension
    public static PostgresqlExtension postgresqlExtension = new PostgresqlExtension(docker);

}
import com.palantir.docker.compose.configuration.ShutdownStrategy;
import com.palantir.docker.compose.execution.Docker;
import com.palantir.docker.compose.execution.DockerCompose;
import com.palantir.docker.compose.execution.KillDownShutdownStrategy;

public class JvmShutdownStrategy implements ShutdownStrategy {

    private DockerCompose dockerCompose;
    private Docker docker;
    private KillDownShutdownStrategy killDownShutdownStrategy = new KillDownShutdownStrategy();

    public JvmShutdownStrategy() {
        Runtime.getRuntime().addShutdownHook(new Thread(this::shutdownHook));
    }

    public void shutdownHook() {
        try {
            killDownShutdownStrategy.stop(dockerCompose);
            killDownShutdownStrategy.shutdown(dockerCompose, docker);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void stop(DockerCompose dockerCompose) {
        this.dockerCompose = dockerCompose;
    }

    @Override
    public void shutdown(DockerCompose dockerCompose, Docker docker) {
        this.dockerCompose = dockerCompose;
        this.docker = docker;
    }
}
public class PostgresqlExtension implements BeforeAllCallback, AfterAllCallback {
    private static boolean started = false;
    final static Lock lock = new ReentrantLock();
    private final DockerComposeExtension docker;

    public PostgresqlExtension(DockerComposeExtension docker) {
        this.docker = docker;
    }

    @Override
    public void beforeAll(ExtensionContext context) {
        lock.lock();
        if (!started) {
            context.getRoot().getStore(GLOBAL).put(PostgresqlExtension.class.getName(), this);
            var url = docker
                              .containers().container("postgresql")
                              .port(5432)
                              .inFormat("jdbc:postgresql://$HOST:$EXTERNAL_PORT/testdb");
            System.setProperty("spring.datasource.url", url);
            started = true;
        }
        lock.unlock();
    }

    @Override
    public void afterAll(ExtensionContext context) {
    }
}