testParallelChecksMatchOneAtATime

tcmts.cli.test.e2e.sync.testParallelChecksMatchOneAtATime · #ay23wfff · term

Checking what's received in several checker processes at once stores what checking it in one does, and still rejects tampered source among many.

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

export const testParallelChecksMatchOneAtATime = () =>
  withChildren(async (keep) => {
    const root = tempDir("sync parallel");
    // a server checking in 4 processes, and one checking itself
    const serve = async (name: string, checkers: string) => {
      const s = codebase(root, name);
      fs.rmSync(path.join(s.dir, "scratch.ts"));
      const child = keep(
        spawn(process.execPath, [tcmBin(), "serve", "--port", "0", "--token", "tok"], {
          cwd: s.dir,
          env: { ...plainEnv(), TCM_CHECKERS: checkers },
        }),
      );
      let out = "";
      child.stdout!.setEncoding("utf8").on("data", (d: string) => (out += d));
      const url = await new Promise<string>((resolve, reject) => {
        const check = () => {
          const m = /url\s+(https?:\/\/[\d.]+:\d+)/.exec(out);
          if (m) resolve(m[1]!);
          else if (child.exitCode !== null) reject(new Error(out));
          else setTimeout(check, 20);
        };
        check();
      });
      return { url, log: () => out };
    };
    const many = await serve("many", "4");
    const one = await serve("one", "1");

    // 90 components using each other (and a type, a class, a function) across any cut into runs
    const a = codebase(root, "a");
    const defs = [
      "// tcm: namespace lib",
      "export type Point = { x: number; y: number };",
      "export class Box { p: Point; constructor(p: Point) { this.p = p; } size(): number { return this.p.x * this.p.y; } }",
      "export const v0 = (p: Point): number => new Box(p).size();",
      ...Array.from(
        { length: 87 },
        (_, i) => `export const v${i + 1} = (p: Point): number => v${i}(p) + ${i} /* ${i} */;`,
      ),
    ];
    a.scratch(defs.join("\n") + "\n");
    a.tcm("add");
    a.tcm("remote", "add", "many", many.url, "--token", "tok");
    a.tcm("remote", "add", "one", one.url, "--token", "tok");
    assert.match(a.tcm("push", "many"), /sent 90 definition\(s\)/);
    assert.match(a.tcm("push", "one"), /sent 90 definition\(s\)/);
    await new Promise((r) => setTimeout(r, 200));
    assert.match(many.log(), /received 90 definition\(s\), checked by [2-4] processes/);
    assert.match(one.log(), /received 90 definition\(s\)\n/);

    const call = async (base: string, route: string, body: unknown) =>
      (await (
        await fetch(`${base}/${route}`, {
          method: "POST",
          headers: { authorization: "Bearer tok" },
          body: JSON.stringify(body),
        })
      ).json()) as any;
    const hashes = (await call(many.url, "branch", { name: "main" })).branch.names.map(
      (n: { hash: string }) => n.hash,
    );
    const stored = async (base: string) =>
      new Map(
        ((await call(base, "get", { hashes })).records as Record<string, unknown>[]).map((r) => [
          r.hash,
          JSON.stringify([r.ir, r.deps, r.signature, r.shape, r.componentHash, r.componentIndex]),
        ]),
      );
    const [fromMany, fromOne] = [await stored(many.url), await stored(one.url)];
    assert.equal(fromMany.size, 90);
    assert.deepEqual(fromMany, fromOne, "the same, checked either way");

    // and one tampered among many is still caught, by whichever process checks it
    const third = await serve("third", "4");
    const records = (await call(many.url, "get", { hashes })).records as {
      hash: string;
      sourceText: string;
    }[];
    const evil = records.map((r, i) =>
      i === 70
        ? {
            ...r,
            sourceText: r.sourceText.replace("/* ", "/* tampered ").replace(/\+ \d+/, "+ 1000"),
          }
        : r,
    );
    const res = await fetch(`${third.url}/put`, {
      method: "POST",
      headers: { authorization: "Bearer tok" },
      body: JSON.stringify({ records: evil }),
    });
    assert.equal(res.status, 400);
    assert.match(
      ((await res.json()) as { error: string }).error,
      /source does not match their hashes/,
    );
  })

uses