惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
月光博客
月光博客
T
Tailwind CSS Blog
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
罗磊的独立博客
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
量子位
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
博客园 - 聂微东
V
V2EX

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(ollama): warn on WSL2 CUDA crash loop risk · openclaw...
steipete · 2026-04-27 · via Recent Commits to openclaw:main

@@ -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+

});