Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/actions/ci-cli-coverage-shard/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ runs:
exit 0
;;
esac
npx tsx scripts/checks/e2e-mock-parity.ts --base "$base" --head "$head"
parity_check=scripts/checks/e2e-mock-parity.mts
if [ ! -f "$parity_check" ]; then
parity_check=scripts/checks/e2e-mock-parity.ts
fi
if [ ! -f "$parity_check" ]; then
echo "::error title=Missing E2E mock parity entrypoint::Expected scripts/checks/e2e-mock-parity.mts or scripts/checks/e2e-mock-parity.ts."
exit 1
fi
npx tsx "$parity_check" --base "$base" --head "$head"

- name: Build TypeScript plugin
shell: bash
Expand Down
87 changes: 87 additions & 0 deletions test/e2e-mock-parity.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { type SpawnSyncReturns, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import {
isMockParityRelevantSourceChange,
type MockParityManifest,
validateMockParity,
} from "../scripts/checks/e2e-mock-parity";
import { type CompositeAction, readYaml } from "./helpers/e2e-workflow-contract";

const live = "test/e2e/live/example.test.ts";
const fast = "test/e2e/support/example.test.ts";
Expand Down Expand Up @@ -108,3 +113,85 @@ describe("changed live E2E mock parity", () => {
).toEqual([`${live}: liveOnlyReason must be a string`]);
});
});

describe("trusted E2E parity entrypoint selection", () => {
const action = readYaml<CompositeAction>(".github/actions/ci-cli-coverage-shard/action.yaml");
const parityStep = action.runs.steps.find(
(step) => step.name === "Validate changed live E2E mock parity",
);
const parityRun = parityStep?.run ?? "";
const parityEnv = parityStep?.env ?? {};
const stem = "scripts/checks/e2e-mock-parity";

function withParityEntrypoints(
extensions: readonly string[],
verify: (result: SpawnSyncReturns<string>, commandLog: string) => void,
): void {
const temp = mkdtempSync(join(tmpdir(), "nemoclaw-e2e-parity-entrypoint-"));
const fakeBin = join(temp, "bin");
const commandLog = join(temp, "command.json");
mkdirSync(fakeBin);
mkdirSync(join(temp, "scripts", "checks"), { recursive: true });
writeFileSync(
join(fakeBin, "npx"),
[
"#!/usr/bin/env node",
'const fs = require("node:fs");',
"fs.appendFileSync(process.env.COMMAND_LOG, `${JSON.stringify(process.argv.slice(2))}\\n`);",
].join("\n"),
{ mode: 0o755 },
);
for (const extension of extensions) {
writeFileSync(join(temp, `${stem}.${extension}`), "// fixture\n");
}

try {
verify(
spawnSync("bash", ["-c", parityRun], {
cwd: temp,
encoding: "utf8",
env: {
...process.env,
...parityEnv,
COMMAND_LOG: commandLog,
EVENT_NAME: "pull_request",
PATH: `${fakeBin}:${process.env.PATH ?? ""}`,
PUSH_BASE_SHA: "unused",
},
timeout: 5_000,
}),
commandLog,
);
} finally {
rmSync(temp, { force: true, recursive: true });
}
}

it.each([
{
extensions: ["mts", "ts"],
expected: `${stem}.mts`,
title: "prefers .mts when both entrypoints exist",
},
{ extensions: ["mts"], expected: `${stem}.mts`, title: "runs the migrated .mts entrypoint" },
{ extensions: ["ts"], expected: `${stem}.ts`, title: "falls back to the .ts entrypoint" },
])("$title (#6921)", ({ extensions, expected }) => {
withParityEntrypoints(extensions, (result, commandLog) => {
expect(result.status, String(result.stderr)).toBe(0);
expect(
readFileSync(commandLog, "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line)),
).toEqual([["tsx", expected, "--base", "HEAD^1", "--head", "HEAD^2"]]);
});
});

it("rejects a missing parity entrypoint (#6921)", () => {
withParityEntrypoints([], (result, commandLog) => {
expect(result.status, String(result.stderr)).toBe(1);
expect(existsSync(commandLog)).toBe(false);
expect(String(result.stdout)).toContain("Missing E2E mock parity entrypoint");
});
});
});
3 changes: 0 additions & 3 deletions test/pr-workflow-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -975,9 +975,6 @@ describe("pull request and main workflow contracts", () => {
expect(parityStep.run).toContain("base=HEAD^1");
expect(parityStep.run).toContain("head=HEAD^2");
expect(parityStep.run).toContain('base="$PUSH_BASE_SHA"');
expect(parityStep.run).toContain(
'npx tsx scripts/checks/e2e-mock-parity.ts --base "$base" --head "$head"',
);

const trustedCapabilityProbe = requiredWorkflowStep(
prWorkflow.jobs["cli-test-shards"],
Expand Down
Loading