diff --git a/packages/testcontainers/src/wait-strategies/log-wait-strategy.test.ts b/packages/testcontainers/src/wait-strategies/log-wait-strategy.test.ts index 57961c1ea..3f9d36142 100644 --- a/packages/testcontainers/src/wait-strategies/log-wait-strategy.test.ts +++ b/packages/testcontainers/src/wait-strategies/log-wait-strategy.test.ts @@ -55,13 +55,13 @@ describe("LogWaitStrategy", { timeout: 180_000 }, () => { expect(await getRunningContainerNames()).not.toContain(containerName); }); - it("should throw an error if the message is never received", async () => { + it("should throw an error if the log stream ends and the message is not received", async () => { const containerName = `container-${new RandomUuid().nextUuid()}`; await expect( new GenericContainer("cristianrgreco/testcontainer:1.1.14") .withName(containerName) - .withCommand("/bin/sh", "-c", 'echo "Ready"') + .withCommand(["/bin/sh", "-c", 'echo "Ready"']) .withWaitStrategy(Wait.forLogMessage("unexpected")) .start() ).rejects.toThrowError(`Log stream ended and message "unexpected" was not received`); diff --git a/packages/testcontainers/src/wait-strategies/log-wait-strategy.ts b/packages/testcontainers/src/wait-strategies/log-wait-strategy.ts index a54c3f928..39f183461 100644 --- a/packages/testcontainers/src/wait-strategies/log-wait-strategy.ts +++ b/packages/testcontainers/src/wait-strategies/log-wait-strategy.ts @@ -16,25 +16,46 @@ export class LogWaitStrategy extends AbstractWaitStrategy { super(); } - public async waitUntilReady(container: Dockerode.Container, boundPorts: BoundPorts, startTime?: Date): Promise { - await Promise.race([this.handleTimeout(container.id), this.handleLogs(container, startTime)]); + async waitUntilReady(container: Dockerode.Container, boundPorts: BoundPorts, startTime?: Date): Promise { + const abortController = new AbortController(); + + await Promise.race([ + this.handleTimeout(container.id, abortController), + this.handleLogs(container, startTime ? startTime.getTime() / 1000 : 0, abortController), + ]); } - async handleTimeout(containerId: string): Promise { - await setTimeout(this.startupTimeout); + private async handleTimeout(containerId: string, abortController: AbortController): Promise { + try { + await setTimeout(this.startupTimeout, undefined, { signal: abortController.signal }); + } catch (err) { + if (!(err instanceof Error && err.name === "AbortError")) { + throw err; + } + } this.throwError(containerId, `Log message "${this.message}" not received after ${this.startupTimeout}ms`); + abortController.abort(); } - async handleLogs(container: Dockerode.Container, startTime?: Date): Promise { + private async handleLogs( + container: Dockerode.Container, + startTime: number, + abortController: AbortController + ): Promise { log.debug(`Waiting for log message "${this.message}"...`, { containerId: container.id }); const client = await getContainerRuntimeClient(); - const stream = await client.container.logs(container, { since: startTime ? startTime.getTime() / 1000 : 0 }); + const stream = await client.container.logs(container, { since: startTime }); let matches = 0; for await (const line of byline(stream)) { + if (abortController.signal.aborted) { + break; + } if (this.matches(line)) { if (++matches === this.times) { - return log.debug(`Log wait strategy complete`, { containerId: container.id }); + log.debug(`Log wait strategy complete`, { containerId: container.id }); + abortController.abort(); + return; } } } @@ -42,11 +63,11 @@ export class LogWaitStrategy extends AbstractWaitStrategy { this.throwError(container.id, `Log stream ended and message "${this.message}" was not received`); } - matches(line: string): boolean { + private matches(line: string): boolean { return this.message instanceof RegExp ? this.message.test(line) : line.includes(this.message); } - throwError(containerId: string, message: string): void { + private throwError(containerId: string, message: string): void { log.error(message, { containerId }); throw new Error(message); }