-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bareminimum workqueue test #263
base: main
Are you sure you want to change the base?
Conversation
/ok to test |
pkg/workqueue/workqueue_test.go
Outdated
// Create a context with timeout for processing. | ||
// use DefaultTypedControllerRateLimiter Base delay: 5ms | ||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) | ||
defer cancel() | ||
|
||
// Test EnqueueRaw | ||
t.Run("EnqueueRaw", func(t *testing.T) { | ||
var called int32 | ||
callback := func(ctx context.Context, obj any) error { | ||
atomic.StoreInt32(&called, 1) | ||
return nil | ||
} | ||
wq.EnqueueRaw("AnyObject", callback) | ||
wq.processNextWorkItem(ctx) | ||
|
||
if atomic.LoadInt32(&called) != 1 { | ||
t.Error("EnqueueRaw callback was not invoked") | ||
} | ||
}) | ||
// Test Enqueue with valid and invalid runtime.Object and nill callback | ||
// TODO: Implement a proper claim spec that needs to be processed | ||
t.Run("EnqueueValid", func(t *testing.T) { | ||
var called int32 | ||
callback := func(ctx context.Context, obj any) error { | ||
_, ok := obj.(runtime.Object) | ||
if !ok { | ||
t.Errorf("Expected runtime.Object, got %T", obj) | ||
} | ||
atomic.StoreInt32(&called, 1) | ||
return nil | ||
} | ||
validObj := &runtime.Unknown{} | ||
wq.Enqueue(validObj, callback) | ||
wq.processNextWorkItem(ctx) | ||
|
||
if atomic.LoadInt32(&called) != 1 { | ||
t.Error("Enqueue callback was not invoked") | ||
} | ||
}) | ||
|
||
t.Run("EnqueueInvalid", func(t *testing.T) { | ||
callback := func(ctx context.Context, obj any) error { return nil } | ||
wq.Enqueue("NotRuntimeObject", callback) | ||
}) | ||
|
||
t.Run("NilCallback", func(t *testing.T) { | ||
validObj := &runtime.Unknown{} | ||
wq.Enqueue(validObj, nil) | ||
wq.processNextWorkItem(ctx) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Create a context with timeout for processing. | |
// use DefaultTypedControllerRateLimiter Base delay: 5ms | |
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) | |
defer cancel() | |
// Test EnqueueRaw | |
t.Run("EnqueueRaw", func(t *testing.T) { | |
var called int32 | |
callback := func(ctx context.Context, obj any) error { | |
atomic.StoreInt32(&called, 1) | |
return nil | |
} | |
wq.EnqueueRaw("AnyObject", callback) | |
wq.processNextWorkItem(ctx) | |
if atomic.LoadInt32(&called) != 1 { | |
t.Error("EnqueueRaw callback was not invoked") | |
} | |
}) | |
// Test Enqueue with valid and invalid runtime.Object and nill callback | |
// TODO: Implement a proper claim spec that needs to be processed | |
t.Run("EnqueueValid", func(t *testing.T) { | |
var called int32 | |
callback := func(ctx context.Context, obj any) error { | |
_, ok := obj.(runtime.Object) | |
if !ok { | |
t.Errorf("Expected runtime.Object, got %T", obj) | |
} | |
atomic.StoreInt32(&called, 1) | |
return nil | |
} | |
validObj := &runtime.Unknown{} | |
wq.Enqueue(validObj, callback) | |
wq.processNextWorkItem(ctx) | |
if atomic.LoadInt32(&called) != 1 { | |
t.Error("Enqueue callback was not invoked") | |
} | |
}) | |
t.Run("EnqueueInvalid", func(t *testing.T) { | |
callback := func(ctx context.Context, obj any) error { return nil } | |
wq.Enqueue("NotRuntimeObject", callback) | |
}) | |
t.Run("NilCallback", func(t *testing.T) { | |
validObj := &runtime.Unknown{} | |
wq.Enqueue(validObj, nil) | |
wq.processNextWorkItem(ctx) | |
}) | |
ctx := context.Background() | |
tests := []struct { | |
name string | |
obj any | |
callback func(context.Context, any) error | |
called *int32 | |
}{ | |
{ | |
name: "EnqueueRaw", | |
obj: "AnyObject", | |
callback: func(ctx context.Context, obj any) error { | |
atomic.StoreInt32(&called, 1) | |
return nil | |
}, | |
called: new(int32), | |
}, | |
{ | |
name: "EnqueueValid", | |
obj: &runtime.Unknown{}, | |
callback: func(ctx context.Context, obj any) error { | |
if _, ok := obj.(runtime.Object); !ok { | |
t.Errorf("Expected runtime.Object, got %T", obj) | |
} | |
atomic.StoreInt32(&called, 1) | |
return nil | |
}, | |
called: new(int32), | |
}, | |
{ | |
name: "EnqueueInvalid", | |
obj: "NotRuntimeObject", | |
callback: func(ctx context.Context, obj any) error { return nil }, | |
}, | |
{ | |
name: "NilCallback", | |
obj: &runtime.Unknown{}, | |
}, | |
} | |
for _, tt := range tests { | |
t.Run(tt.name, func(t *testing.T) { | |
wq.Enqueue(tt.obj, tt.callback) | |
wq.processNextWorkItem(ctx) | |
if tt.called != nil && atomic.LoadInt32(tt.called) != 1 { | |
t.Error("Callback was not invoked") | |
} | |
}) | |
} |
This is a more compact and readable form factor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
if atomic.LoadInt32(&called) != 1 { | ||
t.Error("Enqueue callback was not invoked") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you comment on why it's required to use atomics in the context of the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read that this is a standard go practice to use atomics to test callback functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This pull request adds a preliminary unit test for the WorkQueue package to verify that items are enqueued and processed with a default rate limiter and provide appropriate error handling in cases such as invalid types or missing callbacks.
- Added tests for different enqueue scenarios including raw data, valid objects, invalid type, and nil callbacks.
- Validates that callback execution is triggered correctly under controlled conditions.
Reviewed Changes
File | Description |
---|---|
pkg/workqueue/workqueue_test.go | Added tests for WorkQueue functionality covering various enqueuing scenarios |
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
d6dae83
to
7c8dde8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This PR introduces a preliminary unit test for the workqueue package to validate that items are enqueued and processed correctly, including edge cases such as invalid objects and nil callbacks.
- Adds table-driven tests for multiple scenarios (valid, invalid, nil callback).
- Initializes the workqueue with a default rate limiter and verifies callback invocation where applicable.
Reviewed Changes
File | Description |
---|---|
pkg/workqueue/workqueue_test.go | Adds preliminary unit tests for the pkg/workqueue module |
Copilot reviewed 1 out of 1 changed files in this pull request and generated no comments.
Comments suppressed due to low confidence (2)
pkg/workqueue/workqueue_test.go:38
- Consider increasing the timeout duration to reduce the risk of test flakiness in slower environments.
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
pkg/workqueue/workqueue_test.go:84
- [nitpick] Consider explicitly validating that no callback is invoked when a nil callback is provided to improve test robustness.
name: "NilCallback",
7c8dde8
to
60f3084
Compare
Signed-off-by: Swati Gupta <[email protected]>
60f3084
to
053866d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test list form factor
// Create a WorkQueue using the default rate limiter. | ||
defaultRateLimiter := DefaultControllerRateLimiter() | ||
wq := New(defaultRateLimiter) | ||
|
||
require.NotNil(t, wq) | ||
require.NotNil(t, wq.queue) | ||
|
||
// Create a context with timeout for processing. | ||
// use DefaultTypedControllerRateLimiter Base delay: 5ms | ||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) | ||
defer cancel() | ||
|
||
// Test EnqueueRaw | ||
t.Run("EnqueueRaw", func(t *testing.T) { | ||
var called int32 | ||
callback := func(ctx context.Context, obj any) error { | ||
atomic.StoreInt32(&called, 1) | ||
return nil | ||
} | ||
wq.EnqueueRaw("AnyObject", callback) | ||
wq.processNextWorkItem(ctx) | ||
|
||
if atomic.LoadInt32(&called) != 1 { | ||
t.Error("EnqueueRaw callback was not invoked") | ||
} | ||
}) | ||
// Test Enqueue with valid and invalid runtime.Object and nil callback | ||
// TODO: Implement a proper claim spec that needs to be processed | ||
t.Run("EnqueueValid", func(t *testing.T) { | ||
var called int32 | ||
callback := func(ctx context.Context, obj any) error { | ||
_, ok := obj.(runtime.Object) | ||
if !ok { | ||
t.Errorf("Expected runtime.Object, got %T", obj) | ||
} | ||
atomic.StoreInt32(&called, 1) | ||
return nil | ||
} | ||
validObj := &runtime.Unknown{} | ||
wq.Enqueue(validObj, callback) | ||
wq.processNextWorkItem(ctx) | ||
|
||
if atomic.LoadInt32(&called) != 1 { | ||
t.Error("Enqueue callback was not invoked") | ||
} | ||
}) | ||
|
||
t.Run("EnqueueInvalid", func(t *testing.T) { | ||
callback := func(ctx context.Context, obj any) error { return nil } | ||
wq.Enqueue("NotRuntimeObject", callback) | ||
}) | ||
|
||
t.Run("NilCallback", func(t *testing.T) { | ||
validObj := &runtime.Unknown{} | ||
wq.Enqueue(validObj, nil) | ||
wq.processNextWorkItem(ctx) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Create a WorkQueue using the default rate limiter. | |
defaultRateLimiter := DefaultControllerRateLimiter() | |
wq := New(defaultRateLimiter) | |
require.NotNil(t, wq) | |
require.NotNil(t, wq.queue) | |
// Create a context with timeout for processing. | |
// use DefaultTypedControllerRateLimiter Base delay: 5ms | |
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) | |
defer cancel() | |
// Test EnqueueRaw | |
t.Run("EnqueueRaw", func(t *testing.T) { | |
var called int32 | |
callback := func(ctx context.Context, obj any) error { | |
atomic.StoreInt32(&called, 1) | |
return nil | |
} | |
wq.EnqueueRaw("AnyObject", callback) | |
wq.processNextWorkItem(ctx) | |
if atomic.LoadInt32(&called) != 1 { | |
t.Error("EnqueueRaw callback was not invoked") | |
} | |
}) | |
// Test Enqueue with valid and invalid runtime.Object and nil callback | |
// TODO: Implement a proper claim spec that needs to be processed | |
t.Run("EnqueueValid", func(t *testing.T) { | |
var called int32 | |
callback := func(ctx context.Context, obj any) error { | |
_, ok := obj.(runtime.Object) | |
if !ok { | |
t.Errorf("Expected runtime.Object, got %T", obj) | |
} | |
atomic.StoreInt32(&called, 1) | |
return nil | |
} | |
validObj := &runtime.Unknown{} | |
wq.Enqueue(validObj, callback) | |
wq.processNextWorkItem(ctx) | |
if atomic.LoadInt32(&called) != 1 { | |
t.Error("Enqueue callback was not invoked") | |
} | |
}) | |
t.Run("EnqueueInvalid", func(t *testing.T) { | |
callback := func(ctx context.Context, obj any) error { return nil } | |
wq.Enqueue("NotRuntimeObject", callback) | |
}) | |
t.Run("NilCallback", func(t *testing.T) { | |
validObj := &runtime.Unknown{} | |
wq.Enqueue(validObj, nil) | |
wq.processNextWorkItem(ctx) | |
}) | |
wq := New(DefaultControllerRateLimiter()) | |
require.NotNil(t, wq) | |
require.NotNil(t, wq.queue) | |
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) | |
defer cancel() | |
tests := []struct { | |
name string | |
obj any | |
callback func(context.Context, any) error | |
validate func(*testing.T, *int32) | |
processNextWorkItem bool | |
}{ | |
{ | |
name: "EnqueueRaw", | |
obj: "AnyObject", | |
callback: func(ctx context.Context, obj any) error { | |
atomic.StoreInt32(obj.(*int32), 1) | |
return nil | |
}, | |
validate: func(t *testing.T, called *int32) { | |
if atomic.LoadInt32(called) != 1 { | |
t.Error("EnqueueRaw callback was not invoked") | |
} | |
}, | |
processNextWorkItem: true, | |
}, | |
{ | |
name: "EnqueueValid", | |
obj: &runtime.Unknown{}, | |
callback: func(ctx context.Context, obj any) error { | |
if _, ok := obj.(runtime.Object); !ok { | |
t.Errorf("Expected runtime.Object, got %T", obj) | |
} | |
atomic.StoreInt32(new(int32), 1) | |
return nil | |
}, | |
validate: func(t *testing.T, called *int32) { | |
// No validation needed for runtime.Object type | |
}, | |
processNextWorkItem: true, | |
}, | |
{ | |
name: "EnqueueInvalid", | |
obj: "NotRuntimeObject", | |
callback: func(ctx context.Context, obj any) error { return nil }, | |
validate: nil, | |
processNextWorkItem: false, | |
}, | |
{ | |
name: "NilCallback", | |
obj: &runtime.Unknown{}, | |
callback: nil, | |
validate: nil, | |
processNextWorkItem: true, | |
}, | |
} | |
for _, tt := range tests { | |
t.Run(tt.name, func(t *testing.T) { | |
var called int32 | |
if tt.name == "EnqueueRaw" { | |
wq.EnqueueRaw(&called, tt.callback) | |
} else { | |
wq.Enqueue(tt.obj, tt.callback) | |
} | |
if tt.processNextWorkItem { | |
wq.processNextWorkItem(ctx) | |
if tt.validate != nil { | |
tt.validate(t, &called) | |
} | |
} | |
}) | |
} |
I have tested this suggestion it works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it may be a bit of an overkill to add the extra validate func and bool just to make the test compact. If its not strongly desired, I would like to stick to the current format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ArangoGutierrez how do we move forward with this? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ArangoGutierrez how do we move forward with this? :)
This adds a preliminary unit test for
pkg/workqueue
which initialize the WorkQueue with a default Rate-limiter for handling event failures and retries asynchronously. The tests make sure the items added are valid and processed.Heres the Test results