Skip to content

Commit 06ae2f1

Browse files
Handle Docker event streams and match both status and Action fields (#1230)
1 parent 1eec889 commit 06ae2f1

File tree

2 files changed

+58
-6
lines changed

2 files changed

+58
-6
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { PassThrough } from "stream";
2+
import { waitForDockerEvent } from "./test-helper";
3+
4+
describe("waitForDockerEvent", () => {
5+
it("should resolve when action matches in ndjson stream", async () => {
6+
const eventStream = new PassThrough();
7+
const waitPromise = waitForDockerEvent(eventStream, "pull");
8+
9+
eventStream.write('{"Action":"create"}\n{"Action":"pull"}\n');
10+
11+
await expect(waitPromise).resolves.toBeUndefined();
12+
});
13+
14+
it("should resolve when status matches in ndjson stream", async () => {
15+
const eventStream = new PassThrough();
16+
const waitPromise = waitForDockerEvent(eventStream, "pull");
17+
18+
eventStream.write('{"status":"pull"}\n');
19+
20+
await expect(waitPromise).resolves.toBeUndefined();
21+
});
22+
23+
it("should resolve when action matches in json-seq stream", async () => {
24+
const eventStream = new PassThrough();
25+
const waitPromise = waitForDockerEvent(eventStream, "pull");
26+
27+
eventStream.write('\u001e{"Action":"pull"}\n');
28+
29+
await expect(waitPromise).resolves.toBeUndefined();
30+
});
31+
});

packages/testcontainers/src/utils/test-helper.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,39 @@ export const composeContainerName = async (serviceName: string, index = 1): Prom
125125

126126
export const waitForDockerEvent = async (eventStream: Readable, eventName: string, times = 1) => {
127127
let currentTimes = 0;
128+
let pendingData = "";
129+
130+
const parseDockerEvent = (eventData: string): { status?: string; Action?: string } | undefined => {
131+
try {
132+
return JSON.parse(eventData);
133+
} catch {
134+
return undefined;
135+
}
136+
};
137+
128138
return new Promise<void>((resolve) => {
129-
eventStream.on("data", (data) => {
130-
try {
131-
if (JSON.parse(data).status === eventName) {
139+
const onData = (data: string | Buffer) => {
140+
// Docker events can be emitted as ndjson or json-seq; normalize both to line-delimited JSON.
141+
pendingData += data.toString().split(String.fromCharCode(30)).join("\n");
142+
143+
const lines = pendingData.split("\n");
144+
pendingData = lines.pop() ?? "";
145+
146+
for (const line of lines) {
147+
const event = parseDockerEvent(line);
148+
const action = event?.status ?? event?.Action;
149+
150+
if (action === eventName) {
132151
if (++currentTimes === times) {
152+
eventStream.off("data", onData);
133153
resolve();
154+
return;
134155
}
135156
}
136-
} catch (err) {
137-
// ignored
138157
}
139-
});
158+
};
159+
160+
eventStream.on("data", onData);
140161
});
141162
};
142163

0 commit comments

Comments
 (0)