Skip to content

Commit b837e90

Browse files
authored
Simplify log-wait-strategy (#977)
1 parent 823950c commit b837e90

File tree

2 files changed

+56
-37
lines changed

2 files changed

+56
-37
lines changed

packages/testcontainers/src/wait-strategies/log-wait-strategy.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,31 @@ describe("LogWaitStrategy", { timeout: 180_000 }, () => {
5454

5555
expect(await getRunningContainerNames()).not.toContain(containerName);
5656
});
57+
58+
it("should throw an error if the message is never received", async () => {
59+
const containerName = `container-${new RandomUuid().nextUuid()}`;
60+
61+
await expect(
62+
new GenericContainer("cristianrgreco/testcontainer:1.1.14")
63+
.withName(containerName)
64+
.withCommand("/bin/sh", "-c", 'echo "Ready"')
65+
.withWaitStrategy(Wait.forLogMessage("unexpected"))
66+
.start()
67+
).rejects.toThrowError(`Log stream ended and message "unexpected" was not received`);
68+
69+
expect(await getRunningContainerNames()).not.toContain(containerName);
70+
});
71+
72+
it("does not matter if container does not send all content in a single line", async () => {
73+
const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14")
74+
.withCommand([
75+
"node",
76+
"-e",
77+
"process.stdout.write('Hello '); setTimeout(() => process.stdout.write('World\\n'), 2000)",
78+
])
79+
.withWaitStrategy(Wait.forLogMessage("Hello World"))
80+
.start();
81+
82+
await container.stop();
83+
});
5784
});
Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import byline from "byline";
22
import Dockerode from "dockerode";
3+
import { setTimeout } from "timers/promises";
34
import { log } from "../common";
45
import { getContainerRuntimeClient } from "../container-runtime";
56
import { BoundPorts } from "../utils/bound-ports";
@@ -16,46 +17,37 @@ export class LogWaitStrategy extends AbstractWaitStrategy {
1617
}
1718

1819
public async waitUntilReady(container: Dockerode.Container, boundPorts: BoundPorts, startTime?: Date): Promise<void> {
20+
await Promise.race([this.handleTimeout(container.id), this.handleLogs(container, startTime)]);
21+
}
22+
23+
async handleTimeout(containerId: string): Promise<void> {
24+
await setTimeout(this.startupTimeout);
25+
this.throwError(containerId, `Log message "${this.message}" not received after ${this.startupTimeout}ms`);
26+
}
27+
28+
async handleLogs(container: Dockerode.Container, startTime?: Date): Promise<void> {
1929
log.debug(`Waiting for log message "${this.message}"...`, { containerId: container.id });
2030
const client = await getContainerRuntimeClient();
2131
const stream = await client.container.logs(container, { since: startTime ? startTime.getTime() / 1000 : 0 });
22-
return new Promise((resolve, reject) => {
23-
const timeout = setTimeout(() => {
24-
const message = `Log message "${this.message}" not received after ${this.startupTimeout}ms`;
25-
log.error(message, { containerId: container.id });
26-
reject(new Error(message));
27-
}, this.startupTimeout);
28-
29-
const comparisonFn: (line: string) => boolean = (line: string) => {
30-
if (this.message instanceof RegExp) {
31-
return this.message.test(line);
32-
} else {
33-
return line.includes(this.message);
34-
}
35-
};
36-
37-
let count = 0;
38-
const lineProcessor = (line: string) => {
39-
if (comparisonFn(line)) {
40-
if (++count === this.times) {
41-
stream.destroy();
42-
clearTimeout(timeout);
43-
log.debug(`Log wait strategy complete`, { containerId: container.id });
44-
resolve();
45-
}
32+
33+
let matches = 0;
34+
for await (const line of byline(stream)) {
35+
if (this.matches(line)) {
36+
if (++matches === this.times) {
37+
return log.debug(`Log wait strategy complete`, { containerId: container.id });
4638
}
47-
};
48-
49-
byline(stream)
50-
.on("data", lineProcessor)
51-
.on("err", lineProcessor)
52-
.on("end", () => {
53-
stream.destroy();
54-
clearTimeout(timeout);
55-
const message = `Log stream ended and message "${this.message}" was not received`;
56-
log.error(message, { containerId: container.id });
57-
reject(new Error(message));
58-
});
59-
});
39+
}
40+
}
41+
42+
this.throwError(container.id, `Log stream ended and message "${this.message}" was not received`);
43+
}
44+
45+
matches(line: string): boolean {
46+
return this.message instanceof RegExp ? this.message.test(line) : line.includes(this.message);
47+
}
48+
49+
throwError(containerId: string, message: string): void {
50+
log.error(message, { containerId });
51+
throw new Error(message);
6052
}
6153
}

0 commit comments

Comments
 (0)