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
23 changes: 5 additions & 18 deletions test/e2e/support/jetson-workflow-boundary.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { describe, expect, it } from "vitest";
import YAML from "yaml";
import { validateE2eWorkflowBoundary } from "../../../tools/e2e/workflow-boundary.mts";
import {
validateE2eWorkflowBoundary,
validateJetsonRunnerDispatchBoundary,
} from "../../../tools/e2e/workflow-boundary.mts";
import { readWorkflow } from "../../helpers/e2e-workflow-contract.ts";

function validateWorkflowMutation(
mutate: (workflow: ReturnType<typeof readWorkflow>) => void,
): string[] {
const workflow = readWorkflow();
mutate(workflow);
const directory = mkdtempSync(join(tmpdir(), "nemoclaw-jetson-guard-"));
const workflowPath = join(directory, "workflow.yaml");
try {
writeFileSync(workflowPath, YAML.stringify(workflow));
return validateE2eWorkflowBoundary(workflowPath);
} finally {
rmSync(directory, { force: true, recursive: true });
}
return validateJetsonRunnerDispatchBoundary(workflow);
}

describe("Jetson nvmap GPU E2E workflow boundary", () => {
Expand Down Expand Up @@ -58,16 +49,12 @@ describe("Jetson nvmap GPU E2E workflow boundary", () => {
guard!.if = "always()";
const authIndex = steps.findIndex((step) => step.name === "Authenticate to Docker Hub");
steps.splice(authIndex + 1, 0, guard!);
steps.find((step) => step.name === "Upload Jetson nvmap GPU artifacts")!.if = "success()";
steps.find((step) => step.name === "Clean up Docker auth")!.if = "success()";
});
expect(guardErrors).toEqual(
expect.arrayContaining([
"jetson-nvmap-gpu job must use ubuntu-latest unless allow_jetson_runner_queue is true",
"jetson-nvmap-gpu dispatch guard must run before Docker Hub auth",
"jetson-nvmap-gpu dispatch guard must run unless allow_jetson_runner_queue is true",
"jetson-nvmap-gpu upload-e2e-artifacts invocation must run with always()",
"jetson-nvmap-gpu Docker Hub cleanup step must always run",
]),
);
});
Expand Down
14 changes: 12 additions & 2 deletions tools/e2e/workflow-boundary.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3556,6 +3556,17 @@ function validateJetsonRunnerDispatchGuard(errors: string[], jobs: WorkflowRecor
requireRunDoesNotContain(errors, guard, "linux-arm64-gpu-jetson-orin-latest-1");
}

export function validateJetsonRunnerDispatchBoundary(workflow: unknown): string[] {
const workflowRecord = asRecord(workflow);
const triggers = asRecord(workflowRecord.on ?? workflowRecord[true as unknown as string]);
const workflowDispatch = asRecord(triggers.workflow_dispatch);
const errors: string[] = [];

validateAllowJetsonRunnerQueueInput(errors, asRecord(workflowDispatch.inputs));
validateJetsonRunnerDispatchGuard(errors, asRecord(workflowRecord.jobs));
return errors;
}

function validateSandboxRlimitConnectJob(errors: string[], jobs: WorkflowRecord): void {
const jobName = "sandbox-rlimits-connect";
const job = asRecord(jobs[jobName]);
Expand Down Expand Up @@ -3624,7 +3635,6 @@ export function validateE2eWorkflowBoundary(workflowPath = DEFAULT_E2E_WORKFLOW_

const dispatchInputs = asRecord(workflowDispatch.inputs);
requireInput(errors, dispatchInputs, "targets");
validateAllowJetsonRunnerQueueInput(errors, dispatchInputs);
const jobsInput = requireInput(errors, dispatchInputs, "jobs");
const jobsDescription = stringValue(jobsInput.description);
if (!jobsDescription.includes("default-enabled tests")) {
Expand All @@ -3645,6 +3655,7 @@ export function validateE2eWorkflowBoundary(workflowPath = DEFAULT_E2E_WORKFLOW_
if (permissions.contents !== "read") errors.push("workflow permissions.contents must be read");

const jobs = asRecord(workflow.jobs);
errors.push(...validateJetsonRunnerDispatchBoundary(workflow));
const { errors: inventoryErrors, inventory: freeStandingInventory } =
deriveFreeStandingJobsInventoryFromJobs(jobs);
errors.push(...inventoryErrors);
Expand Down Expand Up @@ -4087,7 +4098,6 @@ export function validateE2eWorkflowBoundary(workflowPath = DEFAULT_E2E_WORKFLOW_

validateFreeStandingJobSelector(errors, jobs, "gateway-health-honest", "gateway-health-honest");

validateJetsonRunnerDispatchGuard(errors, jobs);
validateSandboxRlimitConnectJob(errors, jobs);

validateFreeStandingJobSelector(
Expand Down
Loading