runTest

tcmts.cli.run.runTest · #q36cmzmp · term · runs on the server

uses node:fs

Runs tcm test.

import { slotWaits, waitTime } from "#codebase/tcmts/cli/budget";
import { resolveBuild } from "#codebase/tcmts/cli/builds";
import { routeTest } from "#codebase/tcmts/cli/commands";
import { execute } from "#codebase/tcmts/cli/main";
import type { Context } from "#codebase/tcmts/cli/run";
import { bold, dim } from "#codebase/tcmts/cli/style";
import { runSuites, suitesIn } from "#codebase/tcmts/cli/suites";
import { isTestPath, printOutcomes, runCached, summary } from "#codebase/tcmts/cli/testing";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { parse } from "@bomb.sh/router";
import fs from "node:fs";
import path_1 from "node:path";

export async function runTest({ cb, cmd, rest, cwd, kept }: Context): Promise<number> {
  const { model } = execute(cmd, parse(routeTest(), { argv: rest }));
  if (model.definitions && model.suites)
    throw new UsageError(
      "--definitions and --suites leave each other nothing to run: give one, or neither for both",
    );
  // the tests named, or under the namespaces named (all of them, with none)
  const wanted = model.names.length ? model.names : [""];
  const ns = wanted.join(", ");
  const under = (p: string) => wanted.some((w) => !w || p === w || p.startsWith(w + "."));
  // test definitions a package runs its command with are run as programs of their own, with the suites
  const declared = [
    ...new Map(wanted.flatMap((w) => suitesIn(cb, w)).map((s) => [s.path, s])).values(),
  ];
  const commandTests = new Set(suitesIn(cb, "").map((s) => s.path));
  const names = model.suites
    ? []
    : cb.store
        .names()
        .filter(
          (n) =>
            n.kind !== "type" && isTestPath(n.path) && !commandTests.has(n.path) && under(n.path),
        );
  const suites = model.definitions ? [] : declared;
  if (!names.length && !suites.length) {
    console.log(
      dim(
        model.suites
          ? `no suites${ns ? ` under ${ns}` : ""} (a package's manifest declares them: suites: { <command>: [<namespaces>] })`
          : `no tests${ns ? ` under ${ns}` : ""} (tests are definitions named test… or under a \`tests\` namespace)`,
      ),
    );
    return 0;
  }
  let ok = true;
  if (names.length) {
    const outcomes = runCached(
      cb,
      names.map((n) => ({ path: n.path, rec: cb.store.getDef(n.hash)! })),
      cb.source(),
      model.fresh,
    );
    printOutcomes(outcomes);
    console.log(`\n${summary(outcomes)}`);
    ok = outcomes.every((o) => o.result.ok);
  }
  // suites: test files running a package's command, as a program
  if (suites.length) {
    console.log(
      `${names.length ? "\n" : ""}${bold("suites")} ${dim(`(running ${[...new Set(suites.map((s) => s.bin))].join(", ")})`)}`,
    );
    // each as it finishes: a run can take minutes
    const builds = new Map(
      (model.bin ?? []).map((b) => {
        const at = b.indexOf("=");
        if (at < 1) throw new UsageError(`--bin takes cmd=<path>, not '${b}'`);
        // (tcm's: a kept build's name too, `tcm=previous`)
        const command = b.slice(0, at);
        const file = b.slice(at + 1);
        return [
          command,
          command === "tcm" && !fs.existsSync(path_1.resolve(cwd, file))
            ? resolveBuild(file, kept, cwd)
            : path_1.resolve(cwd, file),
        ] as const;
      }),
    );
    // (and how long it waited for the machine's suite slots, if long enough to matter)
    const waits = slotWaits((l) => console.error(dim(l)));
    const outcomes = await runSuites(
      cb,
      suites,
      model.fresh,
      (o) => printOutcomes([o]),
      builds,
      waits,
    );
    const waited = waits.waited();
    console.log(
      `\nsuites: ${summary(outcomes)}${waited >= 1000 ? dim(`, ${waitTime(waited)} waiting for slots`) : ""}`,
    );
    ok &&= outcomes.every((o) => o.result.ok);
  }
  return ok ? 0 : 1;
}

uses

used by