train-kit / test / episodes.test.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Episode JSONL: append-only files the trainer tails by byte offset.
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, readdirSync, appendFileSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { EpisodeWriter, readNewTransitions } from "../dist/episodes.js";

const t1 = { g: "toy", f: [[1, 0], [0, 1]], a: 1, r: 0.5, f2: null, done: true };
const t2 = { g: "toy", s: "k1", a: 0, r: -1, s2: null, done: true };

test("writer buffers, flushes one file per game+month, and clears", () => {
  const dir = mkdtempSync(join(tmpdir(), "tk-ep-"));
  const w = new EpisodeWriter(dir);
  w.add(t1);
  w.add(t1);
  assert.equal(w.pending, 2);
  w.flush();
  assert.equal(w.pending, 0);
  const files = readdirSync(join(dir, "episodes"));
  assert.equal(files.length, 1);
  assert.match(files[0], /^toy-\d{4}-\d{2}\.jsonl$/);
  const lines = readFileSync(join(dir, "episodes", files[0]), "utf8").trim().split("\n");
  assert.deepEqual(lines.map((l) => JSON.parse(l)), [t1, t1]);
  w.flush(); // empty flush is a no-op
});

test("readNewTransitions resumes from offsets and skips foreign/torn lines", () => {
  const dir = mkdtempSync(join(tmpdir(), "tk-ep-"));
  const epDir = join(dir, "episodes");
  const w = new EpisodeWriter(dir);
  w.add(t1);
  w.flush();
  const state = { offsets: {} };
  const first = readNewTransitions([epDir], "toy", state);
  assert.equal(first.length, 1);
  assert.deepEqual(readNewTransitions([epDir], "toy", state), [], "nothing new");

  // append: one more line, a foreign-game line, and a torn line with no newline
  const file = join(epDir, readdirSync(epDir)[0]);
  appendFileSync(file, JSON.stringify(t2) + "\n" + JSON.stringify({ ...t1, g: "other" }) + "\n" + '{"g":"toy","half');
  const second = readNewTransitions([epDir], "toy", state);
  assert.deepEqual(second, [t2], "only the new same-game complete line");

  // finish the torn line — it arrives on the next read
  appendFileSync(file, '":1}\n');
  const third = readNewTransitions([epDir], "toy", state);
  assert.equal(third.length, 1);
  assert.equal(third[0].half, 1);
});

test("a missing episode dir just yields nothing", () => {
  assert.deepEqual(readNewTransitions([join(tmpdir(), "tk-none-xyz")], "toy", { offsets: {} }), []);
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/train-kit.git