




















1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { describe, expect, it } from "vitest";
5+import {
6+detectLinuxVolatileStateDir,
7+formatLinuxVolatileStateDirWarning,
8+} from "./doctor-state-integrity.js";
9+10+describe("detectLinuxVolatileStateDir", () => {
11+const TMPFS_MOUNT_INFO = [
12+"22 1 0:21 / / rw,relatime - ext4 /dev/sda1 rw",
13+"30 22 0:30 / /tmp rw,nosuid,nodev - tmpfs tmpfs rw",
14+"35 22 0:35 / /home/user/.openclaw rw - tmpfs tmpfs rw,size=1048576k",
15+].join("\n");
16+const RAMFS_MOUNT_INFO = [
17+"22 1 0:21 / / rw,relatime - ext4 /dev/sda1 rw",
18+"35 22 0:35 / /home/user/.openclaw rw - ramfs ramfs rw",
19+].join("\n");
20+const OVERLAY_MOUNT_INFO = [
21+"22 1 0:21 / / rw,relatime - overlay overlay rw,lowerdir=/lower,upperdir=/upper",
22+].join("\n");
23+const EXT4_MOUNT_INFO = "22 1 0:21 / / rw,relatime - ext4 /dev/sda1 rw";
24+25+it.each([
26+["tmpfs", TMPFS_MOUNT_INFO],
27+["ramfs", RAMFS_MOUNT_INFO],
28+])("detects %s state directories", (fsType, mountInfo) => {
29+const result = detectLinuxVolatileStateDir("/home/user/.openclaw", {
30+platform: "linux",
31+ mountInfo,
32+resolveRealPath: (targetPath) => targetPath,
33+});
34+35+expect(result).toMatchObject({
36+path: "/home/user/.openclaw",
37+mountPoint: "/home/user/.openclaw",
38+ fsType,
39+});
40+});
41+42+it("uses the most specific matching mount", () => {
43+const mountInfo = [
44+"22 1 0:21 / / rw - ext4 /dev/sda1 rw",
45+"30 22 0:30 / /home rw - ext4 /dev/sda2 rw",
46+"35 30 0:35 / /home/user/.openclaw rw - tmpfs tmpfs rw",
47+].join("\n");
48+49+expect(
50+detectLinuxVolatileStateDir("/home/user/.openclaw", {
51+platform: "linux",
52+ mountInfo,
53+resolveRealPath: (targetPath) => targetPath,
54+}),
55+).toMatchObject({
56+mountPoint: "/home/user/.openclaw",
57+fsType: "tmpfs",
58+});
59+});
60+61+it("detects a missing state directory through an existing symlink", () => {
62+const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-volatile-"));
63+try {
64+const volatileMount = path.join(root, "volatile");
65+const stateLink = path.join(root, "state");
66+fs.mkdirSync(volatileMount);
67+fs.symlinkSync(volatileMount, stateLink, "dir");
68+const resolvedVolatileMount = fs.realpathSync(volatileMount);
69+70+const result = detectLinuxVolatileStateDir(path.join(stateLink, "openclaw"), {
71+platform: "linux",
72+mountInfo: [
73+"22 1 0:21 / / rw,relatime - ext4 /dev/sda1 rw",
74+`35 22 0:35 / ${resolvedVolatileMount} rw - tmpfs tmpfs rw`,
75+].join("\n"),
76+});
77+78+expect(result).toMatchObject({
79+path: path.join(resolvedVolatileMount, "openclaw"),
80+mountPoint: resolvedVolatileMount,
81+fsType: "tmpfs",
82+});
83+} finally {
84+fs.rmSync(root, { recursive: true, force: true });
85+}
86+});
87+88+it.each([
89+["overlay", OVERLAY_MOUNT_INFO],
90+["ext4", EXT4_MOUNT_INFO],
91+])("does not flag %s filesystems", (_name, mountInfo) => {
92+expect(
93+detectLinuxVolatileStateDir("/home/user/.openclaw", {
94+platform: "linux",
95+ mountInfo,
96+resolveRealPath: (targetPath) => targetPath,
97+}),
98+).toBeNull();
99+});
100+101+it("does not inspect mount information on non-Linux platforms", () => {
102+expect(
103+detectLinuxVolatileStateDir("/home/user/.openclaw", {
104+platform: "darwin",
105+mountInfo: TMPFS_MOUNT_INFO,
106+resolveRealPath: (targetPath) => targetPath,
107+}),
108+).toBeNull();
109+});
110+111+it("does not warn when mount information is unavailable", () => {
112+expect(
113+detectLinuxVolatileStateDir("/home/user/.openclaw", {
114+platform: "linux",
115+mountInfo: "",
116+resolveRealPath: (targetPath) => targetPath,
117+}),
118+).toBeNull();
119+});
120+});
121+122+describe("formatLinuxVolatileStateDirWarning", () => {
123+it("covers all SQLite state and sidecar files under the volatile state directory", () => {
124+const warning = formatLinuxVolatileStateDirWarning("~/.openclaw", {
125+path: "/home/user/.openclaw",
126+mountPoint: "/home/user/.openclaw",
127+fsType: "tmpfs",
128+});
129+130+expect(warning).toContain("volatile filesystem");
131+expect(warning).toContain("tmpfs");
132+expect(warning).toContain("SQLite state");
133+expect(warning).toContain("WAL/journal sidecars");
134+expect(warning).toContain("lost on reboot");
135+expect(warning).toContain("OPENCLAW_STATE_DIR");
136+});
137+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。