
















@@ -0,0 +1,157 @@
1+import { promisify } from "node:util";
2+import { beforeEach, describe, expect, it, vi } from "vitest";
3+4+const { isWSL2SyncMock } = vi.hoisted(() => ({
5+isWSL2SyncMock: vi.fn(() => false),
6+}));
7+8+vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
9+isWSL2Sync: isWSL2SyncMock,
10+}));
11+12+vi.mock("node:fs/promises", () => ({
13+access: vi.fn(),
14+}));
15+16+vi.mock("node:child_process", async () => {
17+const { promisify: realPromisify } = await import("node:util");
18+const mockExecFile = vi.fn();
19+const execFilePromise = vi.fn();
20+(mockExecFile as unknown as Record<symbol, unknown>)[realPromisify.custom] = execFilePromise;
21+return { execFile: mockExecFile };
22+});
23+24+import { execFile } from "node:child_process";
25+import { access } from "node:fs/promises";
26+import {
27+checkWsl2CrashLoopRisk,
28+hasWslCuda,
29+isOllamaEnabledWithRestartAlways,
30+parseSystemctlShowProperties,
31+} from "./wsl2-crash-loop-check.js";
32+33+const accessMock = vi.mocked(access);
34+const execFileMock = execFile as unknown as ReturnType<typeof vi.fn> & {
35+[key: symbol]: ReturnType<typeof vi.fn>;
36+};
37+const execFilePromiseMock = vi.mocked(execFileMock[promisify.custom]);
38+39+function createLogger() {
40+return {
41+debug: vi.fn(),
42+error: vi.fn(),
43+info: vi.fn(),
44+warn: vi.fn(),
45+};
46+}
47+48+function mockSystemctl(stdout: string): void {
49+execFilePromiseMock.mockResolvedValue({ stdout, stderr: "" });
50+}
51+52+describe("wsl2 crash-loop check", () => {
53+beforeEach(() => {
54+vi.clearAllMocks();
55+isWSL2SyncMock.mockReturnValue(false);
56+});
57+58+it("parses systemctl show properties", () => {
59+expect(
60+parseSystemctlShowProperties("UnitFileState=enabled\nRestart=always\nIgnoredLine\n"),
61+).toEqual(
62+new Map([
63+["UnitFileState", "enabled"],
64+["Restart", "always"],
65+]),
66+);
67+});
68+69+it("detects enabled Restart=always ollama service", async () => {
70+mockSystemctl("UnitFileState=enabled\nRestart=always\n");
71+72+await expect(isOllamaEnabledWithRestartAlways()).resolves.toBe(true);
73+74+expect(execFilePromiseMock).toHaveBeenCalledWith(
75+"systemctl",
76+["show", "ollama.service", "--property=UnitFileState,Restart", "--no-pager"],
77+{ timeout: 5000 },
78+);
79+});
80+81+it("does not treat enabled-runtime as persistent autostart", async () => {
82+mockSystemctl("UnitFileState=enabled-runtime\nRestart=always\n");
83+84+await expect(isOllamaEnabledWithRestartAlways()).resolves.toBe(false);
85+});
86+87+it("requires Restart=always", async () => {
88+mockSystemctl("UnitFileState=enabled\nRestart=on-failure\n");
89+90+await expect(isOllamaEnabledWithRestartAlways()).resolves.toBe(false);
91+});
92+93+it("returns false when systemctl is unavailable", async () => {
94+execFilePromiseMock.mockRejectedValue(new Error("systemd unavailable"));
95+96+await expect(isOllamaEnabledWithRestartAlways()).resolves.toBe(false);
97+});
98+99+it("detects CUDA from the first available WSL marker", async () => {
100+accessMock.mockResolvedValueOnce(undefined);
101+102+await expect(hasWslCuda()).resolves.toBe(true);
103+expect(accessMock).toHaveBeenCalledWith("/dev/dxg");
104+});
105+106+it("checks the remaining CUDA markers before returning false", async () => {
107+accessMock.mockRejectedValue(new Error("missing"));
108+109+await expect(hasWslCuda()).resolves.toBe(false);
110+expect(accessMock).toHaveBeenCalledTimes(4);
111+});
112+113+it("warns for WSL2 plus Ollama autostart plus CUDA", async () => {
114+isWSL2SyncMock.mockReturnValue(true);
115+mockSystemctl("UnitFileState=enabled\nRestart=always\n");
116+accessMock.mockResolvedValueOnce(undefined);
117+const logger = createLogger();
118+119+await checkWsl2CrashLoopRisk(logger);
120+121+expect(logger.warn).toHaveBeenCalledTimes(1);
122+const message = String(logger.warn.mock.calls[0]?.[0]);
123+expect(message).toContain("WSL2 crash-loop risk");
124+expect(message).toContain("sudo systemctl disable ollama");
125+expect(message).toContain("autoMemoryReclaim=disabled");
126+expect(message).toContain("OLLAMA_KEEP_ALIVE=5m");
127+});
128+129+it("does not probe systemd outside WSL2", async () => {
130+const logger = createLogger();
131+132+await checkWsl2CrashLoopRisk(logger);
133+134+expect(execFilePromiseMock).not.toHaveBeenCalled();
135+expect(logger.warn).not.toHaveBeenCalled();
136+});
137+138+it("does not warn when CUDA is not visible", async () => {
139+isWSL2SyncMock.mockReturnValue(true);
140+mockSystemctl("UnitFileState=enabled\nRestart=always\n");
141+accessMock.mockRejectedValue(new Error("missing"));
142+const logger = createLogger();
143+144+await checkWsl2CrashLoopRisk(logger);
145+146+expect(logger.warn).not.toHaveBeenCalled();
147+});
148+149+it("never throws from advisory checks", async () => {
150+isWSL2SyncMock.mockReturnValue(true);
151+execFilePromiseMock.mockRejectedValue(new Error("boom"));
152+const logger = createLogger();
153+154+await expect(checkWsl2CrashLoopRisk(logger)).resolves.toBeUndefined();
155+expect(logger.warn).not.toHaveBeenCalled();
156+});
157+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。