diff --git a/tests/bdd/PLAN.md b/tests/bdd/PLAN.md index 663332d84..2f90cf257 100644 --- a/tests/bdd/PLAN.md +++ b/tests/bdd/PLAN.md @@ -35,6 +35,7 @@ regex restricted to `\$\{[A-Z0-9_]+\}`. | Step | Notes | |------|-------| | `Given environment variable {string} is set` | Fails if the env var is empty. Used at the top of any scenario that interpolates it later. | +| `Given these environment variables are set:` (table) | Requires a `name` header and one or more variable names. Fails on the first empty variable and names it in the error. | | `Given file {string} exists` | Bare-file precondition. Same semantics as `Then file {string} should exist`; use `Given` form for narrative preconditions. | ### Infrastructure bootstrap (Given) @@ -163,7 +164,7 @@ them via `${VAR}` in command strings and table cells. |-----|--------|----------| | `NVCF_CLI` | `go build` of `src/clis/nvcf-cli` at suite start | Absolute path to the freshly built CLI binary. | | `REPO_ROOT` | `git rev-parse --show-toplevel` at suite start | Absolute path to the repo root. Required when invoking `make -C deploy/stacks/self-managed` because the Makefile's `-C` changes cwd; relative paths to fixtures from there break. | -| `NGC_API_KEY` / `SAMPLE_NGC_ORG` / `SAMPLE_NGC_TEAM` | The operator's shell | Passed through unchanged. The `Given environment variable {string} is set` step asserts they are non-empty before any scenario uses them. | +| `NGC_API_KEY` / `SAMPLE_NGC_ORG` / `SAMPLE_NGC_TEAM` | The operator's shell | Passed through unchanged. An environment-variable precondition step asserts they are non-empty before any scenario uses them. | Feature files may also export their own env vars at runtime via `When I export command output to environment variable {string}`. Those diff --git a/tests/bdd/dsl/env.go b/tests/bdd/dsl/env.go new file mode 100644 index 000000000..3528fd8cd --- /dev/null +++ b/tests/bdd/dsl/env.go @@ -0,0 +1,42 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dsl + +import ( + "fmt" + "os" + "strings" +) + +// RequireEnvironmentVariables fails on the first named variable whose value +// is empty. Names are trimmed so table formatting whitespace is not semantic. +func RequireEnvironmentVariables(names []string) error { + if len(names) == 0 { + return fmt.Errorf("at least one environment variable name is required") + } + for _, rawName := range names { + name := strings.TrimSpace(rawName) + if name == "" { + return fmt.Errorf("environment variable name must not be empty") + } + if os.Getenv(name) == "" { + return fmt.Errorf("environment variable %q is not set", name) + } + } + return nil +} diff --git a/tests/bdd/dsl/env_test.go b/tests/bdd/dsl/env_test.go new file mode 100644 index 000000000..2c069e3d4 --- /dev/null +++ b/tests/bdd/dsl/env_test.go @@ -0,0 +1,51 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dsl + +import ( + "strings" + "testing" +) + +func TestRequireEnvironmentVariables(t *testing.T) { + t.Setenv("BDD_ENV_PRESENT_ONE", "one") + t.Setenv("BDD_ENV_PRESENT_TWO", "two") + if err := RequireEnvironmentVariables([]string{"BDD_ENV_PRESENT_ONE", "BDD_ENV_PRESENT_TWO"}); err != nil { + t.Fatalf("require present variables: %v", err) + } +} + +func TestRequireEnvironmentVariablesReportsMissingName(t *testing.T) { + t.Setenv("BDD_ENV_PRESENT", "present") + t.Setenv("BDD_ENV_MISSING_EXACT", "") + err := RequireEnvironmentVariables([]string{"BDD_ENV_PRESENT", "BDD_ENV_MISSING_EXACT"}) + if err == nil { + t.Fatal("expected missing-variable error") + } + if !strings.Contains(err.Error(), `"BDD_ENV_MISSING_EXACT"`) { + t.Fatalf("error %q does not name the missing variable", err) + } +} + +func TestRequireEnvironmentVariablesRejectsEmptyNames(t *testing.T) { + for _, names := range [][]string{nil, {""}, {" "}} { + if err := RequireEnvironmentVariables(names); err == nil { + t.Fatalf("RequireEnvironmentVariables(%q) succeeded, want error", names) + } + } +} diff --git a/tests/bdd/features/multi-cluster-eks-helmfile.feature b/tests/bdd/features/multi-cluster-eks-helmfile.feature index 5a2a2c7d5..26e2f17f6 100644 --- a/tests/bdd/features/multi-cluster-eks-helmfile.feature +++ b/tests/bdd/features/multi-cluster-eks-helmfile.feature @@ -53,15 +53,17 @@ Feature: Install a multi-cluster NVCF stack across two pre-provisioned EKS clust # --compute-context ${EKS_COMPUTE_CONTEXT} Background: - Given environment variable "NVCF_CLI" is set - And environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "REPO_ROOT" is set - And environment variable "EKS_CONTEXT" is set - And environment variable "EKS_COMPUTE_CONTEXT" is set - And environment variable "EKS_COMPUTE_CLUSTER_NAME" is set - And environment variable "EKS_REGION" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | REPO_ROOT | + | EKS_CONTEXT | + | EKS_COMPUTE_CONTEXT | + | EKS_COMPUTE_CLUSTER_NAME | + | EKS_REGION | # Helmfile pulls OCI charts through helm, so host-side helm # registry auth must be present before any helmfile sync. # Keep $NGC_API_KEY unbraced so the BDD runner does not expand diff --git a/tests/bdd/features/multi-cluster-helmfile.feature b/tests/bdd/features/multi-cluster-helmfile.feature index 54f1b44ae..9f08c4809 100644 --- a/tests/bdd/features/multi-cluster-helmfile.feature +++ b/tests/bdd/features/multi-cluster-helmfile.feature @@ -27,9 +27,11 @@ Feature: Install a local multi-cluster NVCF stack with Helmfile Rule: Helmfile installs the control plane on the control-plane cluster Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | # The multi-cluster fixture starts from local service-DNS # endpoint values, then the Background overlays # operator-specific registry values before the first Helmfile @@ -160,8 +162,10 @@ Feature: Install a local multi-cluster NVCF stack with Helmfile Rule: Helmfile registers and installs NVCA on the compute cluster Background: - Given environment variable "NVCF_CLI" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | REPO_ROOT | # This rule depends on the earlier control-plane scenario in the # same feature run. That scenario authors local-bdd.yaml with # the compute-reachable endpoints, creates the pull secrets, and diff --git a/tests/bdd/features/multi-cluster-up.feature b/tests/bdd/features/multi-cluster-up.feature index 89401d3b4..5a21e03da 100644 --- a/tests/bdd/features/multi-cluster-up.feature +++ b/tests/bdd/features/multi-cluster-up.feature @@ -8,16 +8,18 @@ Feature: Bring up a local multi-cluster NVCF stack with the CLI Rule: self-hosted install installs the control plane; compute-plane CLIs register and install each compute cluster Background: - Given environment variable "NVCF_CLI" is set - And environment variable "NGC_API_KEY" is set # SAMPLE_NGC_ORG / SAMPLE_NGC_TEAM are consumed by # `make build-and-deploy-multicluster` (the credential provider # validation step) when the `multi-cluster ncp-local compute # clusters are running` step runs the build target. Without them, # that target fails at CREDENTIAL PROVIDER VALIDATION and skips # the gateway API setup. - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | # self-hosted install --env local reads operator-authored local # secrets files from both split stacks: # deploy/stacks/self-managed/secrets/local-secrets.yaml (control diff --git a/tests/bdd/features/observability-all.feature b/tests/bdd/features/observability-all.feature index 030070dc0..84050469a 100644 --- a/tests/bdd/features/observability-all.feature +++ b/tests/bdd/features/observability-all.feature @@ -6,11 +6,13 @@ Feature: Install local Helmfile observability for both planes both monitor families. Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "NVCF_CLI" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | NVCF_CLI | + | REPO_ROOT | # Helmfile pulls OCI charts during installation. Keep $NGC_API_KEY unbraced # so the BDD runner does not expand it into command logs. And command has succeeded: diff --git a/tests/bdd/features/observability-compute.feature b/tests/bdd/features/observability-compute.feature index 097687f6d..84a66e3ba 100644 --- a/tests/bdd/features/observability-compute.feature +++ b/tests/bdd/features/observability-compute.feature @@ -7,11 +7,13 @@ Feature: Install local Helmfile observability with the compute profile control-plane-only observability components. Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "NVCF_CLI" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | NVCF_CLI | + | REPO_ROOT | # Helmfile pulls OCI charts during installation. Keep $NGC_API_KEY unbraced # so the BDD runner does not expand it into command logs. And command has succeeded: diff --git a/tests/bdd/features/observability-control.feature b/tests/bdd/features/observability-control.feature index c52c19e22..78ec6baa8 100644 --- a/tests/bdd/features/observability-control.feature +++ b/tests/bdd/features/observability-control.feature @@ -5,9 +5,11 @@ Feature: Install local Helmfile observability with the control profile so that the control plane has its shared metrics infrastructure and monitors. Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | # Helmfile pulls OCI charts during installation. Keep $NGC_API_KEY unbraced # so the BDD runner does not expand it into command logs. And command has succeeded: diff --git a/tests/bdd/features/observability-disabled.feature b/tests/bdd/features/observability-disabled.feature index 029f9ae8a..aeddb1172 100644 --- a/tests/bdd/features/observability-disabled.feature +++ b/tests/bdd/features/observability-disabled.feature @@ -5,10 +5,12 @@ Feature: Render local Helmfile stacks with observability disabled so that I can verify the profile does not add observability resources. Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | REPO_ROOT | # Helmfile pulls OCI charts during rendering. Keep $NGC_API_KEY unbraced # so the BDD runner does not expand it into command logs. And command has succeeded: diff --git a/tests/bdd/features/single-cluster-eks-helmfile.feature b/tests/bdd/features/single-cluster-eks-helmfile.feature index e7a457b22..3f1b14a12 100644 --- a/tests/bdd/features/single-cluster-eks-helmfile.feature +++ b/tests/bdd/features/single-cluster-eks-helmfile.feature @@ -52,14 +52,16 @@ Feature: Install a single-cluster NVCF stack on a pre-provisioned EKS cluster wi # --compute-context ${EKS_CONTEXT} Background: - Given environment variable "REPO_ROOT" is set - And environment variable "NVCF_CLI" is set - And environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "EKS_CONTEXT" is set - And environment variable "EKS_CLUSTER_NAME" is set - And environment variable "EKS_REGION" is set + Given these environment variables are set: + | name | + | REPO_ROOT | + | NVCF_CLI | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | EKS_CONTEXT | + | EKS_CLUSTER_NAME | + | EKS_REGION | # Helmfile pulls OCI charts through helm, so host-side helm # registry auth must be present before any helmfile sync. # Keep $NGC_API_KEY unbraced so the BDD runner does not expand diff --git a/tests/bdd/features/single-cluster-helmfile-upstream-images.feature b/tests/bdd/features/single-cluster-helmfile-upstream-images.feature index 99de96866..a951622fc 100644 --- a/tests/bdd/features/single-cluster-helmfile-upstream-images.feature +++ b/tests/bdd/features/single-cluster-helmfile-upstream-images.feature @@ -7,10 +7,12 @@ Feature: Install a local single-cluster stack with upstream supporting images mirrors. Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | + | REPO_ROOT | And file "tools/ncp-local-cluster/secrets/docker-config.json" exists # Conflict precheck: ncp-local-cp claims host ports that overlap with the # single-cluster topology. Run diff --git a/tests/bdd/features/single-cluster-helmfile.feature b/tests/bdd/features/single-cluster-helmfile.feature index f4083b743..a38ea42f7 100644 --- a/tests/bdd/features/single-cluster-helmfile.feature +++ b/tests/bdd/features/single-cluster-helmfile.feature @@ -8,9 +8,11 @@ Feature: Install a local single-cluster NVCF stack with Helmfile Rule: Operator authors the local Helmfile environment file Background: - Given environment variable "NGC_API_KEY" is set - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | And I copy the file "tests/bdd/fixtures/self-managed-local-bdd.yaml" to "deploy/stacks/self-managed/environments/local-bdd.yaml" # The fixture is a copy of deploy/stacks/self-managed/environments/local.yaml, # which already carries every ncp-local local-mode override (storageClass, @@ -95,8 +97,10 @@ Feature: Install a local single-cluster NVCF stack with Helmfile Rule: Helmfile installs NVCA on the same local cluster after registration via the stack Makefile Background: - Given environment variable "NVCF_CLI" is set - And environment variable "REPO_ROOT" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | REPO_ROOT | # This rule depends on the earlier control-plane install scenario # in the same feature run. That scenario creates the cluster, # pull secrets, and Helmfile control-plane releases. The diff --git a/tests/bdd/features/single-cluster-up-oneclick.feature b/tests/bdd/features/single-cluster-up-oneclick.feature index e2462b056..55e47c34b 100644 --- a/tests/bdd/features/single-cluster-up-oneclick.feature +++ b/tests/bdd/features/single-cluster-up-oneclick.feature @@ -20,13 +20,15 @@ Feature: Bring up a local single-cluster NVCF stack with the self-hosted up one- Rule: self-hosted up installs the control plane and compute plane in one command Background: - Given environment variable "NVCF_CLI" is set - And environment variable "NGC_API_KEY" is set # SAMPLE_NGC_ORG / SAMPLE_NGC_TEAM are consumed by the # build-and-deploy-cluster credential-provider validation the # `a single-cluster ncp-local cluster is running` step runs. - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | # self-hosted up --env local reads operator-authored local secrets # from both split stacks: # deploy/stacks/self-managed/secrets/local-secrets.yaml. diff --git a/tests/bdd/features/single-cluster-up.feature b/tests/bdd/features/single-cluster-up.feature index eebab9d16..a92aed5fa 100644 --- a/tests/bdd/features/single-cluster-up.feature +++ b/tests/bdd/features/single-cluster-up.feature @@ -8,16 +8,18 @@ Feature: Bring up a local single-cluster NVCF stack with the CLI Rule: install --control-plane installs the CP; compute-plane register/install completes the worker layer Background: - Given environment variable "NVCF_CLI" is set - And environment variable "NGC_API_KEY" is set # SAMPLE_NGC_ORG / SAMPLE_NGC_TEAM are consumed by # `make build-and-deploy-cluster` (the credential provider # validation step) when the `a single-cluster ncp-local cluster is # running` step runs the build target. Without them, that target # fails at CREDENTIAL PROVIDER VALIDATION and skips the gateway # API setup. - And environment variable "SAMPLE_NGC_ORG" is set - And environment variable "SAMPLE_NGC_TEAM" is set + Given these environment variables are set: + | name | + | NVCF_CLI | + | NGC_API_KEY | + | SAMPLE_NGC_ORG | + | SAMPLE_NGC_TEAM | # self-hosted install --env local reads operator-authored local # secrets files from both split stacks: # deploy/stacks/self-managed/secrets/local-secrets.yaml (control diff --git a/tests/bdd/steps/file_steps.go b/tests/bdd/steps/file_steps.go index 7a70f45f5..a05d17b5c 100644 --- a/tests/bdd/steps/file_steps.go +++ b/tests/bdd/steps/file_steps.go @@ -38,6 +38,7 @@ func registerFileSteps(ctx *godog.ScenarioContext, sc *ScenarioContext) { ctx.Step(`^I substitute "([^"]*)" in file "([^"]*)" with base64 of "([^"]*)"$`, sc.iSubstituteBase64) ctx.Step(`^I substitute a block in file "([^"]*)":$`, sc.iSubstituteBlock) ctx.Step(`^environment variable "([^"]*)" is set$`, sc.environmentVariableIsSet) + ctx.Step(`^these environment variables are set:$`, sc.environmentVariablesAreSet) ctx.Step(`^file "([^"]*)" exists$`, sc.fileShouldExist) } @@ -98,10 +99,15 @@ func (sc *ScenarioContext) iSubstituteBlock(path string, doc *godog.DocString) e // The scenario writer uses this to surface missing prerequisites // before any later step interpolates a blank value. func (sc *ScenarioContext) environmentVariableIsSet(name string) error { - if os.Getenv(name) == "" { - return fmt.Errorf("environment variable %q is not set", name) + return dsl.RequireEnvironmentVariables([]string{name}) +} + +func (sc *ScenarioContext) environmentVariablesAreSet(table *godog.Table) error { + names, err := tableToSingleColumn(table, "name") + if err != nil { + return err } - return nil + return dsl.RequireEnvironmentVariables(names) } // fileShouldExist is shared by the bare "file ... exists" Given/Then diff --git a/tests/bdd/steps/steps_test.go b/tests/bdd/steps/steps_test.go index 9ab185e68..e77604512 100644 --- a/tests/bdd/steps/steps_test.go +++ b/tests/bdd/steps/steps_test.go @@ -157,6 +157,50 @@ func TestEnvironmentVariableIsSet(t *testing.T) { } } +func TestEnvironmentVariablesAreSet(t *testing.T) { + sc, _ := newScenarioContext(t) + t.Setenv("BDD_TMP_REQUIRED_ONE", "one") + t.Setenv("BDD_TMP_REQUIRED_TWO", "two") + table := docTable(t, [][]string{ + {"name"}, + {"BDD_TMP_REQUIRED_ONE"}, + {"BDD_TMP_REQUIRED_TWO"}, + }) + if err := sc.environmentVariablesAreSet(table); err != nil { + t.Fatalf("require variables: %v", err) + } +} + +func TestEnvironmentVariablesAreSetReportsMissingVariable(t *testing.T) { + sc, _ := newScenarioContext(t) + t.Setenv("BDD_TMP_REQUIRED_PRESENT", "present") + t.Setenv("BDD_TMP_REQUIRED_MISSING", "") + table := docTable(t, [][]string{ + {"name"}, + {"BDD_TMP_REQUIRED_PRESENT"}, + {"BDD_TMP_REQUIRED_MISSING"}, + }) + err := sc.environmentVariablesAreSet(table) + if err == nil { + t.Fatal("expected missing-variable error") + } + if !strings.Contains(err.Error(), `"BDD_TMP_REQUIRED_MISSING"`) { + t.Fatalf("error %q does not name the missing variable", err) + } +} + +func TestEnvironmentVariablesAreSetRejectsInvalidTable(t *testing.T) { + sc, _ := newScenarioContext(t) + for _, table := range []*godog.Table{ + docTable(t, [][]string{{"variable"}, {"BDD_TMP_REQUIRED"}}), + docTable(t, [][]string{{"name"}, {""}}), + } { + if err := sc.environmentVariablesAreSet(table); err == nil { + t.Fatal("expected invalid-table error") + } + } +} + func TestCommandHasSucceededCachesResolved(t *testing.T) { sc, fake := newScenarioContext(t) t.Setenv("EXAMPLE_VAR", "value") @@ -529,7 +573,9 @@ func TestRegisterAllRunsAFeatureFile(t *testing.T) { // the regex registrations and the Before hook are both exercised. feature := `Feature: Smoke Scenario: register-all smoke - Given environment variable "BDD_TMP_SMOKE" is set + Given these environment variables are set: + | name | + | BDD_TMP_SMOKE | When I successfully run command "echo smoke" And I successfully run command: """