Skip to content

Add unit tests for internal/state - #2658

Open
abrarshivani wants to merge 1 commit into
NVIDIA:mainfrom
abrarshivani:unit-test-state
Open

Add unit tests for internal/state#2658
abrarshivani wants to merge 1 commit into
NVIDIA:mainfrom
abrarshivani:unit-test-state

Conversation

@abrarshivani

@abrarshivani abrarshivani commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Description

Adds unit tests across internal/state, bringing statement coverage to 93.9%. Tests only, no production source changed.

They use the controller-runtime fake client with interceptor.Funcs to inject Get/List/Create/Update/Delete failures, a fake manager, a fake REST mapper, and the real manifest renderer, so error paths exercise real behaviour rather than restating the implementation.

The remaining uncovered statements aren't reachable from a unit test: the GetWatchSources predicate closure needs a live informer, the NewManager/newNVIDIADriverStates success returns need the hardcoded /opt/gpu-operator/manifests/state-driver path, and a handful of in-loop error branches can't fire with a valid NVIDIADriver spec.

Behaviours pinned rather than fixed

Out of scope for a tests-only change, so each is pinned by a characterization test — closing the gap later fails the test and becomes a conscious decision.

  • isDaemonSetReady skips the ObservedGeneration re-check on the nonzero-desired path, so a previous generation's status can report ready right after a spec change.
  • isDeploymentReady doesn't require Status.Replicas == desired, unlike DeploymentComplete.
  • deleteStateRelatedObjects treats a Forbidden List as nothing to clean up. state_skel.go documents this as intentional; the residual risk is list permission revoked after creation.
  • The getOSTag error branch in nodepool.go is dead — getOSTag never returns a non-nil error.

Happy to open issues for these and link them from the test comments.

Commits

  1. Add unit tests for internal/state.
  2. Apply go fix to internal/state tests — 14 ptr.To(x)new(x), 6 interface{}any. Scoped to this PR's files; go fix ./... rewrites 48 files repo-wide including this package's production sources, which belongs in its own pass.

Checklist

  • No secrets, sensitive information, or unrelated changes
  • Lint checks passing (make lint)
  • Generated assets in-sync (make validate-generated-assets)
  • Go mod artifacts in-sync (make validate-modules)
  • Test cases are added for new code paths

Testing

go test ./internal/state/... -covermode=count   # coverage: 93.9% of statements
go test ./internal/state/... -race              # ok
golangci-lint run ./internal/state/...          # 0 issues (GOOS=linux)

@copy-pr-bot

copy-pr-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@abrarshivani
abrarshivani force-pushed the unit-test-state branch 5 times, most recently from 6d26bb8 to dbc251d Compare July 23, 2026 19:38
@abrarshivani
abrarshivani force-pushed the unit-test-state branch 14 times, most recently from 7fa5f4c to 6c16861 Compare July 31, 2026 20:35
@abrarshivani
abrarshivani marked this pull request as ready for review July 31, 2026 23:22
@abrarshivani abrarshivani self-assigned this Aug 3, 2026
@rahulait

Copy link
Copy Markdown
Contributor

@abrarshivani thanks for these changes. Can we rebase it to latest main? We can then get these reviewed again and merged.

@abrarshivani

Copy link
Copy Markdown
Contributor Author

@rahulait sure.

@tariq1890

Copy link
Copy Markdown
Contributor

Can you run go fix ./... and see if there are any further changes applied?

context

@tariq1890

Copy link
Copy Markdown
Contributor

Thanks @abrarshivani for trying that out. So go fix ./... does lead to some diffs.

FYI @kvalliyurnatt The modernize lint checker does not seem to report any potential modernisation improvements.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds comprehensive unit-test coverage for the state package. The tests cover driver specification and manifest rendering, synchronization, stale DaemonSet cleanup, additional configuration generation, default image handling, skeleton reconciliation, readiness checks, deletion behavior, state-manager aggregation, watch-source deduplication, CRD validation, and InfoCatalog behavior. No production declarations change.

Merge Risk: 🟡 Moderate · up to 07b34

This test-only change does not alter runtime code, but it adds coverage that preserves behaviors which can report incomplete rollouts as ready, leave managed resources behind after permission errors, collide DaemonSet names, or apply incomplete configuration while reporting success. Merge should wait for those behaviors to be fixed or explicitly accepted, along with the remaining repository validation checks.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
internal/state/driver_sync_test.go-232-233 (1)

232-233: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the required rendered object.

require.NotEmpty passes if rendering omits the DaemonSet and returns only another manifest object. Require a DaemonSet and validate a stable behavior such as its name or driver-container image.

As per path instructions, flag assertions that still pass when the behavior under test is broken.

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Team

Run ID: 1cc048a7-4a87-4b03-a888-36cb2d7a30ee

📥 Commits

Reviewing files that changed from the base of the PR and between ae2959a and 07b3479.

