diff --git a/pkg/evidence/attestation/oci_test.go b/pkg/evidence/attestation/oci_test.go index 49c84f940..834bc0919 100644 --- a/pkg/evidence/attestation/oci_test.go +++ b/pkg/evidence/attestation/oci_test.go @@ -288,7 +288,7 @@ func readEvidencePackageFiles(t *testing.T, storePath, manifestDigest string) ma tarReader := tar.NewReader(gzipReader) for { header, nextErr := tarReader.Next() - if nextErr == io.EOF { + if stderrors.Is(nextErr, io.EOF) { break } if nextErr != nil { diff --git a/pkg/oci/archive_checkpoint_c_test.go b/pkg/oci/archive_checkpoint_c_test.go index 997d3f0c3..7433cbc1a 100644 --- a/pkg/oci/archive_checkpoint_c_test.go +++ b/pkg/oci/archive_checkpoint_c_test.go @@ -201,7 +201,7 @@ func assertCanonicalArchiveBytes(t *testing.T, data []byte, prefix string) { } for { header, nextErr := tReader.Next() - if nextErr == io.EOF { + if stderrors.Is(nextErr, io.EOF) { break } if nextErr != nil { diff --git a/pkg/oci/archive_test.go b/pkg/oci/archive_test.go index eaecfc4fe..99e8f0429 100644 --- a/pkg/oci/archive_test.go +++ b/pkg/oci/archive_test.go @@ -368,7 +368,7 @@ func readArchiveFiles(t *testing.T, archivePath string) (map[string]string, []st var entries []string for { header, nextErr := tarReader.Next() - if nextErr == io.EOF { + if stderrors.Is(nextErr, io.EOF) { break } if nextErr != nil { diff --git a/pkg/oci/push_test.go b/pkg/oci/push_test.go index 9454a0735..857dd6b6a 100644 --- a/pkg/oci/push_test.go +++ b/pkg/oci/push_test.go @@ -106,7 +106,7 @@ func extractFilesFromOCIArtifact(t *testing.T, ociLayoutDir, digest string) map[ tr := tar.NewReader(gzr) for { header, err := tr.Next() - if err == io.EOF { + if stderrors.Is(err, io.EOF) { break } if err != nil { diff --git a/tools/coverage/build.go b/tools/coverage/build.go index b29f5ecf5..098d26165 100644 --- a/tools/coverage/build.go +++ b/tools/coverage/build.go @@ -171,14 +171,14 @@ func newRow(kind Kind, item string, harnesses map[Harness]bool, unwiredUAT bool) func hardwareCadence(h map[Harness]bool, status Status) (hardware, cadence string) { switch { case status == StatusStubbed: - return "GPU (unwired)", "—" + return "GPU (unwired)", emDash case h[HarnessUAT] || h[HarnessGPUNightly]: return "GPU (H100, real)", "nightly" case h[HarnessChainsaw] || h[HarnessKWOK]: return "simulated / none", "per-PR" case h[HarnessDemo]: - return "docs", "—" + return "docs", emDash default: - return "—", "—" + return emDash, emDash } } diff --git a/tools/coverage/render.go b/tools/coverage/render.go index 7283bf059..ef10a326f 100644 --- a/tools/coverage/render.go +++ b/tools/coverage/render.go @@ -19,6 +19,9 @@ import ( "strings" ) +// emDash is the placeholder rendered for an empty matrix cell. +const emDash = "—" + // Render returns the full Markdown body of docs/user/coverage-matrix.md. // // The page is fully generated (no splice markers): this keeps it inside the @@ -94,7 +97,7 @@ func renderTable(heading string, m Matrix, kind Kind) string { } note := r.Note if note == "" { - note = "—" + note = emDash } fmt.Fprintf(&b, "| `%s` | %s | %s | %s | %s | %s |\n", r.Item, exercisedBy(r.Harnesses), r.Hardware, r.Cadence, r.Status, note) @@ -112,7 +115,7 @@ func exercisedBy(h map[Harness]bool) string { } } if len(parts) == 0 { - return "—" + return emDash } return strings.Join(parts, ", ") } diff --git a/validators/conformance/secure_access_check.go b/validators/conformance/secure_access_check.go index ab0c78d83..22802146e 100644 --- a/validators/conformance/secure_access_check.go +++ b/validators/conformance/secure_access_check.go @@ -67,6 +67,11 @@ const ( // GPU at all (no ResourceClaims, no nvidia.com/gpu limits). noAllocProbePrefix = "no-alloc-probe-" + // allocationModeExactCount is the Kubernetes DRA DeviceRequest + // allocationMode enum value. It is an upstream API constant, not an AICR + // internal token. See k8s.io/api/resource/{v1beta1,v1,v1beta2}.DeviceRequest. + allocationModeExactCount = "ExactCount" + // cudaTestImage runs the in-container GPU visibility verification. cudaTestImage = "nvidia/cuda:12.9.0-base-ubuntu24.04" // bashShell is the shell binary used by cudaTestImage containers. @@ -1587,12 +1592,12 @@ func buildResourceClaim(run *gpuTestRun, version string) *unstructured.Unstructu } if version == versionV1beta1 { request["deviceClassName"] = draDriverGPU - request["allocationMode"] = "ExactCount" + request["allocationMode"] = allocationModeExactCount request["count"] = int64(1) } else { request["exactly"] = map[string]interface{}{ "deviceClassName": draDriverGPU, - "allocationMode": "ExactCount", + "allocationMode": allocationModeExactCount, "count": int64(1), } } diff --git a/validators/performance/inference_perf_constraint.go b/validators/performance/inference_perf_constraint.go index 741edb437..4b643caee 100644 --- a/validators/performance/inference_perf_constraint.go +++ b/validators/performance/inference_perf_constraint.go @@ -270,6 +270,15 @@ const ( mainContainerName = "main" ) +// inferenceSkip* are the result.status strings returned when the inference +// performance check cannot run. The "skipped " prefix is contractual: +// inference_perf.go dispatches on it via strings.HasPrefix(result.status, +// "skipped") to emit CTRF Skip status. +const ( + inferenceSkipMsgNoDynamoPlatform = "skipped - dynamo-platform not in recipe components" + inferenceSkipMsgCRDNotInstalled = "skipped - DynamoGraphDeployment CRD not installed on cluster (dynamo-platform component declared but operator not deployed yet)" +) + type inferenceRoutingMode string const ( @@ -418,7 +427,7 @@ func validateInferencePerf(ctx *validators.Context) (*inferenceResult, error) { // Guard B: dynamo-platform must be declared in the recipe's componentRefs. // Presence of a Criteria block is independent and not consulted here. if !hasDynamoPlatform(ctx) { - return &inferenceResult{status: "skipped - dynamo-platform not in recipe components"}, nil + return &inferenceResult{status: inferenceSkipMsgNoDynamoPlatform}, nil } // Guard C: the Dynamo operator CRD must actually be installed on the @@ -437,7 +446,7 @@ func validateInferencePerf(ctx *validators.Context) (*inferenceResult, error) { return nil, crdErr } if !installed { - return &inferenceResult{status: "skipped - DynamoGraphDeployment CRD not installed on cluster (dynamo-platform component declared but operator not deployed yet)"}, nil + return &inferenceResult{status: inferenceSkipMsgCRDNotInstalled}, nil } // Build workload configuration from cluster state. Callees already diff --git a/validators/performance/inference_perf_test.go b/validators/performance/inference_perf_test.go index a057a9402..680ff02c7 100644 --- a/validators/performance/inference_perf_test.go +++ b/validators/performance/inference_perf_test.go @@ -3126,3 +3126,14 @@ func TestRejectUnsupportedGPUTopology(t *testing.T) { }) } } + +func TestInferenceSkipMessagesHaveContractualPrefix(t *testing.T) { + for _, msg := range []string{ + inferenceSkipMsgNoDynamoPlatform, + inferenceSkipMsgCRDNotInstalled, + } { + if !strings.HasPrefix(msg, "skipped") { + t.Fatalf("skip status %q is missing contractual 'skipped' prefix — inference_perf.go dispatches on it", msg) + } + } +} diff --git a/validators/performance/nccl_all_reduce_bw.go b/validators/performance/nccl_all_reduce_bw.go index be858098e..96f5245bf 100644 --- a/validators/performance/nccl_all_reduce_bw.go +++ b/validators/performance/nccl_all_reduce_bw.go @@ -62,6 +62,21 @@ func constraintNameForVariant(variant ncclVariant) string { } } +// ncclVariantDisplayName returns a human-readable, non-stuttering name for the +// variant suitable for skip messages and logs. +func ncclVariantDisplayName(variant ncclVariant) string { + switch variant { + case variantNET: + return "NET" + case variantNVLS: + return "NVLS" + case variantDefault: + return "default" + default: + return string(variant) + } +} + func checkNCCLAllReduceBWVariant(ctx *validators.Context, variant ncclVariant) error { name := constraintNameForVariant(variant) constraint, found := findPerformanceConstraint(ctx, name) diff --git a/validators/performance/nccl_all_reduce_bw_constraint.go b/validators/performance/nccl_all_reduce_bw_constraint.go index 11013fdc9..7fdb4a4b1 100644 --- a/validators/performance/nccl_all_reduce_bw_constraint.go +++ b/validators/performance/nccl_all_reduce_bw_constraint.go @@ -64,6 +64,22 @@ const ( ncclTrainingRuntimeName = "nccl-all-reduce-runtime" ) +// skipMsg* are the constraint-result strings returned when the NCCL check cannot +// run. The "skipped " prefix is contractual: nccl_all_reduce_bw.go:78 dispatches +// on it via strings.HasPrefix(actual, "skipped") to emit CTRF Skip status. +const ( + skipMsgNCCLNoInput = "skipped - requires Service + Accelerator" + skipMsgNCCLNotImplemented = "skipped - requires Service + Accelerator to be implemented" + skipMsgNCCLFewNodes = "skipped - requires at least 2 GPU nodes for EW fabric test" +) + +// skipMsgNCCLNoProfile returns the skip result when the resolved benchmark +// profile does not implement the requested NCCL variant. +func skipMsgNCCLNoProfile(target ncclBenchmarkTarget, variant ncclVariant) string { + return fmt.Sprintf("skipped - benchmark profile %s does not implement the %s NCCL variant", + target.String(), ncclVariantDisplayName(variant)) +} + // Package-level GVR definitions for Kubeflow Trainer CRDs used by both // applyNCCLResources and cleanupNCCLResources. var ( @@ -250,7 +266,7 @@ func validateNcclAllReduceBw(ctx *validators.Context, constraint recipe.Constrai // Skip unless the validation targets a supported service + accelerator combination. if ctx.ValidationInput == nil { slog.Info("Skipping NCCL All Reduce bandwidth validation: no validation") - return "skipped - requires Service + Accelerator", true, nil + return skipMsgNCCLNoInput, true, nil } service := ctx.ValidationInput.Criteria.Service @@ -321,10 +337,9 @@ func validateNcclAllReduceBw(ctx *validators.Context, constraint recipe.Constrai // The profile itself is valid (resolveNCCLBenchmarkProfile fails // closed on unknown pairs); it just doesn't implement this variant // — e.g. gb200/eks covers net and nvls but not the default check. - return fmt.Sprintf("skipped - benchmark profile %s does not implement the %s NCCL variant", - target.String(), constraintNameForVariant(variant)), true, nil + return skipMsgNCCLNoProfile(target, variant), true, nil } - return "skipped - requires Service + Accelerator to be implemented", true, nil + return skipMsgNCCLNotImplemented, true, nil } // Extract threshold from constraint @@ -373,7 +388,7 @@ func validateNcclAllReduceBw(ctx *validators.Context, constraint recipe.Constrai if gpuConfig.WorkerCount < 2 { slog.Info("Skipping NCCL All Reduce bandwidth validation: requires at least 2 GPU nodes for EW fabric test", "nodes", gpuConfig.WorkerCount) - return "skipped - requires at least 2 GPU nodes for EW fabric test", true, nil + return skipMsgNCCLFewNodes, true, nil } // Preflight cluster-side prerequisites before spending TrainJob time. diff --git a/validators/performance/nccl_benchmark_profile_test.go b/validators/performance/nccl_benchmark_profile_test.go index 13f971c16..bedc0f876 100644 --- a/validators/performance/nccl_benchmark_profile_test.go +++ b/validators/performance/nccl_benchmark_profile_test.go @@ -255,7 +255,7 @@ func TestValidateNcclAllReduceBwProfileGate(t *testing.T) { profile: "gb200/eks", constraint: thresholdC(checkNameNCCLAllReduceBW, ">= 40"), variant: variantDefault, - wantMsg: "skipped - benchmark profile gb200/eks does not implement the nccl-all-reduce-bw NCCL variant", + wantMsg: "skipped - benchmark profile gb200/eks does not implement the default NCCL variant", }, { name: "malformed profile fails closed before cluster access", @@ -373,8 +373,14 @@ func TestValidateNcclAllReduceBwProfileClusterPath(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - want := "skipped - requires at least 2 GPU nodes for EW fabric test" + want := skipMsgNCCLFewNodes if !passed || msg != want { t.Errorf("got (%q, %v), want (%q, true)", msg, passed, want) } } + +func TestSkipMsgNCCLFewNodesHasContractualPrefix(t *testing.T) { + if !strings.HasPrefix(skipMsgNCCLFewNodes, "skipped") { + t.Fatal("skip status prefix is contractual — nccl_all_reduce_bw.go dispatches on it") + } +} diff --git a/validators/performance/nccl_benchmark_runtime_test.go b/validators/performance/nccl_benchmark_runtime_test.go index 5d8dedce9..490d9053e 100644 --- a/validators/performance/nccl_benchmark_runtime_test.go +++ b/validators/performance/nccl_benchmark_runtime_test.go @@ -321,7 +321,7 @@ spec: if err != nil { t.Fatalf("unexpected error: %v", err) } - want := "skipped - requires at least 2 GPU nodes for EW fabric test" + want := skipMsgNCCLFewNodes if !passed || msg != want { t.Errorf("got (%q, %v), want (%q, true) — sizing must narrow by the runtime's own nodeSelector", msg, passed, want) } @@ -356,7 +356,7 @@ func TestValidateNcclAllReduceBwCustomRuntimeClusterPath(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - want := "skipped - requires at least 2 GPU nodes for EW fabric test" + want := skipMsgNCCLFewNodes if !passed || msg != want { t.Errorf("got (%q, %v), want (%q, true)", msg, passed, want) }