test(vineyard): migrate controller tests to Ginkgo/Gomega#5699
test(vineyard): migrate controller tests to Ginkgo/Gomega#5699hxrshxz wants to merge 2 commits intofluid-cloudnative:masterfrom
Conversation
Add Ginkgo v2/Gomega test suite for pkg/controllers/v1alpha1/vineyard/ covering getRuntime, GetOrCreateEngine, RemoveEngine, NewRuntimeReconciler, ControllerName, and Reconcile. All 11 specs pass with 92.3% package coverage. Also add VineyardRuntimeKind constant to api/v1alpha1/vineyardruntime_types.go, filling the same gap that exists for all other runtimes (JindoRuntimeKind, EFCRuntimeKind, ThinRuntimeKind, JuiceFSRuntimeKind). Signed-off-by: Harsh <harshmastic@gmail.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @hxrshxz. Thanks for your PR. I'm waiting for a fluid-cloudnative member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request migrates the controller tests for the Vineyard controller to Ginkgo/Gomega, providing a more structured and readable testing framework. It also adds a missing constant and increases unit test coverage for several key functions within the controller. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Pull request overview
Migrates pkg/controllers/v1alpha1/vineyard/ controller unit tests to Ginkgo v2/Gomega and adds a missing API constant to support the new tests.
Changes:
- Added Ginkgo test suite bootstrap for the vineyard controller package.
- Added Ginkgo/Gomega unit tests covering runtime retrieval, engine caching/removal, reconciler construction, controller naming, and selected reconcile paths.
- Added
VineyardRuntimeKindconstant in the Vineyard API types.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/controllers/v1alpha1/vineyard/suite_test.go | Adds Ginkgo v2 suite bootstrap for the vineyard controller tests. |
| pkg/controllers/v1alpha1/vineyard/implement_test.go | Adds Ginkgo unit tests for getRuntime, GetOrCreateEngine, and RemoveEngine. |
| pkg/controllers/v1alpha1/vineyard/controller_test.go | Adds Ginkgo unit tests for reconciler construction, controller name, and reconcile behavior. |
| api/v1alpha1/vineyardruntime_types.go | Introduces VineyardRuntimeKind constant used by migrated tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive unit tests for the VineyardRuntime controller and its associated implementation logic, covering scenarios like controller initialization, reconciliation of existing and non-existent runtimes, handling invalid runtime names, and engine lifecycle management (creation, caching, and removal). Additionally, a new constant VineyardRuntimeKind has been added to the API types.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #5699 +/- ##
=======================================
Coverage 61.22% 61.22%
=======================================
Files 444 444
Lines 30557 30557
=======================================
Hits 18710 18710
Misses 10307 10307
Partials 1540 1540 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
/gemini review |
1 similar comment
|
/gemini review |
Signed-off-by: Harsh <harshmastic@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request migrates the controller tests for vineyard to use the Ginkgo/Gomega framework, improving the structure and readability of the tests. The changes include adding new test files (controller_test.go, implement_test.go, suite_test.go) and a necessary constant VineyardRuntimeKind.
My review focuses on improving the maintainability of the new tests by reducing code duplication. I've suggested using BeforeEach blocks for common test setup, which will make the tests cleaner and more consistent. This also addresses a minor inconsistency in one of the test cases.
| var _ = Describe("VineyardRuntime Controller", func() { | ||
| Describe("NewRuntimeReconciler", func() { | ||
| It("should create a RuntimeReconciler with non-nil fields", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
| mockClient := fake.NewFakeClientWithScheme(s) | ||
| recorder := record.NewFakeRecorder(16) | ||
|
|
||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| Expect(r).NotTo(BeNil()) | ||
| Expect(r.Scheme).NotTo(BeNil()) | ||
| Expect(r.engines).NotTo(BeNil()) | ||
| Expect(r.mutex).NotTo(BeNil()) | ||
| Expect(r.RuntimeReconciler).NotTo(BeNil()) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("ControllerName", func() { | ||
| It("should return the correct controller name", func() { | ||
| s := runtime.NewScheme() | ||
| mockClient := fake.NewFakeClientWithScheme(s) | ||
| recorder := record.NewFakeRecorder(16) | ||
|
|
||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| Expect(r.ControllerName()).To(Equal(controllerName)) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("Reconcile", func() { | ||
| It("should return empty result when runtime is not found", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
| mockClient := fake.NewFakeClientWithScheme(s) | ||
| recorder := record.NewFakeRecorder(16) | ||
|
|
||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| req := ctrl.Request{ | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: "nonexistent-runtime", | ||
| Namespace: "default", | ||
| }, | ||
| } | ||
|
|
||
| result, err := r.Reconcile(context.TODO(), req) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(result).To(Equal(ctrl.Result{})) | ||
| }) | ||
|
|
||
| It("should requeue with error when runtime name violates the controller DNS-1035 validation rule", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| // Runtime name starting with a digit fails the controller's DNS-1035 validation. | ||
| invalidName := "20-vineyard" | ||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: invalidName, | ||
| Namespace: "default", | ||
| }, | ||
| } | ||
| dataset := &datav1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: invalidName, | ||
| Namespace: "default", | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime, dataset) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| req := ctrl.Request{ | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: invalidName, | ||
| Namespace: "default", | ||
| }, | ||
| } | ||
|
|
||
| _, err := r.Reconcile(context.TODO(), req) | ||
| // Invalid runtime name causes an error path | ||
| Expect(err).To(HaveOccurred()) | ||
| }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
There's repeated setup code across the test cases, such as initializing the runtime scheme. This can be consolidated into a BeforeEach block to improve code readability and maintainability.
Additionally, the test case for ControllerName is missing the call to datav1alpha1.AddToScheme(s), which is inconsistent with other tests and could lead to failures if the code under test requires the scheme to be aware of datav1alpha1 types.
Consider refactoring to use a BeforeEach block for common setup, which will also fix the inconsistency and reduce code duplication.
| var _ = Describe("VineyardRuntime Implement", func() { | ||
| const ( | ||
| runtimeName = "test-vineyard" | ||
| runtimeNamespace = "fluid" | ||
| ) | ||
|
|
||
| Describe("getRuntime", func() { | ||
| It("should return the runtime when it exists", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| } | ||
|
|
||
| got, err := r.getRuntime(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(got).NotTo(BeNil()) | ||
| Expect(got.Name).To(Equal(runtimeName)) | ||
| Expect(got.Namespace).To(Equal(runtimeNamespace)) | ||
| }) | ||
|
|
||
| It("should return error when runtime does not exist", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: "nonexistent", | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| } | ||
|
|
||
| got, err := r.getRuntime(ctx) | ||
| Expect(err).To(HaveOccurred()) | ||
| Expect(got).To(BeNil()) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("GetOrCreateEngine", func() { | ||
| It("should create a new engine when not present in cache", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
| dataset := &datav1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime, dataset) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, | ||
| EngineImpl: common.VineyardEngineImpl, | ||
| Runtime: vineyardRuntime, | ||
| } | ||
|
|
||
| engine, err := r.GetOrCreateEngine(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(engine).NotTo(BeNil()) | ||
| }) | ||
|
|
||
| It("should return cached engine on second call with same context", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
| dataset := &datav1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime, dataset) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, | ||
| EngineImpl: common.VineyardEngineImpl, | ||
| Runtime: vineyardRuntime, | ||
| } | ||
|
|
||
| engine1, err := r.GetOrCreateEngine(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| engine2, err := r.GetOrCreateEngine(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| // Both calls should return the same engine instance (cached) | ||
| Expect(engine1).To(BeIdenticalTo(engine2)) | ||
| }) | ||
|
|
||
| It("should return error for unknown engine impl", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "test", | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: "test", | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, | ||
| EngineImpl: "unknown-engine-impl", | ||
| Runtime: vineyardRuntime, | ||
| } | ||
|
|
||
| _, err := r.GetOrCreateEngine(ctx) | ||
| Expect(err).To(HaveOccurred()) | ||
| }) | ||
| }) | ||
|
|
||
| Describe("RemoveEngine", func() { | ||
| It("should remove an engine from the cache", func() { | ||
| s := runtime.NewScheme() | ||
| Expect(datav1alpha1.AddToScheme(s)).NotTo(HaveOccurred()) | ||
|
|
||
| vineyardRuntime := &datav1alpha1.VineyardRuntime{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
| dataset := &datav1alpha1.Dataset{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| } | ||
|
|
||
| mockClient := fake.NewFakeClientWithScheme(s, vineyardRuntime, dataset) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: runtimeName, | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, | ||
| EngineImpl: common.VineyardEngineImpl, | ||
| Runtime: vineyardRuntime, | ||
| } | ||
|
|
||
| // First create an engine | ||
| _, err := r.GetOrCreateEngine(ctx) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| // Remove it | ||
| r.RemoveEngine(ctx) | ||
|
|
||
| // engines map should now be empty | ||
| r.mutex.Lock() | ||
| defer r.mutex.Unlock() | ||
| Expect(r.engines).To(BeEmpty()) | ||
| }) | ||
|
|
||
| It("should be a no-op when engine is not in cache", func() { | ||
| s := runtime.NewScheme() | ||
| mockClient := fake.NewFakeClientWithScheme(s) | ||
| recorder := record.NewFakeRecorder(16) | ||
| r := NewRuntimeReconciler(mockClient, fake.NullLogger(), s, recorder) | ||
|
|
||
| ctx := cruntime.ReconcileRequestContext{ | ||
| Context: context.TODO(), | ||
| NamespacedName: types.NamespacedName{ | ||
| Name: "missing", | ||
| Namespace: runtimeNamespace, | ||
| }, | ||
| Log: fake.NullLogger(), | ||
| } | ||
|
|
||
| // Should not panic or error | ||
| Expect(func() { r.RemoveEngine(ctx) }).NotTo(Panic()) | ||
| }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
There is significant code duplication for test setup across different Describe and It blocks. For example, runtime.NewScheme(), datav1alpha1.AddToScheme(s), and record.NewFakeRecorder(16) are called in almost every test case.
To improve maintainability and reduce redundancy, you can use BeforeEach blocks at appropriate levels (Describe or Context) to handle this common setup. This will make the tests cleaner and easier to manage.
|
There was a problem hiding this comment.
Code Review
This pull request migrates the unit tests for the Vineyard controller to Ginkgo/Gomega, which is a good improvement for test structure and readability. The new tests cover the main functionalities of the reconciler and its helper methods. I've found a few critical issues in the new test code that would prevent compilation. Please see my comments for details.
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, |
There was a problem hiding this comment.
The constant common.VineyardRuntime does not appear to be defined in the pkg/common package, which will lead to a compilation error. Please use the string literal "vineyard" instead, which corresponds to the runtimeType for Vineyard.
| RuntimeType: common.VineyardRuntime, | |
| RuntimeType: "vineyard", |
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, |
There was a problem hiding this comment.
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, |
There was a problem hiding this comment.
| }, | ||
| Client: mockClient, | ||
| Log: fake.NullLogger(), | ||
| RuntimeType: common.VineyardRuntime, |
There was a problem hiding this comment.



Ⅰ. Describe what this PR does
Add Ginkgo v2/Gomega unit tests for
pkg/controllers/v1alpha1/vineyard/and add the missingVineyardRuntimeKindconstant used by the migrated tests.Ⅱ. Does this pull request fix one issue?
NONE
Ⅲ. List the added test cases (unit test/integration test) if any, please explain if no tests are needed.
suite_test.goGinkgo bootstrap forpkg/controllers/v1alpha1/vineyard/getRuntime,GetOrCreateEngine,RemoveEngine,NewRuntimeReconciler,ControllerName, andReconcileⅣ. Describe how to verify it
make fmtgo build ./api/v1alpha1/...go test -coverprofile=/tmp/fluid-vineyard-pr.cover ./pkg/controllers/v1alpha1/vineyard/... -count=1 -vgo tool cover -func=/tmp/fluid-vineyard-pr.coverⅤ. Special notes for reviews
N/A