From c648b3beeec5b0b7257e7ad5f188bf9ac547eb4d Mon Sep 17 00:00:00 2001 From: taole33 Date: Sun, 14 Dec 2025 22:59:47 +0900 Subject: [PATCH] Ensure graceful shutdown in GenericContainer.stop() (#10365) --- .../containers/GenericContainer.java | 6 ++- .../containers/GenericContainerTest.java | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 0fe944433ae..587a3f0a4c7 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -647,7 +647,11 @@ public void stop() { } catch (Exception e) { imageName = ""; } - + try { + dockerClient.stopContainerCmd(containerId).exec(); + } catch (Exception e) { + logger().warn("Failed to stop container gracefully: {}", e.getMessage()); + } containerIsStopping(containerInfo); ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName); containerIsStopped(containerInfo); diff --git a/core/src/test/java/org/testcontainers/containers/GenericContainerTest.java b/core/src/test/java/org/testcontainers/containers/GenericContainerTest.java index 37aac1b0af9..df2d3e0bbc1 100644 --- a/core/src/test/java/org/testcontainers/containers/GenericContainerTest.java +++ b/core/src/test/java/org/testcontainers/containers/GenericContainerTest.java @@ -21,6 +21,7 @@ import org.rnorth.ducttape.unreliables.Unreliables; import org.testcontainers.DockerClientFactory; import org.testcontainers.TestImages; +import org.testcontainers.containers.output.ToStringConsumer; import org.testcontainers.containers.startupcheck.StartupCheckStrategy; import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; @@ -336,6 +337,46 @@ private static Optional reportLeakedContainers() { ); } + @Test + public void testPostHookExecutionOnStop() { + String scriptContent = + "#!/bin/bash\n" + + "function on_shutdown() {\n" + + " echo 'HOOK_TRIGGERED'\n" + + "}\n" + + "trap on_shutdown SIGTERM SIGINT EXIT\n" + + "echo 'CONTAINER_STARTED'\n" + + "while true; do sleep 1; done\n"; + + ToStringConsumer logConsumer = new ToStringConsumer(); + + try ( + GenericContainer container = new GenericContainer<>( + new ImageFromDockerfile() + .withFileFromString("entrypoint.sh", scriptContent) + .withDockerfileFromBuilder(builder -> { + builder + .from("alpine:3.18") + .run("apk add --no-cache tini bash") + .copy("entrypoint.sh", "/entrypoint.sh") + .run("chmod +x /entrypoint.sh") + .entryPoint("/sbin/tini", "--", "/entrypoint.sh") + .build(); + }) + ) + .withLogConsumer(logConsumer) + .waitingFor(Wait.forLogMessage(".*CONTAINER_STARTED.*", 1)) + ) { + container.start(); + + container.stop(); + + assertThat(logConsumer.toUtf8String()) + .as("The shutdown hook defined in 'trap' should be executed upon container stop") + .contains("HOOK_TRIGGERED"); + } + } + static class NoopStartupCheckStrategy extends StartupCheckStrategy { @Override