testSuiteRunKeepsWhatASaveReplacedMeanwhile

tcmts.cli.test.e2e.suites.testSuiteRunKeepsWhatASaveReplacedMeanwhile · #26szbpgs · term

A definition a save replaces while a suite runs is still part of what the run executed: its result holds only for that version of it. (A run used to lose what no name pointed at by the time it ended, and then held for every later version of it.)

import { okIn, plainEnv, strip, tcmBin, tcmIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

export const testSuiteRunKeepsWhatASaveReplacedMeanwhile = async () => {
  const dir = tempDir("suites");
  const ok = (...args: string[]) => okIn(dir, ...args);
  const scratch = (ns: string, text: string) =>
    fs.writeFileSync(path.join(dir, "scratch.ts"), `// tcm: namespace ${ns}\n${text}`);
  ok("init");
  scratch(
    "sayer",
    `export const x = () => "one";
export const main = () => console.log(x());
export const manifest = { name: "sayer", bin: { say: "main" }, suites: { say: ["e2e"] } };
`,
  );
  ok("add");
  // the first run waits, once it has run the command, until it's told to go on (a file "go")
  scratch(
    "sayer.e2e",
    `import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
export const testSays = () => {
  const r = spawnSync(process.execPath, [process.env.SAY_BIN!], { encoding: "utf8" });
  assert.equal(r.stdout, "one\\n");
  if (fs.existsSync("go")) return;
  fs.writeFileSync("started", "");
  const until = Date.now() + 120_000;
  while (!fs.existsSync("go") && Date.now() < until)
    Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
};
`,
  );
  ok("add");
  const run = spawn(process.execPath, [tcmBin(), "test"], {
    cwd: dir,
    env: plainEnv(),
    stdio: ["ignore", "pipe", "pipe"],
  });
  let out = "";
  run.stdout.setEncoding("utf8").on("data", (d: string) => (out += d));
  run.stderr.setEncoding("utf8").on("data", (d: string) => (out += d));
  const ended = new Promise<number | null>((resolve) => run.on("close", resolve));
  try {
    const until = Date.now() + 120_000;
    while (!fs.existsSync(path.join(dir, "started"))) {
      if (Date.now() > until) throw new Error(`the suite didn't start:\n${out}`);
      await new Promise((resolve) => setTimeout(resolve, 50));
    }
    // while it runs, another process saves x
    scratch("sayer", `export const x = () => "two";\n`);
    ok("update");
  } finally {
    fs.writeFileSync(path.join(dir, "go"), "");
  }
  assert.equal(await ended, 0, out);
  assert.match(strip(out), /suites: 1 passed, 0 failed\n/);
  // the run ran x as it was: x has changed since, so the suite runs again (and fails: it says "two")
  const after = tcmIn(dir, "test");
  assert.equal(after.status, 1, after.out);
  assert.doesNotMatch(after.out, /cached/);
  // x as the run ran it: the run holds again
  scratch("sayer", `export const x = () => "one";\n`);
  ok("update");
  assert.match(ok("test"), /suites: 1 passed, 0 failed \(1 cached\)/);
}

uses