runSuite

tcmts.cli.suites.runSuite · #5gqixqet · term · runs on the server

uses node:fs

Run a suite's test as a program of its own (passing if it returns), its command being entry: its result, and what it executed (named as names had it when the run started: #htnfjrhl).

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { binVariable, executed, SUITE_TIMEOUT_MS, tcmsEnded } from "#codebase/tcmts/cli/suites";
import type { Suite } from "#codebase/tcmts/cli/suites";
import { nodeArgs, writeRunner } from "#codebase/tcmts/loader";
import type { TestResult } from "#codebase/tcmts/store";
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

export function runSuite(
  cb: Codebase,
  s: Suite,
  entry: string,
  names?: ReadonlyMap<string, string>,
): Promise<{ result: TestResult; ran: Record<string, string> }> {
  const coverage = fs.mkdtempSync(path.join(os.tmpdir(), "tcm-suite-"));
  const started = Date.now();
  // (run from a test, node:test's own setting would make this run report to that test's runner)
  // (nor TCM_CODEBASE: a suite works in directories of its own; nor what the MCP server set for its own commands,
  // when a tool runs the tests: TCM_BRIEF's shorter reports, and TCM_BRANCH, the session's branch of this codebase;
  // nor what a tcm run further out set its commands to, TCM_COMMANDS)
  const {
    NODE_TEST_CONTEXT: _,
    TCM_CODEBASE: __,
    TCM_BRIEF: ___,
    TCM_BRANCH: ____,
    TCM_COMMANDS: _____,
    ...env
  } = process.env;
  const child = spawn(
    process.execPath,
    [...nodeArgs(cb.lay), writeRunner(s.hash, cb.source(), cb.lay)],
    {
      cwd: cb.lay.root,
      env: {
        ...env,
        [binVariable(s.bin)]: entry,
        // (what it runs takes no slot of its own: it's in this one, `inSuiteSlot`)
        TCM_SUITE_SLOT: "1",
        NODE_V8_COVERAGE: coverage,
        NO_COLOR: "1",
        FORCE_COLOR: "0",
      },
      stdio: ["ignore", "pipe", "pipe"],
    },
  );
  let out = "";
  // a test that hangs (something it started keeps it alive) fails, rather than keeping the run going for good
  const limit = setTimeout(() => {
    out += `\nError: timed out: still running after ${SUITE_TIMEOUT_MS / 60_000} minutes\n`;
    child.kill("SIGKILL");
  }, SUITE_TIMEOUT_MS);
  child.stdout.setEncoding("utf8").on("data", (d: string) => (out += d));
  child.stderr.setEncoding("utf8").on("data", (d: string) => (out += d));
  return new Promise((resolve) => {
    child.on("close", async (code) => {
      clearTimeout(limit);
      const ok = code === 0;
      // what it threw: the error's first lines
      const thrown = ok ? undefined : /^(\w*Error\b.*(?:\n(?! {4}at ).*){0,12})/m.exec(out)?.[1];
      const message = ok ? "" : (thrown?.trim() ?? `exited ${code}`);
      const output = ok ? "" : out.trim().split("\n").slice(0, 80).join("\n");
      // what ran, once every tcm it started has ended (one that won't, or code no name follows: what ran isn't known,
      // so it isn't cached)
      const ran = ((await tcmsEnded(coverage, 15_000)) && executed(cb, coverage, names)) || {};
      fs.rmSync(coverage, { recursive: true, force: true });
      resolve({ result: { hash: s.hash, ok, message, output, ms: Date.now() - started }, ran });
    });
  });
}

uses

used by