These guidelines are derived from the existing Kueue codebase. They capture the conventions that make the code consistent, maintainable, and reusable. Follow these when writing new code or reviewing pull requests.

Kueue is a Kubernetes project and follows the upstream Kubernetes conventions. Be familiar with these core guidelines:


Product Code Guidelines

Package Organization

Controller Structure

Controllers follow the controller-runtime reconciler pattern:

Reconcile Loop

API Types (CRDs)

Webhooks

Error Handling

Client Usage

Logging

Status Conditions

Event Recording

Feature Gates

Metrics

Constants

RBAC

Concurrency

Dependencies and Testability

Code Generation

Linter


Test Code Guidelines

Test Organization

Test Frameworks

Use the right framework for the right level:

Level Framework Assertions
Unit Standard testing package go-cmp/cmp with cmpopts
Integration Ginkgo v2 Gomega
E2E Ginkgo v2 Gomega

Do not mix: unit tests should not use Gomega, and Ginkgo tests should not use cmp.Diff.

Table-Driven Unit Tests

Use map-based table-driven tests with descriptive string keys:

cases := map[string]struct {
    localQueue     *kueue.LocalQueue
    clusterQueue   *kueue.ClusterQueue
    wantLocalQueue *kueue.LocalQueue
    wantError      error
}{
    "local queue with Hold StopPolicy": {
        // setup ...
    },
    "cluster queue is inactive": {
        // setup ...
    },
}

for name, tc := range cases {
    t.Run(name, func(t *testing.T) {
        // test logic using tc
    })
}

Conventions:

Test Function Naming

Object Builders (Wrappers)

Build test objects using the fluent wrapper pattern:

wl := utiltestingapi.MakeWorkload("test-wl", "default").
    Queue("test-queue").
    Request(corev1.ResourceCPU, "4").
    SimpleReserveQuota("cq", "rf", now).
    Obj()

Key conventions:

When adding a new field or feature, extend existing wrappers rather than constructing objects by hand.

Fake Client Setup

Use the project’s client builder for consistent scheme and index setup:

cl := utiltesting.NewClientBuilder().
    WithObjects(objs...).
    WithStatusSubresource(objs...).
    WithInterceptorFuncs(interceptor.Funcs{
        SubResourcePatch: utiltesting.TreatSSAAsStrategicMerge,
    }).
    Build()

Context and Logging in Tests

Use the project helper to create a context with a test-scoped logger:

ctx, log := utiltesting.ContextWithLog(t)

This ensures log output is captured by the test framework and visible on failure.

Unit Test Assertions

Use go-cmp/cmp for comparing complex objects:

if diff := cmp.Diff(tc.wantError, gotError); diff != "" {
    t.Errorf("unexpected reconcile error (-want/+got):\n%s", diff)
}

Use predefined comparison options from test/util/constants.go:

cmpOpts := cmp.Options{
    cmpopts.EquateEmpty(),
    util.IgnoreConditionTimestamps,
    util.IgnoreObjectMetaResourceVersion,
}
if diff := cmp.Diff(want, got, cmpOpts...); diff != "" {
    t.Errorf("unexpected result (-want,+got):\n%s", diff)
}

Common options:

Integration Test Assertions (Gomega)

Use gomega.Eventually for asynchronous assertions:

gomega.Eventually(func(g gomega.Gomega) {
    g.Expect(k8sClient.Get(ctx, key, obj)).To(gomega.Succeed())
    g.Expect(obj.Status.Phase).To(gomega.Equal("Ready"))
}, util.Timeout, util.Interval).Should(gomega.Succeed())

Use custom matchers from pkg/util/testing/:

Integration Test Framework (envtest)

Set up the test suite with the shared framework:

var fwk *framework.Framework

var _ = ginkgo.BeforeSuite(func() {
    fwk = &framework.Framework{
        WebhookPath: util.WebhookPath,
    }
    cfg = fwk.Init()
    ctx, k8sClient = fwk.SetupClient(cfg)
})

var _ = ginkgo.AfterSuite(func() {
    fwk.Teardown()
})

Timeout Constants

Use the predefined constants from test/util/constants.go for timeouts and polling intervals.

Mocking and Error Injection

Use controller-runtime’s interceptor pattern to inject errors:

funcs := interceptor.Funcs{
    Get: func(ctx context.Context, client client.WithWatch, key client.ObjectKey,
        obj client.Object, opts ...client.GetOption) error {
        return errors.New("simulated error")
    },
}
cl := utiltesting.NewClientBuilder().WithInterceptorFuncs(funcs).Build()

Generated mocks live in internal/mocks/ — regenerate with make generate.

Integration/E2E Test Lifecycle

Follow this cleanup pattern:

ginkgo.AfterEach(func() {
    gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
    util.ExpectObjectToBeDeleted(ctx, k8sClient, obj, true)
})

Use ginkgo.By("description", func() { ... }) to document substeps within a test for readability in failure output.

Clock Injection in Tests

Use testingclock.NewFakeClock(time.Now()) for deterministic time control:

clock := testingclock.NewFakeClock(time.Now().Truncate(time.Second))
reconciler := NewMyReconciler(cl, WithClock(clock))

This allows tests to control time-dependent behavior without flakiness.

Running Tests

For detailed instructions on running and debugging tests, see the Testing Guide.