@@ -153,6 +153,55 @@ export function registerStopChildBehaviorTests<TChild>(params: {
|
153 | 153 | expect(child.unref).toHaveBeenCalledOnce(); |
154 | 154 | }); |
155 | 155 | |
| 156 | +it("force-kills Windows child process trees when graceful taskkill fails", async () => { |
| 157 | +const child = new EventEmitter() as EventEmitter & { |
| 158 | +exitCode: number | null; |
| 159 | +kill: ReturnType<typeof vi.fn>; |
| 160 | +pid: number; |
| 161 | +signalCode: NodeJS.Signals | null; |
| 162 | +stderr: { destroy: ReturnType<typeof vi.fn> }; |
| 163 | +stdin: { destroy: ReturnType<typeof vi.fn> }; |
| 164 | +stdout: { destroy: ReturnType<typeof vi.fn> }; |
| 165 | +unref: ReturnType<typeof vi.fn>; |
| 166 | +}; |
| 167 | +child.exitCode = null; |
| 168 | +child.kill = vi.fn(() => true); |
| 169 | +child.pid = 4450; |
| 170 | +child.signalCode = null; |
| 171 | +child.stderr = { destroy: vi.fn() }; |
| 172 | +child.stdin = { destroy: vi.fn() }; |
| 173 | +child.stdout = { destroy: vi.fn() }; |
| 174 | +child.unref = vi.fn(); |
| 175 | +const runTaskkill = vi |
| 176 | +.fn() |
| 177 | +.mockReturnValueOnce({ error: undefined, status: 1 }) |
| 178 | +.mockReturnValueOnce({ error: undefined, status: 0 }) |
| 179 | +.mockReturnValueOnce({ error: undefined, status: 0 }); |
| 180 | + |
| 181 | +await expect( |
| 182 | +params.stopChild(child as unknown as TChild, { |
| 183 | +killGraceMs: 1, |
| 184 | +platform: "win32", |
| 185 | + runTaskkill, |
| 186 | +teardownGraceMs: 1, |
| 187 | +}), |
| 188 | +).resolves.toEqual({ |
| 189 | +exitedBeforeTeardown: false, |
| 190 | +exitCode: null, |
| 191 | +signal: "SIGKILL", |
| 192 | +}); |
| 193 | +expect(runTaskkill).toHaveBeenNthCalledWith(1, "taskkill", ["/PID", "4450", "/T"], { |
| 194 | +stdio: "ignore", |
| 195 | +}); |
| 196 | +expect(runTaskkill).toHaveBeenNthCalledWith(2, "taskkill", ["/PID", "4450", "/T", "/F"], { |
| 197 | +stdio: "ignore", |
| 198 | +}); |
| 199 | +expect(runTaskkill).toHaveBeenNthCalledWith(3, "taskkill", ["/PID", "4450", "/T", "/F"], { |
| 200 | +stdio: "ignore", |
| 201 | +}); |
| 202 | +expect(child.kill).not.toHaveBeenCalled(); |
| 203 | +}); |
| 204 | + |
156 | 205 | it.skipIf(process.platform === "win32")( |
157 | 206 | "preserves pre-teardown wrapper exits while cleaning the process group", |
158 | 207 | async () => { |
|