|
| 1 | +// Incremental Line Reader tests cover shared streaming E2E log tail behavior. |
| 2 | +import { appendFileSync, writeFileSync } from "node:fs"; |
| 3 | +import path from "node:path"; |
| 4 | +import { afterEach, describe, expect, it } from "vitest"; |
| 5 | +import { createIncrementalLineReader } from "../../scripts/e2e/lib/incremental-line-reader.mjs"; |
| 6 | +import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js"; |
| 7 | + |
| 8 | +const tempDirs: string[] = []; |
| 9 | + |
| 10 | +afterEach(() => { |
| 11 | +cleanupTempDirs(tempDirs); |
| 12 | +}); |
| 13 | + |
| 14 | +function withTempLog(run: (logPath: string) => void): void { |
| 15 | +const root = makeTempDir(tempDirs, "openclaw-line-reader-"); |
| 16 | +run(path.join(root, "output.log")); |
| 17 | +} |
| 18 | + |
| 19 | +describe("scripts/e2e/lib/incremental-line-reader.mjs", () => { |
| 20 | +it("returns complete appended lines and carries partial lines forward", () => { |
| 21 | +withTempLog((logPath) => { |
| 22 | +const reader = createIncrementalLineReader(logPath); |
| 23 | + |
| 24 | +expect(reader.readLines()).toEqual({ lines: [], reset: false }); |
| 25 | + |
| 26 | +writeFileSync(logPath, "first\nsecond\npartial", "utf8"); |
| 27 | +expect(reader.readLines()).toEqual({ lines: ["first", "second"], reset: false }); |
| 28 | + |
| 29 | +appendFileSync(logPath, "\nthird\n", "utf8"); |
| 30 | +expect(reader.readLines()).toEqual({ lines: ["partial", "third"], reset: false }); |
| 31 | +expect(reader.readLines()).toEqual({ lines: [], reset: false }); |
| 32 | +}); |
| 33 | +}); |
| 34 | + |
| 35 | +it("resets when an existing log is rewritten without changing size", () => { |
| 36 | +withTempLog((logPath) => { |
| 37 | +writeFileSync(logPath, "first\n", "utf8"); |
| 38 | +const reader = createIncrementalLineReader(logPath); |
| 39 | + |
| 40 | +expect(reader.readLines()).toEqual({ lines: ["first"], reset: false }); |
| 41 | + |
| 42 | +writeFileSync(logPath, "other\n", "utf8"); |
| 43 | +expect(reader.readLines()).toEqual({ lines: ["other"], reset: true }); |
| 44 | +}); |
| 45 | +}); |
| 46 | + |
| 47 | +it("clamps oversized initial reads to complete tail lines", () => { |
| 48 | +withTempLog((logPath) => { |
| 49 | +writeFileSync(logPath, "drop-partial\nkeep\nlast\n", "utf8"); |
| 50 | +const reader = createIncrementalLineReader(logPath, { maxReadBytes: 7 }); |
| 51 | + |
| 52 | +expect(reader.readLines()).toEqual({ lines: ["last"], reset: false }); |
| 53 | +}); |
| 54 | +}); |
| 55 | +}); |