Skip to content

Commit

Permalink
test: 종료 시 스프링 라이프 사이클 콜백을 제대로 호출하는지 검증
Browse files Browse the repository at this point in the history
  • Loading branch information
joon6093 committed Nov 25, 2024
1 parent 063ff94 commit 1d72c5b
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.jeyong.test.cleanup;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CleanupConfiguration {

@Bean
public PreDestroyHandler preDestroyHandler() {
return new PreDestroyHandler();
}

@Bean
public ContextClosedEventHandler contextClosedEventHandler() {
return new ContextClosedEventHandler();
}

@Bean
public ShutdownHookHandler shutdownHookHandler() {
ShutdownHookHandler shutdownHookHandler = new ShutdownHookHandler();
shutdownHookHandler.registerShutdownHook();
return shutdownHookHandler;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.jeyong.test.cleanup;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;

@Slf4j
public class ContextClosedEventHandler {

public static final String CONTEXT_CLOSED_EVENT_LOG = "Handling ContextClosedEvent...";

@EventListener(ContextClosedEvent.class)
public void handleContextClosed() {
log.info(CONTEXT_CLOSED_EVENT_LOG);
}
}
15 changes: 15 additions & 0 deletions test/src/main/java/io/jeyong/test/cleanup/PreDestroyHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.jeyong.test.cleanup;

import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PreDestroyHandler {

public static final String PRE_DESTROY_LOG = "Executing @PreDestroy cleanup logic...";

@PreDestroy
public void cleanup() {
log.info(PRE_DESTROY_LOG);
}
}
13 changes: 13 additions & 0 deletions test/src/main/java/io/jeyong/test/cleanup/ShutdownHookHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.jeyong.test.cleanup;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ShutdownHookHandler {

public static final String SHUTDOWN_HOOK_LOG = "Executing JVM Shutdown Hook...";

public void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> log.info(SHUTDOWN_HOOK_LOG)));
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,70 @@
package io.jeyong.test.integration;

import static io.jeyong.test.cleanup.ContextClosedEventHandler.CONTEXT_CLOSED_EVENT_LOG;
import static io.jeyong.test.cleanup.PreDestroyHandler.PRE_DESTROY_LOG;
import static io.jeyong.test.cleanup.ShutdownHookHandler.SHUTDOWN_HOOK_LOG;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.images.builder.ImageFromDockerfile;

@SpringBootTest
@DisplayName("SigtermHandler Integration Test")
public class SigtermHandlerTest {

private final Logger logger = LoggerFactory.getLogger(SigtermHandlerTest.class);
private static ImageFromDockerfile dockerImage;

@BeforeAll
static void setUp() {
dockerImage = buildImage();
}

@Test
@DisplayName("Container should exit with code 0 on SIGTERM")
void testExitCode() throws Exception {
// given
GenericContainer<?> container = new GenericContainer<>(dockerImage)
.waitingFor(forLogMessage(".*Started TestApplication.*\\n", 1));
container.start();

// when
sendSigtermToContainer(container);

// then
Long exitCode = container.getCurrentContainerInfo().getState().getExitCodeLong();
assertThat(exitCode).isEqualTo(0);
}

@Test
@DisplayName("Application should exit with code 0 on SIGTERM")
void testSigtermHandling() throws Exception {
@DisplayName("Container should exit with cleanup performed on SIGTERM")
void testCleanUp() throws Exception {
// given
Path tempDir = Files.createTempDirectory("docker-context");
copyFile("../gradlew", tempDir.resolve("gradlew"));
copyDirectory("../gradle", tempDir.resolve("gradle"));
copyFile("../settings.gradle", tempDir.resolve("settings.gradle"));
copyDirectory("../handler", tempDir.resolve("handler"));
copyDirectory("../test", tempDir.resolve("test"));

ImageFromDockerfile image = new ImageFromDockerfile()
GenericContainer<?> container = new GenericContainer<>(dockerImage)
.waitingFor(forLogMessage(".*Started TestApplication.*\\n", 1));
container.start();

// when
sendSigtermToContainer(container);

// then
String logs = container.getLogs();
assertThat(logs).contains(PRE_DESTROY_LOG);
assertThat(logs).contains(CONTEXT_CLOSED_EVENT_LOG);
assertThat(logs).contains(SHUTDOWN_HOOK_LOG);
}

private static ImageFromDockerfile buildImage() {
Path tempDir = createTempDirectory();
return new ImageFromDockerfile()
.withFileFromPath(".", tempDir)
.withDockerfileFromBuilder(builder -> {
builder.from("eclipse-temurin:17")
Expand All @@ -47,32 +79,37 @@ void testSigtermHandling() throws Exception {
.cmd("java", "-jar", "/app/test/build/libs/test-0.0.1-SNAPSHOT.jar")
.build();
});
}

// when
try (GenericContainer<?> container = new GenericContainer<>(image)
.withLogConsumer(new Slf4jLogConsumer(logger))) {
container.start();
sendSigtermToContainer(container);

// then
Long exitCode = container.getCurrentContainerInfo().getState().getExitCodeLong();
assertThat(exitCode).isEqualTo(0);
private static Path createTempDirectory() {
try {
Path tempDir = Files.createTempDirectory("docker-context");
copyFile("../gradlew", tempDir.resolve("gradlew"));
copyDirectory("../gradle", tempDir.resolve("gradle"));
copyFile("../settings.gradle", tempDir.resolve("settings.gradle"));
copyDirectory("../handler", tempDir.resolve("handler"));
copyDirectory("../test", tempDir.resolve("test"));
return tempDir;
} catch (Exception e) {
throw new RuntimeException("Failed to create temp directory for Docker context", e);
}
}

private void sendSigtermToContainer(GenericContainer<?> container) {
private static void copyFile(String source, Path destination) throws Exception {
FileUtils.copyFile(new File(source), destination.toFile());
}

private static void copyDirectory(String source, Path destination) throws Exception {
FileUtils.copyDirectory(new File(source), destination.toFile());
}

private static void sendSigtermToContainer(GenericContainer<?> container) throws Exception {
String containerId = container.getContainerId();
container.getDockerClient()
.killContainerCmd(containerId)
.withSignal("SIGTERM")
.exec();
}

private void copyFile(String source, Path destination) throws Exception {
FileUtils.copyFile(new File(source), destination.toFile());
}

private void copyDirectory(String source, Path destination) throws Exception {
FileUtils.copyDirectory(new File(source), destination.toFile());
Thread.sleep(1000);
}
}

0 comments on commit 1d72c5b

Please sign in to comment.