📒 Files selected for processing (7)
  • internal/state/driver_cleanup_test.go
  • internal/state/driver_manifest_test.go
  • internal/state/driver_sync_test.go
  • internal/state/info_source_test.go
  • internal/state/manager_test.go
  • internal/state/state_skel_reconcile_test.go
  • internal/state/state_skel_test.go

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread internal/state/driver_manifest_test.go Outdated

// Truncation cuts inside the CR name, so the "-<osVersion>" suffix is dropped
// entirely: one over-long CR name collides across every OS.
assert.Equal(t, "nvidia-gpu-driver-"+strings.Repeat("a", 253-len("nvidia-gpu-driver-")), name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the OS suffix for long valid CR names.

A legal 253-character NVIDIADriver name also removes the OS suffix. Two node pools for that CR can then render the same DaemonSet name. One pool can replace or prevent the other pool's DaemonSet.

Reserve space for the OS suffix before truncating the CR-name portion. Test the 253-character Kubernetes name boundary and assert distinct names per OS.

ctx := log.IntoContext(context.Background(), logger)

objects, err := driverState.getManifestObjects(ctx, driverCR, driverInfoCatalog(fakeClusterInfo{}))
require.NoError(t, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return the additional-configuration error.

This test expects a missing RepoConfig to log an error and continue. Sync can then apply a DaemonSet without the requested repository configuration and suppress the retry that would repair the CR state.

Return the error from the production rendering path. Change this test to require that error.

As per path instructions, flag “an error that is logged and then returned as success.”

Source: Path instructions

skel := &stateSkel{}
ready, err := skel.isDaemonSetReady(toUnstructuredDaemonSet(t, staleDaemonSet), logr.Discard())
require.NoError(t, err)
require.True(t, ready, "known gap: stale-generation status is currently treated as ready")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject stale DaemonSet status.

Line 178 accepts Ready when ObservedGeneration is lower than Generation. This test passes while a stale DaemonSet status advances reconciliation. Expect false, and update isDaemonSetReady to reject this status.

Source: Path instructions

Spec: appsv1.DeploymentSpec{Replicas: ptr.To[int32](1)},
Status: appsv1.DeploymentStatus{Replicas: 2, UpdatedReplicas: 1, AvailableReplicas: 1},
},
expectedReady: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not accept the old Deployment replica.

Line 250 accepts Ready while an old replica remains during rollout. This test passes while isDeploymentReady reports completion before the rollout has fully drained. Expect false, and update the readiness predicate.

Source: Path instructions

Comment on lines +384 to +386
syncState, err := skel.handleStateObjectsDeletion(ctx)
require.NoError(t, err)
assert.Equal(t, SyncState(SyncStateIgnore), syncState)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Propagate the Forbidden List error.

These assertions accept Ignore after List returns Forbidden. The state-owned DaemonSet remains present, and reconciliation does not retry the cleanup. Return an error with SyncStateError, then update this test to require that result.

Source: Path instructions

@kvalliyurnatt

Copy link
Copy Markdown
Contributor

Thanks @abrarshivani for trying that out. So go fix ./... does lead to some diffs.

FYI @kvalliyurnatt The modernize lint checker does not seem to report any potential modernisation improvements.

@tariq1890 I think this PR does not have my changes yet https://github.com/abrarshivani/gpu-operator/blob/07b3479e617c24ed20c6a86c3601e2328448767b/.golangci.yml , I checked locally with a rebase it does show the modernize changes, so I think a rebase should fix it

@tariq1890

Copy link
Copy Markdown
Contributor

I think this PR does not have my changes yet https://github.com/abrarshivani/gpu-operator/blob/07b3479e617c24ed20c6a86c3601e2328448767b/.golangci.yml , I checked locally with a rebase it does show the modernize changes, so I think a rebase should fix it

Ah ok, I'd assumed that this was already rebased on top of latest main. Thanks for pointing that out!

Covers the package at 93.9% of statements. Tests only, no production
source changed.

The tests use the controller-runtime fake client with interceptor.Funcs
to inject Get/List/Create/Update/Delete failures, a fake manager, a fake
REST mapper, and the real manifest renderer, so the error paths exercise
real behaviour rather than restating the implementation.

A few blocks encode behaviour that looks questionable but is out of scope
for a tests-only change, so they are pinned by characterization tests
instead: isDaemonSetReady does not re-check ObservedGeneration on the
nonzero-desired path, isDeploymentReady does not require Status.Replicas
to equal desired, and deleteStateRelatedObjects treats a Forbidden List
as nothing to clean up on the invariant that the operator cannot have
created objects of a kind it cannot list. Each is asserted so that
closing the gap later fails the test and becomes a conscious decision.

Some paths are not reachable from a unit test and are left uncovered:
the predicate closure inside GetWatchSources only runs under a live
informer, and the NewManager and newNVIDIADriverStates success returns
need the hardcoded /opt/gpu-operator/manifests/state-driver path, which
is a string literal rather than an overridable var.

go fix is applied to these files, so they use new(x) over ptr.To(x) and
any over interface{}. It is limited to the files added here; running it
across the repo rewrites 48 files including production sources, which
belongs in its own pass.

Signed-off-by: Abrar Shivani <ashivani@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants