Skip to content

Commit 01b047c

Browse files
authored
test(goosefs): migrate suite to Ginkgo v2 and add controller tests (#5687)
* test(goosefs): migrate suite_test.go to Ginkgo v2 Signed-off-by: Harsh <harshmastic@gmail.com> * test(goosefs): add Ginkgo v2 controller tests Signed-off-by: Harsh <harshmastic@gmail.com> * test(goosefs): extract envtest runtime constant Signed-off-by: Harsh <harshmastic@gmail.com> --------- Signed-off-by: Harsh <harshmastic@gmail.com>
1 parent 008d8f2 commit 01b047c

2 files changed

Lines changed: 287 additions & 15 deletions

File tree

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
Copyright 2026 The Fluid Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package goosefs
18+
19+
import (
20+
"context"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
25+
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
26+
cruntime "github.com/fluid-cloudnative/fluid/pkg/runtime"
27+
"github.com/fluid-cloudnative/fluid/pkg/utils/fake"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/client-go/tools/record"
32+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
34+
)
35+
36+
// buildTestReconcileCtx builds a minimal ReconcileRequestContext for unit tests.
37+
func buildTestReconcileCtx(req ctrl.Request) cruntime.ReconcileRequestContext {
38+
return cruntime.ReconcileRequestContext{
39+
Context: context.Background(),
40+
NamespacedName: req.NamespacedName,
41+
Log: zap.New(zap.UseDevMode(true)),
42+
}
43+
}
44+
45+
const (
46+
testRuntimeName = "test-goosefs-runtime"
47+
testRuntimeNamespace = "default"
48+
envtestRuntimeName = "envtest-runtime"
49+
)
50+
51+
var _ = Describe("RuntimeReconciler", func() {
52+
var (
53+
testScheme *runtime.Scheme
54+
)
55+
56+
BeforeEach(func() {
57+
testScheme = runtime.NewScheme()
58+
Expect(datav1alpha1.AddToScheme(testScheme)).To(Succeed())
59+
})
60+
61+
Describe("NewRuntimeReconciler", func() {
62+
It("should create a RuntimeReconciler with non-nil fields", func() {
63+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
64+
fakeRecorder := record.NewFakeRecorder(10)
65+
log := zap.New(zap.UseDevMode(true))
66+
67+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
68+
69+
Expect(reconciler).NotTo(BeNil())
70+
Expect(reconciler.Scheme).NotTo(BeNil())
71+
Expect(reconciler.RuntimeReconciler).NotTo(BeNil())
72+
Expect(reconciler.engines).NotTo(BeNil())
73+
Expect(reconciler.mutex).NotTo(BeNil())
74+
})
75+
})
76+
77+
Describe("ControllerName", func() {
78+
It("should return the expected controller name", func() {
79+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
80+
fakeRecorder := record.NewFakeRecorder(10)
81+
log := zap.New(zap.UseDevMode(true))
82+
83+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
84+
Expect(reconciler.ControllerName()).To(Equal(controllerName))
85+
})
86+
})
87+
88+
Describe("Reconcile", func() {
89+
Context("when the GooseFSRuntime does not exist", func() {
90+
It("should return empty result and no error (not-found is ignored)", func() {
91+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
92+
fakeRecorder := record.NewFakeRecorder(10)
93+
log := zap.New(zap.UseDevMode(true))
94+
95+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
96+
97+
req := ctrl.Request{
98+
NamespacedName: types.NamespacedName{
99+
Name: "nonexistent-runtime",
100+
Namespace: testRuntimeNamespace,
101+
},
102+
}
103+
result, err := reconciler.Reconcile(context.Background(), req)
104+
105+
Expect(err).NotTo(HaveOccurred())
106+
Expect(result).To(Equal(ctrl.Result{}))
107+
})
108+
})
109+
110+
Context("when a GooseFSRuntime exists", func() {
111+
It("should attempt to reconcile the runtime without crashing", func() {
112+
runtime := &datav1alpha1.GooseFSRuntime{
113+
ObjectMeta: metav1.ObjectMeta{
114+
Name: testRuntimeName,
115+
Namespace: testRuntimeNamespace,
116+
},
117+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
118+
}
119+
120+
fakeClient := fake.NewFakeClientWithScheme(testScheme, runtime)
121+
fakeRecorder := record.NewFakeRecorder(10)
122+
log := zap.New(zap.UseDevMode(true))
123+
124+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
125+
126+
req := ctrl.Request{
127+
NamespacedName: types.NamespacedName{
128+
Name: testRuntimeName,
129+
Namespace: testRuntimeNamespace,
130+
},
131+
}
132+
// Reconcile will call ReconcileInternal which requires a full engine;
133+
// the result may be an error from engine creation but must not panic.
134+
_, _ = reconciler.Reconcile(context.Background(), req)
135+
// Primary assertion: reconciler does not panic and returns
136+
})
137+
})
138+
})
139+
140+
Describe("GetOrCreateEngine", func() {
141+
It("should return an error when EngineImpl is unset (no engine created)", func() {
142+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
143+
ObjectMeta: metav1.ObjectMeta{
144+
Name: testRuntimeName,
145+
Namespace: testRuntimeNamespace,
146+
},
147+
}
148+
149+
fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
150+
fakeRecorder := record.NewFakeRecorder(10)
151+
log := zap.New(zap.UseDevMode(true))
152+
153+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
154+
Expect(reconciler.engines).To(BeEmpty())
155+
156+
ctx := cruntime.ReconcileRequestContext{
157+
NamespacedName: types.NamespacedName{
158+
Name: testRuntimeName,
159+
Namespace: testRuntimeNamespace,
160+
},
161+
Client: fakeClient,
162+
Log: log,
163+
Recorder: fakeRecorder,
164+
Runtime: gooseFSRuntime,
165+
// EngineImpl intentionally left empty — simulates unknown impl
166+
}
167+
168+
engine, err := reconciler.GetOrCreateEngine(ctx)
169+
// An empty EngineImpl causes CreateEngine to return an error; no engine stored.
170+
Expect(err).To(HaveOccurred())
171+
Expect(engine).To(BeNil())
172+
Expect(reconciler.engines).To(BeEmpty())
173+
})
174+
})
175+
176+
Describe("RemoveEngine", func() {
177+
It("should not panic when removing a non-existent engine", func() {
178+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
179+
fakeRecorder := record.NewFakeRecorder(10)
180+
log := zap.New(zap.UseDevMode(true))
181+
182+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
183+
184+
ctx := cruntime.ReconcileRequestContext{
185+
NamespacedName: types.NamespacedName{
186+
Name: testRuntimeName,
187+
Namespace: testRuntimeNamespace,
188+
},
189+
Client: fakeClient,
190+
Log: log,
191+
Recorder: fakeRecorder,
192+
}
193+
194+
// RemoveEngine on an empty map must not panic.
195+
Expect(func() {
196+
reconciler.RemoveEngine(ctx)
197+
}).NotTo(Panic())
198+
Expect(reconciler.engines).To(BeEmpty())
199+
})
200+
})
201+
202+
Describe("getRuntime", func() {
203+
It("should return error when runtime does not exist", func() {
204+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
205+
fakeRecorder := record.NewFakeRecorder(10)
206+
log := zap.New(zap.UseDevMode(true))
207+
208+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
209+
210+
req := ctrl.Request{
211+
NamespacedName: types.NamespacedName{
212+
Name: "missing",
213+
Namespace: testRuntimeNamespace,
214+
},
215+
}
216+
ctx := buildTestReconcileCtx(req)
217+
rt, err := reconciler.getRuntime(ctx)
218+
Expect(err).To(HaveOccurred())
219+
Expect(rt).To(BeNil())
220+
})
221+
222+
It("should return the runtime when it exists", func() {
223+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
224+
ObjectMeta: metav1.ObjectMeta{
225+
Name: testRuntimeName,
226+
Namespace: testRuntimeNamespace,
227+
},
228+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
229+
}
230+
231+
fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
232+
fakeRecorder := record.NewFakeRecorder(10)
233+
log := zap.New(zap.UseDevMode(true))
234+
235+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
236+
237+
req := ctrl.Request{
238+
NamespacedName: types.NamespacedName{
239+
Name: testRuntimeName,
240+
Namespace: testRuntimeNamespace,
241+
},
242+
}
243+
ctx := buildTestReconcileCtx(req)
244+
rt, err := reconciler.getRuntime(ctx)
245+
Expect(err).NotTo(HaveOccurred())
246+
Expect(rt).NotTo(BeNil())
247+
Expect(rt.Name).To(Equal(testRuntimeName))
248+
Expect(rt.Namespace).To(Equal(testRuntimeNamespace))
249+
})
250+
})
251+
252+
Describe("GooseFSRuntime CRD via envtest", func() {
253+
It("should create and retrieve a GooseFSRuntime via the k8sClient", func() {
254+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
255+
ObjectMeta: metav1.ObjectMeta{
256+
Name: envtestRuntimeName,
257+
Namespace: "default",
258+
},
259+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
260+
}
261+
262+
By("creating the GooseFSRuntime")
263+
Expect(k8sClient.Create(context.Background(), gooseFSRuntime)).To(Succeed())
264+
265+
By("retrieving the GooseFSRuntime")
266+
var fetched datav1alpha1.GooseFSRuntime
267+
Expect(k8sClient.Get(context.Background(), types.NamespacedName{
268+
Name: envtestRuntimeName,
269+
Namespace: "default",
270+
}, &fetched)).To(Succeed())
271+
Expect(fetched.Name).To(Equal(envtestRuntimeName))
272+
273+
By("deleting the GooseFSRuntime")
274+
Expect(k8sClient.Delete(context.Background(), gooseFSRuntime)).To(Succeed())
275+
})
276+
})
277+
})

pkg/controllers/v1alpha1/goosefs/suite_test.go

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Fluid Authors.
2+
Copyright 2026 The Fluid Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@ limitations under the License.
1717
package goosefs
1818

1919
import (
20-
"os"
2120
"path/filepath"
2221
"testing"
2322

@@ -40,7 +39,6 @@ import (
4039
var cfg *rest.Config
4140
var k8sClient client.Client
4241
var testEnv *envtest.Environment
43-
var useExistingCluster = false
4442

4543
func TestAPIs(t *testing.T) {
4644
RegisterFailHandler(Fail)
@@ -49,36 +47,33 @@ func TestAPIs(t *testing.T) {
4947
"Controller Suite")
5048
}
5149

52-
var _ = BeforeSuite(func(done Done) {
50+
var _ = BeforeSuite(func() {
5351
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
54-
if env := os.Getenv("USE_EXISTING_CLUSTER"); env == "true" {
55-
useExistingCluster = true
56-
}
5752

5853
By("bootstrapping test environment")
5954
testEnv = &envtest.Environment{
60-
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
55+
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
56+
ErrorIfCRDPathMissing: true,
6157
}
6258

6359
var err error
6460
cfg, err = testEnv.Start()
65-
Expect(err).ToNot(HaveOccurred())
66-
Expect(cfg).ToNot(BeNil())
61+
Expect(err).NotTo(HaveOccurred())
62+
Expect(cfg).NotTo(BeNil())
6763

6864
err = datav1alpha1.AddToScheme(scheme.Scheme)
6965
Expect(err).NotTo(HaveOccurred())
7066

7167
// +kubebuilder:scaffold:scheme
7268

7369
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
74-
Expect(err).ToNot(HaveOccurred())
75-
Expect(k8sClient).ToNot(BeNil())
70+
Expect(err).NotTo(HaveOccurred())
71+
Expect(k8sClient).NotTo(BeNil())
7672

77-
close(done)
78-
}, 60)
73+
})
7974

8075
var _ = AfterSuite(func() {
8176
By("tearing down the test environment")
8277
err := testEnv.Stop()
83-
Expect(err).ToNot(HaveOccurred())
78+
Expect(err).NotTo(HaveOccurred())
8479
})

0 commit comments

Comments
 (0)