Skip to content

Commit 3a10edb

Browse files
Clean up healthcheck wait strategy coverage
1 parent 81c4124 commit 3a10edb

8 files changed

Lines changed: 55 additions & 30 deletions

File tree

docs/features/compose.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ If you'd like to override the default wait strategy for all services, you can do
5151

5252
```js
5353
const environment = await new DockerComposeEnvironment(composeFilePath, composeFile)
54-
.withDefaultWaitStrategy(Wait.forHealthCheck())
54+
.withDefaultWaitStrategy(Wait.forListeningPorts())
5555
.up();
5656
```
5757

docs/features/wait-strategies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const container = await new GenericContainer("alpine")
7171

7272
## Health check
7373

74-
Wait until the container's health check is successful:
74+
Explicitly wait until the container's health check is successful. This is optional when the image already defines a health check because Testcontainers uses that as the default wait strategy:
7575

7676
```js
7777
const { GenericContainer, Wait } = require("testcontainers");

packages/modules/kafka/src/kafka-container.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class KafkaContainer extends GenericContainer {
6161
private zooKeeperHost?: string;
6262
private zooKeeperPort?: number;
6363
private saslSslConfig?: SaslSslListenerOptions;
64-
private originalWaitinStrategy: WaitStrategy | undefined;
64+
private originalWaitStrategy: WaitStrategy | undefined;
6565

6666
constructor(image: string) {
6767
super(image);
@@ -81,7 +81,7 @@ export class KafkaContainer extends GenericContainer {
8181
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0",
8282
KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false",
8383
});
84-
this.originalWaitinStrategy = this.waitStrategy;
84+
this.originalWaitStrategy = this.waitStrategy;
8585
}
8686

8787
public withZooKeeper(host: string, port: number): this {
@@ -138,7 +138,7 @@ export class KafkaContainer extends GenericContainer {
138138

139139
// Change the wait strategy to wait for a log message from a fake starter script
140140
// so that we can put a real starter script in place at that moment
141-
this.originalWaitinStrategy = this.waitStrategy;
141+
this.originalWaitStrategy = this.waitStrategy;
142142
this.waitStrategy = Wait.forLogMessage(WAIT_FOR_SCRIPT_MESSAGE);
143143
this.withEntrypoint(["sh"]);
144144
this.withCommand([
@@ -193,12 +193,7 @@ export class KafkaContainer extends GenericContainer {
193193
const boundPorts = BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, inspectResult).filter(
194194
this.exposedPorts
195195
);
196-
await waitForContainer(
197-
client,
198-
dockerContainer,
199-
this.originalWaitinStrategy ?? Wait.forListeningPorts(),
200-
boundPorts
201-
);
196+
await waitForContainer(client, dockerContainer, this.originalWaitStrategy ?? Wait.forListeningPorts(), boundPorts);
202197

203198
if (this.saslSslConfig && this.mode !== KafkaMode.KRAFT) {
204199
await this.createUser(container, this.saslSslConfig.sasl);

packages/modules/redpanda/src/redpanda-container.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const STARTER_SCRIPT = "/testcontainers_start.sh";
2121
const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script...";
2222

2323
export class RedpandaContainer extends GenericContainer {
24-
private originalWaitinStrategy: WaitStrategy | undefined;
24+
private originalWaitStrategy: WaitStrategy | undefined;
2525

2626
constructor(image: string) {
2727
super(image);
@@ -34,7 +34,7 @@ export class RedpandaContainer extends GenericContainer {
3434
target: "/etc/redpanda/.bootstrap.yaml",
3535
},
3636
]);
37-
this.originalWaitinStrategy = this.waitStrategy;
37+
this.originalWaitStrategy = this.waitStrategy;
3838
}
3939

4040
public override async start(): Promise<StartedRedpandaContainer> {
@@ -44,7 +44,7 @@ export class RedpandaContainer extends GenericContainer {
4444
protected override async beforeContainerCreated(): Promise<void> {
4545
// Change the wait strategy to wait for a log message from a fake starter script
4646
// so that we can put a real starter script in place at that moment
47-
this.originalWaitinStrategy = this.waitStrategy;
47+
this.originalWaitStrategy = this.waitStrategy;
4848
this.waitStrategy = Wait.forLogMessage(WAIT_FOR_SCRIPT_MESSAGE);
4949
this.withEntrypoint(["sh"]);
5050
this.withCommand([
@@ -71,12 +71,7 @@ export class RedpandaContainer extends GenericContainer {
7171
const boundPorts = BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, inspectResult).filter(
7272
this.exposedPorts
7373
);
74-
await waitForContainer(
75-
client,
76-
dockerContainer,
77-
this.originalWaitinStrategy ?? Wait.forListeningPorts(),
78-
boundPorts
79-
);
74+
await waitForContainer(client, dockerContainer, this.originalWaitStrategy ?? Wait.forListeningPorts(), boundPorts);
8075
}
8176

8277
private renderRedpandaFile(host: string, port: number): string {

packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => {
9090
});
9191

9292
it("should support default wait strategy", async () => {
93-
await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose-with-healthcheck.yml")
94-
.withDefaultWaitStrategy(Wait.forHealthCheck())
95-
.up();
93+
await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose.yml")
94+
.withDefaultWaitStrategy(Wait.forLogMessage("Listening on port 8080"))
95+
.up(["container"]);
9696

9797
await checkEnvironmentContainerIsHealthy(startedEnvironment, "container-1");
9898
});

packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ describe("GenericContainer default wait strategy", { timeout: 180_000 }, () => {
3232
await checkContainerIsHealthy(container);
3333
});
3434

35+
it("should prefer a healthcheck configured with withHealthCheck over an image healthcheck", async () => {
36+
const context = path.resolve(fixtures, "docker-with-delayed-health-check");
37+
const genericContainer = await GenericContainer.fromDockerfile(context).build();
38+
await using container = await genericContainer
39+
.withExposedPorts(8080)
40+
.withCommand(["sh", "-c", "rm -f /tmp/ready /tmp/custom-ready; touch /tmp/custom-ready; node index.js"])
41+
.withHealthCheck({
42+
test: ["CMD-SHELL", "test -f /tmp/custom-ready"],
43+
interval: 1_000,
44+
timeout: 1_000,
45+
retries: 10,
46+
})
47+
.start();
48+
49+
expect(await getHealthCheckStatus(container)).toBe("healthy");
50+
await checkContainerIsHealthy(container);
51+
});
52+
3553
// Podman compat inspect does not consistently expose Config.Healthcheck for built images.
3654
if (!process.env.CI_PODMAN) {
3755
it("should wait for a healthcheck defined in the image", async () => {

packages/testcontainers/src/wait-strategies/health-check-wait-strategy.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ describe("HealthCheckWaitStrategy", { timeout: 180_000 }, () => {
2828
retries: 5,
2929
startPeriod: 1000,
3030
})
31-
.withWaitStrategy(Wait.forHealthCheck())
3231
.start();
3332

3433
await checkContainerIsHealthy(container);
@@ -44,7 +43,6 @@ describe("HealthCheckWaitStrategy", { timeout: 180_000 }, () => {
4443
.withName(containerName)
4544
.withExposedPorts(8080)
4645
.withHealthCheck({ test: ["CMD-SHELL", "exit 1"], interval: 1, timeout: 3, retries: 3 })
47-
.withWaitStrategy(Wait.forHealthCheck())
4846
.start()
4947
).rejects.toThrowError("Health check failed");
5048

@@ -60,7 +58,6 @@ describe("HealthCheckWaitStrategy", { timeout: 180_000 }, () => {
6058
customGenericContainer
6159
.withName(containerName)
6260
.withExposedPorts(8080)
63-
.withWaitStrategy(Wait.forHealthCheck())
6461
.withHealthCheck({ test: ["CMD-SHELL", "sleep 10"], timeout: 10_000 })
6562
.withStartupTimeout(0)
6663
.start()
@@ -79,7 +76,6 @@ describe("HealthCheckWaitStrategy", { timeout: 180_000 }, () => {
7976
retries: 5,
8077
startPeriod: 1000,
8178
})
82-
.withWaitStrategy(Wait.forHealthCheck())
8379
.start();
8480

8581
await checkContainerIsHealthy(container);

packages/testcontainers/src/wait-strategies/utils/health-check.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,28 @@ import { HealthCheck } from "../../types";
33

44
const DISABLED_HEALTH_CHECK_TEST = "NONE";
55

6-
export const hasHealthCheck = (healthCheck: HealthConfig | HealthCheck | undefined): boolean => {
7-
const test = healthCheck === undefined ? undefined : "test" in healthCheck ? healthCheck.test : healthCheck.Test;
8-
return test !== undefined && test.length > 0 && test[0].toUpperCase() !== DISABLED_HEALTH_CHECK_TEST;
6+
type HealthCheckConfig = HealthConfig | HealthCheck;
7+
8+
const getHealthCheckTest = (healthCheck: HealthCheckConfig): string[] | undefined => {
9+
if ("test" in healthCheck) {
10+
return healthCheck.test;
11+
}
12+
return healthCheck.Test;
13+
};
14+
15+
const isDisabledHealthCheck = (test: string[]): boolean => {
16+
return test[0].toUpperCase() === DISABLED_HEALTH_CHECK_TEST;
17+
};
18+
19+
export const hasHealthCheck = (healthCheck: HealthCheckConfig | undefined): boolean => {
20+
if (healthCheck === undefined) {
21+
return false;
22+
}
23+
24+
const test = getHealthCheckTest(healthCheck);
25+
if (test === undefined || test.length === 0) {
26+
return false;
27+
}
28+
29+
return !isDisabledHealthCheck(test);
930
};

0 commit comments

Comments
 (0)