
























@@ -1,5 +1,64 @@
1-import { describe, expect, it } from "vitest";
2-import { parseFfprobeCodecAndSampleRate, parseFfprobeCsvFields } from "./ffmpeg-exec.js";
1+import type { ChildProcess, ExecFileOptions } from "node:child_process";
2+import { EventEmitter } from "node:events";
3+import { PassThrough } from "node:stream";
4+import { beforeEach, describe, expect, it, vi } from "vitest";
5+import {
6+parseFfprobeCodecAndSampleRate,
7+parseFfprobeCsvFields,
8+runFfprobe,
9+} from "./ffmpeg-exec.js";
10+11+const { execFileMock, resolveSystemBinMock } = vi.hoisted(() => ({
12+execFileMock: vi.fn(),
13+resolveSystemBinMock: vi.fn(),
14+}));
15+16+vi.mock("node:child_process", async (importOriginal) => ({
17+ ...(await importOriginal<typeof import("node:child_process")>()),
18+execFile: execFileMock,
19+}));
20+21+vi.mock("../infra/resolve-system-bin.js", () => ({
22+resolveSystemBin: resolveSystemBinMock,
23+}));
24+25+type ExecFileCallback = (
26+error: Error | null,
27+stdout: string | Buffer,
28+stderr: string | Buffer,
29+) => void;
30+31+function createExecFileChild(): ChildProcess {
32+const child = new EventEmitter() as ChildProcess;
33+child.stdin = new PassThrough() as ChildProcess["stdin"];
34+return child;
35+}
36+37+function mockFfprobeExecFile(child: ChildProcess): {
38+execCallback: () => ExecFileCallback;
39+} {
40+let execCallback: ExecFileCallback | undefined;
41+execFileMock.mockImplementationOnce(
42+(_file: string, _args: string[], _options: ExecFileOptions, callback: ExecFileCallback) => {
43+execCallback = callback;
44+return child;
45+},
46+);
47+return {
48+execCallback: () => {
49+if (!execCallback) {
50+throw new Error("execFile callback was not captured");
51+}
52+return execCallback;
53+},
54+};
55+}
56+57+beforeEach(() => {
58+execFileMock.mockReset();
59+resolveSystemBinMock.mockReset();
60+resolveSystemBinMock.mockReturnValue("/usr/bin/ffprobe");
61+});
362463describe("parseFfprobeCsvFields", () => {
564function expectParsedFfprobeCsvCase(input: string, fieldCount: number, expected: string[]) {
@@ -43,3 +102,32 @@ describe("parseFfprobeCodecAndSampleRate", () => {
43102expectParsedCodecAndSampleRateCase(input, expected);
44103});
45104});
105+106+describe("runFfprobe", () => {
107+it("handles stdin EPIPE without overriding successful ffprobe stdout", async () => {
108+const child = createExecFileChild();
109+const { execCallback } = mockFfprobeExecFile(child);
110+111+const promise = runFfprobe(["pipe:0"], { input: Buffer.alloc(1024) });
112+113+const stdinError = Object.assign(new Error("write EPIPE"), { code: "EPIPE" });
114+expect(() => child.stdin?.emit("error", stdinError)).not.toThrow();
115+execCallback()(null, Buffer.from("ok"), Buffer.alloc(0));
116+117+await expect(promise).resolves.toBe("ok");
118+});
119+120+it("preserves the child callback error after stdin EPIPE", async () => {
121+const child = createExecFileChild();
122+const { execCallback } = mockFfprobeExecFile(child);
123+124+const promise = runFfprobe(["pipe:0"], { input: Buffer.alloc(1024) });
125+126+const stdinError = Object.assign(new Error("write EPIPE"), { code: "EPIPE" });
127+expect(() => child.stdin?.emit("error", stdinError)).not.toThrow();
128+const childError = new Error("ffprobe failed");
129+execCallback()(childError, "", "");
130+131+await expect(promise).rejects.toBe(childError);
132+});
133+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。