From e743132b42dbe896c6822f03500d4cd3300fb5bf Mon Sep 17 00:00:00 2001 From: digital88 Date: Sun, 3 Aug 2025 16:09:00 +0300 Subject: [PATCH 1/6] 687 --- .../docker-compose-environment.test.ts | 14 ++++++ .../docker-compose-environment.ts | 27 ++++++++-- .../abstract-started-container.ts | 4 ++ .../generic-container-wait-strategy.test.ts | 50 +++++++++++++++++++ .../generic-container/generic-container.ts | 23 ++++++++- .../started-generic-container.ts | 4 ++ packages/testcontainers/src/test-container.ts | 1 + .../src/wait-strategies/null-wait-strategy.ts | 7 +++ 8 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts create mode 100644 packages/testcontainers/src/wait-strategies/null-wait-strategy.ts diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts index 9a077642b..f3c115764 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts @@ -97,6 +97,20 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); }); + it("should use wait strategy Wait.forHealthCheck() if healthcheck is defined in service", async () => { + await using startedEnvironment = await new DockerComposeEnvironment( + fixtures, + "docker-compose-with-healthcheck.yml" + ).up(); + + await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); + }); + it("should use wait strategy Wait.forListeningPorts() if healthcheck is NOT defined in service", async () => { + await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose-with-name.yml").up(); + + await checkEnvironmentContainerIsHealthy(startedEnvironment, "custom_container_name"); + }); + it("should support log message wait strategy", async () => { await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose.yml") .withWaitStrategy(await composeContainerName("container"), Wait.forLogMessage("Listening on port 8080")) diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts index 40273b48b..ae6ff5d24 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts @@ -1,4 +1,4 @@ -import { ContainerInfo } from "dockerode"; +import { ContainerInfo, ContainerInspectInfo, HealthConfig } from "dockerode"; import { containerLog, log, RandomUuid, Uuid } from "../common"; import { ComposeOptions, getContainerRuntimeClient, parseComposeContainerName } from "../container-runtime"; import { StartedGenericContainer } from "../generic-container/started-generic-container"; @@ -7,6 +7,7 @@ import { Environment } from "../types"; import { BoundPorts } from "../utils/bound-ports"; import { mapInspectResult } from "../utils/map-inspect-result"; import { ImagePullPolicy, PullPolicy } from "../utils/pull-policy"; +import { NullWaitStrategy } from "../wait-strategies/null-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { waitForContainer } from "../wait-strategies/wait-for-container"; import { WaitStrategy } from "../wait-strategies/wait-strategy"; @@ -23,7 +24,7 @@ export class DockerComposeEnvironment { private profiles: string[] = []; private environment: Environment = {}; private pullPolicy: ImagePullPolicy = PullPolicy.defaultPolicy(); - private defaultWaitStrategy: WaitStrategy = Wait.forListeningPorts(); + private defaultWaitStrategy: WaitStrategy = new NullWaitStrategy(); private waitStrategy: { [containerName: string]: WaitStrategy } = {}; private startupTimeoutMs?: number; private clientOptions: Partial = {}; @@ -159,9 +160,7 @@ export class DockerComposeEnvironment { const inspectResult = await client.container.inspect(container); const mappedInspectResult = mapInspectResult(inspectResult); const boundPorts = BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, mappedInspectResult); - const waitStrategy = this.waitStrategy[containerName] - ? this.waitStrategy[containerName] - : this.defaultWaitStrategy; + const waitStrategy = this.selectWaitStrategy(containerName, inspectResult); if (this.startupTimeoutMs !== undefined) { waitStrategy.withStartupTimeout(this.startupTimeoutMs); } @@ -207,4 +206,22 @@ export class DockerComposeEnvironment { environment: this.environment, }); } + + private selectWaitStrategy(containerName: string, inspectResult: ContainerInspectInfo) { + const containerWaitStrategy = this.waitStrategy[containerName] + ? this.waitStrategy[containerName] + : this.defaultWaitStrategy; + if (!(containerWaitStrategy instanceof NullWaitStrategy)) return containerWaitStrategy; + const healthcheck = ( + inspectResult as ContainerInspectInfo & { + Config: ContainerInspectInfo["Config"] & { + Healthcheck: HealthConfig | undefined; + }; + } + ).Config.Healthcheck; + if (healthcheck?.Test) { + return Wait.forHealthCheck(); + } + return Wait.forListeningPorts(); + } } diff --git a/packages/testcontainers/src/generic-container/abstract-started-container.ts b/packages/testcontainers/src/generic-container/abstract-started-container.ts index 4ba5ccce5..2f6f02352 100644 --- a/packages/testcontainers/src/generic-container/abstract-started-container.ts +++ b/packages/testcontainers/src/generic-container/abstract-started-container.ts @@ -76,6 +76,10 @@ export class AbstractStartedContainer implements StartedTestContainer { return this.startedTestContainer.getIpAddress(networkName); } + public getWaitStrategy() { + return this.startedTestContainer.getWaitStrategy(); + } + public async copyFilesToContainer(filesToCopy: FileToCopy[]): Promise { return this.startedTestContainer.copyFilesToContainer(filesToCopy); } diff --git a/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts new file mode 100644 index 000000000..8e3518bab --- /dev/null +++ b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts @@ -0,0 +1,50 @@ +import path from "path"; +import { HealthCheckWaitStrategy } from "../wait-strategies/health-check-wait-strategy"; +import { HostPortWaitStrategy } from "../wait-strategies/host-port-wait-strategy"; +import { Wait } from "../wait-strategies/wait"; +import { GenericContainer } from "./generic-container"; + +const fixtures = path.resolve(__dirname, "..", "..", "fixtures", "docker"); + +describe("GenericContainer wait strategy", { timeout: 180_000 }, () => { + it("should use Wait.forListeningPorts if healthcheck is not defined in DOCKERFILE", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .start(); + expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); + }); + it("should use Wait.forHealthCheck if withHealthCheck() explicitly called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .start(); + expect(container.getWaitStrategy()).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use Wait.forHealthCheck if healthcheck is defined in DOCKERFILE", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using startedContainer = await genericContainer.start(); + expect(startedContainer.getWaitStrategy()).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if image defines healthcheck", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using container = await genericContainer + .withExposedPorts(8080) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if withHealthCheck() is called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); + }); +}); diff --git a/packages/testcontainers/src/generic-container/generic-container.ts b/packages/testcontainers/src/generic-container/generic-container.ts index bada4e422..bdfad2d4c 100644 --- a/packages/testcontainers/src/generic-container/generic-container.ts +++ b/packages/testcontainers/src/generic-container/generic-container.ts @@ -1,6 +1,6 @@ import archiver from "archiver"; import AsyncLock from "async-lock"; -import { Container, ContainerCreateOptions, HostConfig } from "dockerode"; +import { Container, ContainerCreateOptions, HealthConfig, HostConfig, ImageInspectInfo } from "dockerode"; import { Readable } from "stream"; import { containerLog, hash, log, toNanos } from "../common"; import { ContainerRuntimeClient, getContainerRuntimeClient, ImageName } from "../container-runtime"; @@ -29,6 +29,7 @@ import { createLabels, LABEL_TESTCONTAINERS_CONTAINER_HASH, LABEL_TESTCONTAINERS import { mapInspectResult } from "../utils/map-inspect-result"; import { getContainerPort, getProtocol, hasHostBinding, PortWithOptionalBinding } from "../utils/port"; import { ImagePullPolicy, PullPolicy } from "../utils/pull-policy"; +import { NullWaitStrategy } from "../wait-strategies/null-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { waitForContainer } from "../wait-strategies/wait-for-container"; import { WaitStrategy } from "../wait-strategies/wait-strategy"; @@ -48,7 +49,7 @@ export class GenericContainer implements TestContainer { protected imageName: ImageName; protected startupTimeoutMs?: number; - protected waitStrategy: WaitStrategy = Wait.forListeningPorts(); + protected waitStrategy: WaitStrategy = new NullWaitStrategy(); protected environment: Record = {}; protected exposedPorts: PortWithOptionalBinding[] = []; protected reuse = false; @@ -94,6 +95,8 @@ export class GenericContainer implements TestContainer { await this.beforeContainerCreated(); } + this.waitStrategy = await this.selectWaitStrategy(client); + if (!this.isHelperContainer() && PortForwarderInstance.isRunning()) { const portForwarder = await PortForwarderInstance.getInstance(); this.hostConfig.ExtraHosts = [ @@ -117,6 +120,22 @@ export class GenericContainer implements TestContainer { return this.startContainer(client); } + private async selectWaitStrategy(client: ContainerRuntimeClient): Promise { + if (!(this.waitStrategy instanceof NullWaitStrategy)) return this.waitStrategy; + if (this.healthCheck) { + return Wait.forHealthCheck(); + } + const imageInfo = (await client.image.inspect(this.imageName)) as ImageInspectInfo & { + Config: ImageInspectInfo["Config"] & { + Healthcheck: HealthConfig | undefined; + }; + }; + if (imageInfo.Config.Healthcheck?.Test) { + return Wait.forHealthCheck(); + } + return Wait.forListeningPorts(); + } + private async reuseOrStartContainer(client: ContainerRuntimeClient) { const containerHash = hash(JSON.stringify(this.createOpts)); this.createOpts.Labels = { ...this.createOpts.Labels, [LABEL_TESTCONTAINERS_CONTAINER_HASH]: containerHash }; diff --git a/packages/testcontainers/src/generic-container/started-generic-container.ts b/packages/testcontainers/src/generic-container/started-generic-container.ts index 32f7d3925..fb70cd186 100644 --- a/packages/testcontainers/src/generic-container/started-generic-container.ts +++ b/packages/testcontainers/src/generic-container/started-generic-container.ts @@ -168,6 +168,10 @@ export class StartedGenericContainer implements StartedTestContainer { return this.getNetworkSettings()[networkName].ipAddress; } + public getWaitStrategy() { + return this.waitStrategy; + } + private getNetworkSettings() { return Object.entries(this.inspectResult.NetworkSettings.Networks) .map(([networkName, network]) => ({ diff --git a/packages/testcontainers/src/test-container.ts b/packages/testcontainers/src/test-container.ts index 3711b5eeb..9b4638402 100644 --- a/packages/testcontainers/src/test-container.ts +++ b/packages/testcontainers/src/test-container.ts @@ -79,6 +79,7 @@ export interface StartedTestContainer extends AsyncDisposable { getNetworkNames(): string[]; getNetworkId(networkName: string): string; getIpAddress(networkName: string): string; + getWaitStrategy(): WaitStrategy; copyArchiveFromContainer(path: string): Promise; copyArchiveToContainer(tar: Readable, target?: string): Promise; copyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): Promise; diff --git a/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts b/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts new file mode 100644 index 000000000..51b927dc9 --- /dev/null +++ b/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts @@ -0,0 +1,7 @@ +import { AbstractWaitStrategy } from "./wait-strategy"; + +export class NullWaitStrategy extends AbstractWaitStrategy { + public override waitUntilReady(): Promise { + return Promise.resolve(); + } +} From 40fb443f1f44ada93c2b3b4002068e03ab7de954 Mon Sep 17 00:00:00 2001 From: digital88 Date: Sun, 3 Aug 2025 16:16:19 +0300 Subject: [PATCH 2/6] tests --- .../docker-compose-environment.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts index f3c115764..a244d8dce 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts @@ -10,6 +10,8 @@ import { getVolumeNames, waitForDockerEvent, } from "../utils/test-helper"; +import { HealthCheckWaitStrategy } from "../wait-strategies/health-check-wait-strategy"; +import { HostPortWaitStrategy } from "../wait-strategies/host-port-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { DockerComposeEnvironment } from "./docker-compose-environment"; @@ -104,11 +106,17 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { ).up(); await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); + + const waitStrategy = startedEnvironment.getContainer("container-1").getWaitStrategy(); + expect(waitStrategy).toBeInstanceOf(HealthCheckWaitStrategy); }); it("should use wait strategy Wait.forListeningPorts() if healthcheck is NOT defined in service", async () => { await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose-with-name.yml").up(); await checkEnvironmentContainerIsHealthy(startedEnvironment, "custom_container_name"); + + const waitStrategy = startedEnvironment.getContainer("custom_container_name").getWaitStrategy(); + expect(waitStrategy).toBeInstanceOf(HostPortWaitStrategy); }); it("should support log message wait strategy", async () => { From 7352220a5ff694e94c1a412f86307f263b4b292d Mon Sep 17 00:00:00 2001 From: digital88 Date: Tue, 12 Aug 2025 13:27:17 +0300 Subject: [PATCH 3/6] fixes after review --- .../docker-compose-environment.test.ts | 35 ++++---- .../docker-compose-environment.ts | 7 +- .../abstract-started-container.ts | 4 - .../generic-container-wait-strategy.test.ts | 87 ++++++++++--------- .../generic-container/generic-container.ts | 13 ++- .../started-generic-container.ts | 8 +- packages/testcontainers/src/test-container.ts | 1 - .../src/wait-strategies/null-wait-strategy.ts | 7 -- .../src/wait-strategies/wait-for-container.ts | 4 +- 9 files changed, 81 insertions(+), 85 deletions(-) delete mode 100644 packages/testcontainers/src/wait-strategies/null-wait-strategy.ts diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts index a244d8dce..5bb3abc42 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts @@ -99,25 +99,30 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); }); - it("should use wait strategy Wait.forHealthCheck() if healthcheck is defined in service", async () => { - await using startedEnvironment = await new DockerComposeEnvironment( - fixtures, - "docker-compose-with-healthcheck.yml" - ).up(); + if (!process.env.CI_PODMAN) { + it("should use wait strategy Wait.forHealthCheck() if healthcheck is defined in service", async () => { + await using startedEnvironment = await new DockerComposeEnvironment( + fixtures, + "docker-compose-with-healthcheck.yml" + ).up(); - await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); + await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); - const waitStrategy = startedEnvironment.getContainer("container-1").getWaitStrategy(); - expect(waitStrategy).toBeInstanceOf(HealthCheckWaitStrategy); - }); - it("should use wait strategy Wait.forListeningPorts() if healthcheck is NOT defined in service", async () => { - await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose-with-name.yml").up(); + const waitStrategy = startedEnvironment.getContainer("container-1")["getWaitStrategy"](); + expect(waitStrategy).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use wait strategy Wait.forListeningPorts() if healthcheck is NOT defined in service", async () => { + await using startedEnvironment = await new DockerComposeEnvironment( + fixtures, + "docker-compose-with-name.yml" + ).up(); - await checkEnvironmentContainerIsHealthy(startedEnvironment, "custom_container_name"); + await checkEnvironmentContainerIsHealthy(startedEnvironment, "custom_container_name"); - const waitStrategy = startedEnvironment.getContainer("custom_container_name").getWaitStrategy(); - expect(waitStrategy).toBeInstanceOf(HostPortWaitStrategy); - }); + const waitStrategy = startedEnvironment.getContainer("custom_container_name")["getWaitStrategy"](); + expect(waitStrategy).toBeInstanceOf(HostPortWaitStrategy); + }); + } it("should support log message wait strategy", async () => { await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose.yml") diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts index ae6ff5d24..243e78115 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts @@ -7,7 +7,6 @@ import { Environment } from "../types"; import { BoundPorts } from "../utils/bound-ports"; import { mapInspectResult } from "../utils/map-inspect-result"; import { ImagePullPolicy, PullPolicy } from "../utils/pull-policy"; -import { NullWaitStrategy } from "../wait-strategies/null-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { waitForContainer } from "../wait-strategies/wait-for-container"; import { WaitStrategy } from "../wait-strategies/wait-strategy"; @@ -24,7 +23,7 @@ export class DockerComposeEnvironment { private profiles: string[] = []; private environment: Environment = {}; private pullPolicy: ImagePullPolicy = PullPolicy.defaultPolicy(); - private defaultWaitStrategy: WaitStrategy = new NullWaitStrategy(); + private defaultWaitStrategy: WaitStrategy | undefined; private waitStrategy: { [containerName: string]: WaitStrategy } = {}; private startupTimeoutMs?: number; private clientOptions: Partial = {}; @@ -207,11 +206,11 @@ export class DockerComposeEnvironment { }); } - private selectWaitStrategy(containerName: string, inspectResult: ContainerInspectInfo) { + private selectWaitStrategy(containerName: string, inspectResult: ContainerInspectInfo): WaitStrategy { const containerWaitStrategy = this.waitStrategy[containerName] ? this.waitStrategy[containerName] : this.defaultWaitStrategy; - if (!(containerWaitStrategy instanceof NullWaitStrategy)) return containerWaitStrategy; + if (containerWaitStrategy) return containerWaitStrategy; const healthcheck = ( inspectResult as ContainerInspectInfo & { Config: ContainerInspectInfo["Config"] & { diff --git a/packages/testcontainers/src/generic-container/abstract-started-container.ts b/packages/testcontainers/src/generic-container/abstract-started-container.ts index 2f6f02352..4ba5ccce5 100644 --- a/packages/testcontainers/src/generic-container/abstract-started-container.ts +++ b/packages/testcontainers/src/generic-container/abstract-started-container.ts @@ -76,10 +76,6 @@ export class AbstractStartedContainer implements StartedTestContainer { return this.startedTestContainer.getIpAddress(networkName); } - public getWaitStrategy() { - return this.startedTestContainer.getWaitStrategy(); - } - public async copyFilesToContainer(filesToCopy: FileToCopy[]): Promise { return this.startedTestContainer.copyFilesToContainer(filesToCopy); } diff --git a/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts index 8e3518bab..7348451b4 100644 --- a/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts @@ -3,48 +3,53 @@ import { HealthCheckWaitStrategy } from "../wait-strategies/health-check-wait-st import { HostPortWaitStrategy } from "../wait-strategies/host-port-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { GenericContainer } from "./generic-container"; +import { StartedGenericContainer } from "./started-generic-container"; const fixtures = path.resolve(__dirname, "..", "..", "fixtures", "docker"); -describe("GenericContainer wait strategy", { timeout: 180_000 }, () => { - it("should use Wait.forListeningPorts if healthcheck is not defined in DOCKERFILE", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .start(); - expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); +if (!process.env.CI_PODMAN) { + describe("GenericContainer wait strategy", { timeout: 180_000 }, () => { + it("should use Wait.forListeningPorts if healthcheck is not defined in DOCKERFILE", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); + }); + it("should use Wait.forHealthCheck if withHealthCheck() explicitly called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use Wait.forHealthCheck if healthcheck is defined in DOCKERFILE", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using startedContainer = await genericContainer.start(); + expect((startedContainer as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf( + HealthCheckWaitStrategy + ); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if image defines healthcheck", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using container = await genericContainer + .withExposedPorts(8080) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if withHealthCheck() is called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); + }); }); - it("should use Wait.forHealthCheck if withHealthCheck() explicitly called", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .withHealthCheck({ - test: ["CMD-SHELL", "echo 'started' && exit 0"], - }) - .start(); - expect(container.getWaitStrategy()).toBeInstanceOf(HealthCheckWaitStrategy); - }); - it("should use Wait.forHealthCheck if healthcheck is defined in DOCKERFILE", async () => { - const context = path.resolve(fixtures, "docker-with-health-check"); - const genericContainer = await GenericContainer.fromDockerfile(context).build(); - await using startedContainer = await genericContainer.start(); - expect(startedContainer.getWaitStrategy()).toBeInstanceOf(HealthCheckWaitStrategy); - }); - it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if image defines healthcheck", async () => { - const context = path.resolve(fixtures, "docker-with-health-check"); - const genericContainer = await GenericContainer.fromDockerfile(context).build(); - await using container = await genericContainer - .withExposedPorts(8080) - .withWaitStrategy(Wait.forListeningPorts()) - .start(); - expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); - }); - it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if withHealthCheck() is called", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .withHealthCheck({ - test: ["CMD-SHELL", "echo 'started' && exit 0"], - }) - .withWaitStrategy(Wait.forListeningPorts()) - .start(); - expect(container.getWaitStrategy()).toBeInstanceOf(HostPortWaitStrategy); - }); -}); +} diff --git a/packages/testcontainers/src/generic-container/generic-container.ts b/packages/testcontainers/src/generic-container/generic-container.ts index bdfad2d4c..f7a363ae1 100644 --- a/packages/testcontainers/src/generic-container/generic-container.ts +++ b/packages/testcontainers/src/generic-container/generic-container.ts @@ -29,7 +29,6 @@ import { createLabels, LABEL_TESTCONTAINERS_CONTAINER_HASH, LABEL_TESTCONTAINERS import { mapInspectResult } from "../utils/map-inspect-result"; import { getContainerPort, getProtocol, hasHostBinding, PortWithOptionalBinding } from "../utils/port"; import { ImagePullPolicy, PullPolicy } from "../utils/pull-policy"; -import { NullWaitStrategy } from "../wait-strategies/null-wait-strategy"; import { Wait } from "../wait-strategies/wait"; import { waitForContainer } from "../wait-strategies/wait-for-container"; import { WaitStrategy } from "../wait-strategies/wait-strategy"; @@ -49,7 +48,7 @@ export class GenericContainer implements TestContainer { protected imageName: ImageName; protected startupTimeoutMs?: number; - protected waitStrategy: WaitStrategy = new NullWaitStrategy(); + protected waitStrategy: WaitStrategy | undefined; protected environment: Record = {}; protected exposedPorts: PortWithOptionalBinding[] = []; protected reuse = false; @@ -121,7 +120,7 @@ export class GenericContainer implements TestContainer { } private async selectWaitStrategy(client: ContainerRuntimeClient): Promise { - if (!(this.waitStrategy instanceof NullWaitStrategy)) return this.waitStrategy; + if (this.waitStrategy) return this.waitStrategy; if (this.healthCheck) { return Wait.forHealthCheck(); } @@ -169,7 +168,7 @@ export class GenericContainer implements TestContainer { this.exposedPorts ); if (this.startupTimeoutMs !== undefined) { - this.waitStrategy.withStartupTimeout(this.startupTimeoutMs); + this.waitStrategy?.withStartupTimeout(this.startupTimeoutMs); } await waitForContainer(client, container, this.waitStrategy, boundPorts); @@ -180,7 +179,7 @@ export class GenericContainer implements TestContainer { inspectResult, boundPorts, inspectResult.Name, - this.waitStrategy, + this.waitStrategy ?? Wait.forListeningPorts(), this.autoRemove ); } @@ -225,7 +224,7 @@ export class GenericContainer implements TestContainer { ); if (this.startupTimeoutMs !== undefined) { - this.waitStrategy.withStartupTimeout(this.startupTimeoutMs); + this.waitStrategy?.withStartupTimeout(this.startupTimeoutMs); } if (containerLog.enabled() || this.logConsumer !== undefined) { @@ -252,7 +251,7 @@ export class GenericContainer implements TestContainer { inspectResult, boundPorts, inspectResult.Name, - this.waitStrategy, + this.waitStrategy ?? Wait.forListeningPorts(), this.autoRemove ); diff --git a/packages/testcontainers/src/generic-container/started-generic-container.ts b/packages/testcontainers/src/generic-container/started-generic-container.ts index fb70cd186..5943fed4e 100644 --- a/packages/testcontainers/src/generic-container/started-generic-container.ts +++ b/packages/testcontainers/src/generic-container/started-generic-container.ts @@ -168,10 +168,6 @@ export class StartedGenericContainer implements StartedTestContainer { return this.getNetworkSettings()[networkName].ipAddress; } - public getWaitStrategy() { - return this.waitStrategy; - } - private getNetworkSettings() { return Object.entries(this.inspectResult.NetworkSettings.Networks) .map(([networkName, network]) => ({ @@ -248,4 +244,8 @@ export class StartedGenericContainer implements StartedTestContainer { async [Symbol.asyncDispose]() { await this.stop(); } + + private getWaitStrategy() { + return this.waitStrategy; + } } diff --git a/packages/testcontainers/src/test-container.ts b/packages/testcontainers/src/test-container.ts index 9b4638402..3711b5eeb 100644 --- a/packages/testcontainers/src/test-container.ts +++ b/packages/testcontainers/src/test-container.ts @@ -79,7 +79,6 @@ export interface StartedTestContainer extends AsyncDisposable { getNetworkNames(): string[]; getNetworkId(networkName: string): string; getIpAddress(networkName: string): string; - getWaitStrategy(): WaitStrategy; copyArchiveFromContainer(path: string): Promise; copyArchiveToContainer(tar: Readable, target?: string): Promise; copyDirectoriesToContainer(directoriesToCopy: DirectoryToCopy[]): Promise; diff --git a/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts b/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts deleted file mode 100644 index 51b927dc9..000000000 --- a/packages/testcontainers/src/wait-strategies/null-wait-strategy.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AbstractWaitStrategy } from "./wait-strategy"; - -export class NullWaitStrategy extends AbstractWaitStrategy { - public override waitUntilReady(): Promise { - return Promise.resolve(); - } -} diff --git a/packages/testcontainers/src/wait-strategies/wait-for-container.ts b/packages/testcontainers/src/wait-strategies/wait-for-container.ts index 1c949253c..5c0e64b30 100644 --- a/packages/testcontainers/src/wait-strategies/wait-for-container.ts +++ b/packages/testcontainers/src/wait-strategies/wait-for-container.ts @@ -7,14 +7,14 @@ import { WaitStrategy } from "./wait-strategy"; export const waitForContainer = async ( client: ContainerRuntimeClient, container: Container, - waitStrategy: WaitStrategy, + waitStrategy: WaitStrategy | undefined, boundPorts: BoundPorts, startTime?: Date ): Promise => { log.debug(`Waiting for container to be ready...`, { containerId: container.id }); try { - await waitStrategy.waitUntilReady(container, boundPorts, startTime); + await waitStrategy?.waitUntilReady(container, boundPorts, startTime); log.info(`Container is ready`, { containerId: container.id }); } catch (err) { log.error(`Container failed to be ready: ${err}`, { containerId: container.id }); From f47af2ee34b5fadca2f06adf3161f41c2c384432 Mon Sep 17 00:00:00 2001 From: digital88 Date: Mon, 25 Aug 2025 20:09:49 +0300 Subject: [PATCH 4/6] update @types/dockerode --- package-lock.json | 9 +++++---- packages/testcontainers/package.json | 2 +- .../docker-compose-environment.ts | 10 ++-------- .../src/generic-container/generic-container.ts | 8 ++------ 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index f56d132a1..be9b58df1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7893,9 +7893,10 @@ } }, "node_modules/@types/dockerode": { - "version": "3.3.42", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.42.tgz", - "integrity": "sha512-U1jqHMShibMEWHdxYhj3rCMNCiLx5f35i4e3CEUuW+JSSszc/tVqc6WCAPdhwBymG5R/vgbcceagK0St7Cq6Eg==", + "version": "3.3.43", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.43.tgz", + "integrity": "sha512-YCi0aKKpKeC9dhKTbuglvsWDnAyuIITd6CCJSTKiAdbDzPH4RWu0P9IK2XkJHdyplH6mzYtDYO+gB06JlzcPxg==", + "license": "MIT", "dependencies": { "@types/docker-modem": "*", "@types/node": "*", @@ -22897,7 +22898,7 @@ "license": "MIT", "dependencies": { "@balena/dockerignore": "^1.0.2", - "@types/dockerode": "^3.3.42", + "@types/dockerode": "^3.3.43", "archiver": "^7.0.1", "async-lock": "^1.4.1", "byline": "^5.0.0", diff --git a/packages/testcontainers/package.json b/packages/testcontainers/package.json index d661e1ec3..e41f5d735 100644 --- a/packages/testcontainers/package.json +++ b/packages/testcontainers/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@balena/dockerignore": "^1.0.2", - "@types/dockerode": "^3.3.42", + "@types/dockerode": "^3.3.43", "archiver": "^7.0.1", "async-lock": "^1.4.1", "byline": "^5.0.0", diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts index 243e78115..210dc9431 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.ts @@ -1,4 +1,4 @@ -import { ContainerInfo, ContainerInspectInfo, HealthConfig } from "dockerode"; +import { ContainerInfo, ContainerInspectInfo } from "dockerode"; import { containerLog, log, RandomUuid, Uuid } from "../common"; import { ComposeOptions, getContainerRuntimeClient, parseComposeContainerName } from "../container-runtime"; import { StartedGenericContainer } from "../generic-container/started-generic-container"; @@ -211,13 +211,7 @@ export class DockerComposeEnvironment { ? this.waitStrategy[containerName] : this.defaultWaitStrategy; if (containerWaitStrategy) return containerWaitStrategy; - const healthcheck = ( - inspectResult as ContainerInspectInfo & { - Config: ContainerInspectInfo["Config"] & { - Healthcheck: HealthConfig | undefined; - }; - } - ).Config.Healthcheck; + const healthcheck = inspectResult.Config.Healthcheck; if (healthcheck?.Test) { return Wait.forHealthCheck(); } diff --git a/packages/testcontainers/src/generic-container/generic-container.ts b/packages/testcontainers/src/generic-container/generic-container.ts index f7a363ae1..4d54c48ac 100644 --- a/packages/testcontainers/src/generic-container/generic-container.ts +++ b/packages/testcontainers/src/generic-container/generic-container.ts @@ -1,6 +1,6 @@ import archiver from "archiver"; import AsyncLock from "async-lock"; -import { Container, ContainerCreateOptions, HealthConfig, HostConfig, ImageInspectInfo } from "dockerode"; +import { Container, ContainerCreateOptions, HostConfig } from "dockerode"; import { Readable } from "stream"; import { containerLog, hash, log, toNanos } from "../common"; import { ContainerRuntimeClient, getContainerRuntimeClient, ImageName } from "../container-runtime"; @@ -124,11 +124,7 @@ export class GenericContainer implements TestContainer { if (this.healthCheck) { return Wait.forHealthCheck(); } - const imageInfo = (await client.image.inspect(this.imageName)) as ImageInspectInfo & { - Config: ImageInspectInfo["Config"] & { - Healthcheck: HealthConfig | undefined; - }; - }; + const imageInfo = await client.image.inspect(this.imageName); if (imageInfo.Config.Healthcheck?.Test) { return Wait.forHealthCheck(); } From b94ce112c6d5766f3e463f98ba1af3375877a726 Mon Sep 17 00:00:00 2001 From: digital88 Date: Wed, 27 Aug 2025 19:38:33 +0300 Subject: [PATCH 5/6] move selectWaitStrategy inside startContainer --- .../generic-container-wait-strategy.test.ts | 86 +++++++++---------- .../generic-container/generic-container.ts | 22 +++-- .../src/wait-strategies/wait-for-container.ts | 4 +- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts index 7348451b4..6f1e12be8 100644 --- a/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-wait-strategy.test.ts @@ -7,49 +7,45 @@ import { StartedGenericContainer } from "./started-generic-container"; const fixtures = path.resolve(__dirname, "..", "..", "fixtures", "docker"); -if (!process.env.CI_PODMAN) { - describe("GenericContainer wait strategy", { timeout: 180_000 }, () => { - it("should use Wait.forListeningPorts if healthcheck is not defined in DOCKERFILE", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .start(); - expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); - }); - it("should use Wait.forHealthCheck if withHealthCheck() explicitly called", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .withHealthCheck({ - test: ["CMD-SHELL", "echo 'started' && exit 0"], - }) - .start(); - expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HealthCheckWaitStrategy); - }); - it("should use Wait.forHealthCheck if healthcheck is defined in DOCKERFILE", async () => { - const context = path.resolve(fixtures, "docker-with-health-check"); - const genericContainer = await GenericContainer.fromDockerfile(context).build(); - await using startedContainer = await genericContainer.start(); - expect((startedContainer as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf( - HealthCheckWaitStrategy - ); - }); - it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if image defines healthcheck", async () => { - const context = path.resolve(fixtures, "docker-with-health-check"); - const genericContainer = await GenericContainer.fromDockerfile(context).build(); - await using container = await genericContainer - .withExposedPorts(8080) - .withWaitStrategy(Wait.forListeningPorts()) - .start(); - expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); - }); - it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if withHealthCheck() is called", async () => { - await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") - .withExposedPorts(8080) - .withHealthCheck({ - test: ["CMD-SHELL", "echo 'started' && exit 0"], - }) - .withWaitStrategy(Wait.forListeningPorts()) - .start(); - expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); - }); +describe("GenericContainer wait strategy", { timeout: 180_000 }, () => { + it("should use Wait.forListeningPorts if healthcheck is not defined in DOCKERFILE", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); }); -} + it("should use Wait.forHealthCheck if withHealthCheck() explicitly called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use Wait.forHealthCheck if healthcheck is defined in DOCKERFILE", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using startedContainer = await genericContainer.start(); + expect((startedContainer as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HealthCheckWaitStrategy); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if image defines healthcheck", async () => { + const context = path.resolve(fixtures, "docker-with-health-check"); + const genericContainer = await GenericContainer.fromDockerfile(context).build(); + await using container = await genericContainer + .withExposedPorts(8080) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); + }); + it("should use same WaitStrategy if it's explicitly defined in withWaitStrategy() even if withHealthCheck() is called", async () => { + await using container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") + .withExposedPorts(8080) + .withHealthCheck({ + test: ["CMD-SHELL", "echo 'started' && exit 0"], + }) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + expect((container as StartedGenericContainer)["getWaitStrategy"]()).toBeInstanceOf(HostPortWaitStrategy); + }); +}); diff --git a/packages/testcontainers/src/generic-container/generic-container.ts b/packages/testcontainers/src/generic-container/generic-container.ts index 4d54c48ac..6a459c397 100644 --- a/packages/testcontainers/src/generic-container/generic-container.ts +++ b/packages/testcontainers/src/generic-container/generic-container.ts @@ -94,8 +94,6 @@ export class GenericContainer implements TestContainer { await this.beforeContainerCreated(); } - this.waitStrategy = await this.selectWaitStrategy(client); - if (!this.isHelperContainer() && PortForwarderInstance.isRunning()) { const portForwarder = await PortForwarderInstance.getInstance(); this.hostConfig.ExtraHosts = [ @@ -119,13 +117,13 @@ export class GenericContainer implements TestContainer { return this.startContainer(client); } - private async selectWaitStrategy(client: ContainerRuntimeClient): Promise { + private async selectWaitStrategy(client: ContainerRuntimeClient, container: Container): Promise { if (this.waitStrategy) return this.waitStrategy; if (this.healthCheck) { return Wait.forHealthCheck(); } - const imageInfo = await client.image.inspect(this.imageName); - if (imageInfo.Config.Healthcheck?.Test) { + const containerInfo = await client.container.inspect(container); + if (containerInfo.Config.Healthcheck?.Test) { return Wait.forHealthCheck(); } return Wait.forListeningPorts(); @@ -167,7 +165,9 @@ export class GenericContainer implements TestContainer { this.waitStrategy?.withStartupTimeout(this.startupTimeoutMs); } - await waitForContainer(client, container, this.waitStrategy, boundPorts); + const waitStrategy = this.waitStrategy ?? Wait.forListeningPorts(); + + await waitForContainer(client, container, waitStrategy, boundPorts); return new StartedGenericContainer( container, @@ -175,7 +175,7 @@ export class GenericContainer implements TestContainer { inspectResult, boundPorts, inspectResult.Name, - this.waitStrategy ?? Wait.forListeningPorts(), + waitStrategy, this.autoRemove ); } @@ -183,6 +183,8 @@ export class GenericContainer implements TestContainer { private async startContainer(client: ContainerRuntimeClient): Promise { const container = await client.container.create({ ...this.createOpts, HostConfig: this.hostConfig }); + this.waitStrategy = await this.selectWaitStrategy(client, container); + if (!this.isHelperContainer() && PortForwarderInstance.isRunning()) { await this.connectContainerToPortForwarder(client, container); } @@ -239,7 +241,9 @@ export class GenericContainer implements TestContainer { await this.containerStarting(mappedInspectResult, false); } - await waitForContainer(client, container, this.waitStrategy, boundPorts); + const waitStrategy = this.waitStrategy ?? Wait.forListeningPorts(); + + await waitForContainer(client, container, waitStrategy, boundPorts); const startedContainer = new StartedGenericContainer( container, @@ -247,7 +251,7 @@ export class GenericContainer implements TestContainer { inspectResult, boundPorts, inspectResult.Name, - this.waitStrategy ?? Wait.forListeningPorts(), + waitStrategy, this.autoRemove ); diff --git a/packages/testcontainers/src/wait-strategies/wait-for-container.ts b/packages/testcontainers/src/wait-strategies/wait-for-container.ts index 5c0e64b30..1c949253c 100644 --- a/packages/testcontainers/src/wait-strategies/wait-for-container.ts +++ b/packages/testcontainers/src/wait-strategies/wait-for-container.ts @@ -7,14 +7,14 @@ import { WaitStrategy } from "./wait-strategy"; export const waitForContainer = async ( client: ContainerRuntimeClient, container: Container, - waitStrategy: WaitStrategy | undefined, + waitStrategy: WaitStrategy, boundPorts: BoundPorts, startTime?: Date ): Promise => { log.debug(`Waiting for container to be ready...`, { containerId: container.id }); try { - await waitStrategy?.waitUntilReady(container, boundPorts, startTime); + await waitStrategy.waitUntilReady(container, boundPorts, startTime); log.info(`Container is ready`, { containerId: container.id }); } catch (err) { log.error(`Container failed to be ready: ${err}`, { containerId: container.id }); From e54c7bbc388b95f23a43cd9c072e10b62bd37d33 Mon Sep 17 00:00:00 2001 From: digital88 Date: Thu, 28 Aug 2025 10:36:48 +0300 Subject: [PATCH 6/6] fixed kafka and redpanda --- packages/modules/kafka/src/kafka-container.ts | 9 +++++++-- packages/modules/redpanda/src/redpanda-container.ts | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/modules/kafka/src/kafka-container.ts b/packages/modules/kafka/src/kafka-container.ts index 0cbfa98b8..36d9bec31 100644 --- a/packages/modules/kafka/src/kafka-container.ts +++ b/packages/modules/kafka/src/kafka-container.ts @@ -61,7 +61,7 @@ export class KafkaContainer extends GenericContainer { private zooKeeperHost?: string; private zooKeeperPort?: number; private saslSslConfig?: SaslSslListenerOptions; - private originalWaitinStrategy: WaitStrategy; + private originalWaitinStrategy: WaitStrategy | undefined; constructor(image: string) { super(image); @@ -193,7 +193,12 @@ export class KafkaContainer extends GenericContainer { const boundPorts = BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, inspectResult).filter( this.exposedPorts ); - await waitForContainer(client, dockerContainer, this.originalWaitinStrategy, boundPorts); + await waitForContainer( + client, + dockerContainer, + this.originalWaitinStrategy ?? Wait.forListeningPorts(), + boundPorts + ); if (this.saslSslConfig && this.mode !== KafkaMode.KRAFT) { await this.createUser(container, this.saslSslConfig.sasl); diff --git a/packages/modules/redpanda/src/redpanda-container.ts b/packages/modules/redpanda/src/redpanda-container.ts index ee2025213..f104b76e8 100644 --- a/packages/modules/redpanda/src/redpanda-container.ts +++ b/packages/modules/redpanda/src/redpanda-container.ts @@ -21,7 +21,7 @@ const STARTER_SCRIPT = "/testcontainers_start.sh"; const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script..."; export class RedpandaContainer extends GenericContainer { - private originalWaitinStrategy: WaitStrategy; + private originalWaitinStrategy: WaitStrategy | undefined; constructor(image: string) { super(image); @@ -71,7 +71,12 @@ export class RedpandaContainer extends GenericContainer { const boundPorts = BoundPorts.fromInspectResult(client.info.containerRuntime.hostIps, inspectResult).filter( this.exposedPorts ); - await waitForContainer(client, dockerContainer, this.originalWaitinStrategy, boundPorts); + await waitForContainer( + client, + dockerContainer, + this.originalWaitinStrategy ?? Wait.forListeningPorts(), + boundPorts + ); } private renderRedpandaFile(host: string, port: number): string {