diff --git a/.github/workflows/verify_unit_test.yml b/.github/workflows/verify_unit_test.yml new file mode 100644 index 0000000..396271b --- /dev/null +++ b/.github/workflows/verify_unit_test.yml @@ -0,0 +1,24 @@ +name: Verify Unit tests + +on: + pull_request: + paths: + - 'support/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: v1.19 + + + + - name: Run unit tests + run: go test ./support/. -v \ No newline at end of file diff --git a/go.mod b/go.mod index e9c5f12..d7c6549 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect @@ -43,6 +44,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect diff --git a/go.sum b/go.sum index f7abb7a..8177c66 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= diff --git a/support/batch_test.go b/support/batch_test.go new file mode 100644 index 0000000..4ede034 --- /dev/null +++ b/support/batch_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + batchv1 "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetJob(t *testing.T) { + + test := NewTest(t) + + Job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-job-1", + Namespace: "my-namespace", + }, + } + + test.client.Core().BatchV1().Jobs("my-namespace").Create(test.ctx, Job, metav1.CreateOptions{}) + + // Call the Job function using the fake client + jobs := GetJob(test, "my-namespace", "my-job-1") + + test.Expect(jobs.Name).To(gomega.Equal("my-job-1")) + test.Expect(jobs.Namespace).To(gomega.Equal("my-namespace")) + +} diff --git a/support/core_test.go b/support/core_test.go new file mode 100644 index 0000000..031d329 --- /dev/null +++ b/support/core_test.go @@ -0,0 +1,45 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetPods(t *testing.T) { + test := NewTest(t) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-namespace", + }, + } + + test.client.Core().CoreV1().Pods("test-namespace").Create(test.ctx, pod, metav1.CreateOptions{}) + + // Call the GetPods function with the fake client and namespace + pods := GetPods(test, "test-namespace", metav1.ListOptions{}) + + test.Expect(pods).Should(gomega.HaveLen(1), "Expected 1 pod, but got %d", len(pods)) + test.Expect(pods[0].Name).To(gomega.Equal("test-pod"), "Expected pod name 'test-pod', but got '%s'", pods[0].Name) +} + +func TestGetNodes(t *testing.T) { + test := NewTest(t) + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + } + + test.client.Core().CoreV1().Nodes().Create(test.ctx, node, metav1.CreateOptions{}) + nodes := GetNodes(test) + + test.Expect(nodes).Should(gomega.HaveLen(1), "Expected 1 node, but got %d", len(nodes)) + test.Expect(nodes[0].Name).To(gomega.Equal("test-node"), "Expected node name 'test-node', but got '%s'", nodes[0].Name) + +} diff --git a/support/environment_test.go b/support/environment_test.go new file mode 100644 index 0000000..c40cf72 --- /dev/null +++ b/support/environment_test.go @@ -0,0 +1,145 @@ +package support + +import ( + "fmt" + "os" + "testing" + + "github.com/onsi/gomega" +) + +func TestGetCodeFlareSDKVersion(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + // Set the environment variable. + os.Setenv(CodeFlareTestSdkVersion, "1.2.3") + + // Get the version. + version := GetCodeFlareSDKVersion() + + // Assert that the version is correct. + + g.Expect(version).To(gomega.Equal("1.2.3"), "Expected version 1.2.3, but got %s", version) + +} + +func TestGetRayVersion(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + // Set the environment variable. + os.Setenv(CodeFlareTestRayVersion, "1.4.5") + + // Get the version. + version := GetRayVersion() + + // Assert that the version is correct. + + g.Expect(version).To(gomega.Equal("1.4.5"), "Expected version 1.4.5, but got %s", version) + +} + +func TestGetRayImage(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + // Set the environment variable. + os.Setenv(CodeFlareTestRayImage, "ray/ray:latest") + + // Get the image. + image := GetRayImage() + + // Assert that the image is correct. + + g.Expect(image).To(gomega.Equal("ray/ray:latest"), "Expected image ray/ray:latest, but got %s", image) + +} + +func TestGetPyTorchImage(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + // Set the environment variable. + os.Setenv(CodeFlareTestPyTorchImage, "pytorch/pytorch:latest") + + // Get the image. + image := GetPyTorchImage() + + // Assert that the image is correct. + + g.Expect(image).To(gomega.Equal("pytorch/pytorch:latest"), "Expected image pytorch/pytorch:latest, but got %s", image) + +} + +func TestGetClusterID(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + os.Setenv(ClusterID, "my-cluster-id") + clusterId, ok := GetClusterId() + if !ok { + gomega.Expect(ok).To(gomega.BeTrue(), "Expected GetClusterId() to return true, but got false.") + } + + g.Expect(clusterId).To(gomega.Equal("my-cluster-id"), "Expected GetClusterId() to return 'my-cluster-id', but got '%s'.", clusterId) + +} + +func TestGetInstascaleOcmSecret(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + // Set the Instascale OCM secret environment variable. + os.Setenv(InstaScaleOcmSecret, "default/instascale-ocm-secret") + // Get the Instascale OCM secret namespace and secret name. + namespace, secretName := GetInstascaleOcmSecret() + + // Verify that the namespace and secret name are correct. + + g.Expect(fmt.Sprintf("%s/%s", namespace, secretName)).To( + gomega.Equal("default/instascale-ocm-secret"), + "Expected GetInstascaleOcmSecret() to return 'default/instascale-ocm-secret', but got '%s/%s'.", + namespace, secretName, + ) + +} + +func TestGetClusterType(t *testing.T) { + + g := gomega.NewGomegaWithT(t) + + tests := []struct { + name string + envVarValue string + expected ClusterType + }{ + { + name: "OSD cluster", + envVarValue: "OSD", + expected: OsdCluster, + }, + { + name: "OCP cluster", + envVarValue: "OCP", + expected: OcpCluster, + }, + { + name: "Hypershift cluster", + envVarValue: "HYPERSHIFT", + expected: HypershiftCluster, + }, + { + name: "KIND cluster", + envVarValue: "KIND", + expected: KindCluster, + }, + } + ttt := With(t) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os.Setenv(ClusterTypeEnvVar, tt.envVarValue) + actual := GetClusterType(ttt) + + g.Expect(actual).To( + gomega.Equal(tt.expected), + "Expected GetClusterType() to return %v, but got %v", tt.expected, actual, + ) + + }) + } +} diff --git a/support/events_test.go b/support/events_test.go new file mode 100644 index 0000000..b2d5bbb --- /dev/null +++ b/support/events_test.go @@ -0,0 +1,40 @@ +package support + +import ( + "testing" +) + +func TestGetDefaultEventValueIfNull(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"World", "World"}, + } + + for _, test := range tests { + actual := getDefaultEventValueIfNull(test.input) + if actual != test.expected { + t.Errorf("getDefaultEventValueIfNull(%s) = %s; expected %s", test.input, actual, test.expected) + } + } +} + +func TestGetWhitespaceStr(t *testing.T) { + tests := []struct { + size int + expected string + }{ + {0, ""}, + {1, " "}, + {5, " "}, + {10, " "}, + } + + for _, test := range tests { + actual := getWhitespaceStr(test.size) + if actual != test.expected { + t.Errorf("getWhitespaceStr(%d) = %s; expected %s", test.size, actual, test.expected) + } + } +} diff --git a/support/fakeclient.go b/support/fakeclient.go new file mode 100644 index 0000000..d8e4d1d --- /dev/null +++ b/support/fakeclient.go @@ -0,0 +1,31 @@ +package support + +import ( + "testing" + + fakeray "github.com/ray-project/kuberay/ray-operator/pkg/client/clientset/versioned/fake" + + fakeCore "k8s.io/client-go/kubernetes/fake" + + fakeimage "github.com/openshift/client-go/image/clientset/versioned/fake" + fakeMachine "github.com/openshift/client-go/machine/clientset/versioned/fake" + fakeroute "github.com/openshift/client-go/route/clientset/versioned/fake" +) + +func NewTest(t *testing.T) *T { + fakeCoreClient := fakeCore.NewSimpleClientset() + fakemachineClient := fakeMachine.NewSimpleClientset() + fakeimageClient := fakeimage.NewSimpleClientset() + fakerouteClient := fakeroute.NewSimpleClientset() + fakerayClient := fakeray.NewSimpleClientset() + + test := With(t).(*T) + test.client = &testClient{ + core: fakeCoreClient, + machine: fakemachineClient, + image: fakeimageClient, + route: fakerouteClient, + ray: fakerayClient, + } + return test +} diff --git a/support/image_test.go b/support/image_test.go new file mode 100644 index 0000000..bc94eec --- /dev/null +++ b/support/image_test.go @@ -0,0 +1,30 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + imagev1 "github.com/openshift/api/image/v1" +) + +func TestGetImageStream(t *testing.T) { + + test := NewTest(t) + + ImageStream := &imagev1.ImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-imagestream-1", + Namespace: "my-namespace", + }, + } + + test.client.Image().ImageV1().ImageStreams("my-namespace").Create(test.ctx, ImageStream, metav1.CreateOptions{}) + + image := GetImageStream(test, "my-namespace", "my-imagestream-1") + + test.Expect(image.Name).To(gomega.Equal("my-imagestream-1")) + test.Expect(image.Namespace).To(gomega.Equal("my-namespace")) +} diff --git a/support/ingress_test.go b/support/ingress_test.go new file mode 100644 index 0000000..529b104 --- /dev/null +++ b/support/ingress_test.go @@ -0,0 +1,30 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetIngress(t *testing.T) { + + test := NewTest(t) + // Create a fake client that returns Ingress objects. + Ingress := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ingress-1", + Namespace: "my-namespace", + }, + } + + test.client.Core().NetworkingV1().Ingresses("my-namespace").Create(test.ctx, Ingress, metav1.CreateOptions{}) + + // Call the Ingress function using the fake client + ingress := GetIngress(test, "my-namespace", "my-ingress-1") + + test.Expect(ingress.Name).To(gomega.Equal("my-ingress-1")) + test.Expect(ingress.Namespace).To(gomega.Equal("my-namespace")) +} diff --git a/support/machine_test.go b/support/machine_test.go new file mode 100644 index 0000000..f043020 --- /dev/null +++ b/support/machine_test.go @@ -0,0 +1,31 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + machinev1beta1 "github.com/openshift/api/machine/v1beta1" +) + +func TestGetMachineSets(t *testing.T) { + test := NewTest(t) + + machine := &machinev1beta1.MachineSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-machineset-1", + Namespace: "openshift-machine-api", + }, + } + + test.client.Machine().MachineV1beta1().MachineSets("openshift-machine-api").Create(test.ctx, machine, metav1.CreateOptions{}) + + machines, _ := GetMachineSets(test) + + test.Expect(machines).To(gomega.HaveLen(1)) + test.Expect(machines[0].Name).To(gomega.Equal("test-machineset-1")) + test.Expect(machines[0].Namespace).To(gomega.Equal("openshift-machine-api")) + +} diff --git a/support/ray_test.go b/support/ray_test.go new file mode 100644 index 0000000..4d714eb --- /dev/null +++ b/support/ray_test.go @@ -0,0 +1,46 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + rayv1alpha1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetRayJob(t *testing.T) { + + test := NewTest(t) + + RayJob := &rayv1alpha1.RayJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-job-1", + Namespace: "my-namespace", + }, + } + + test.client.Ray().RayV1().RayJobs("my-namespace").Create(test.ctx, RayJob, metav1.CreateOptions{}) + + rayJob := GetRayJob(test, "my-namespace", "my-job-1") + test.Expect(rayJob.Name).To(gomega.Equal("my-job-1")) + test.Expect(rayJob.Namespace).To(gomega.Equal("my-namespace")) +} + +func TestGetRayCluster(t *testing.T) { + + test := NewTest(t) + + RayCluster := &rayv1alpha1.RayCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster-1", + Namespace: "my-namespace", + }, + } + + test.client.Ray().RayV1().RayClusters("my-namespace").Create(test.ctx, RayCluster, metav1.CreateOptions{}) + raycluster := GetRayCluster(test, "my-namespace", "my-cluster-1") + + test.Expect(raycluster.Name).To(gomega.Equal("my-cluster-1")) + test.Expect(raycluster.Namespace).To(gomega.Equal("my-namespace")) +} diff --git a/support/route_test.go b/support/route_test.go new file mode 100644 index 0000000..4f04308 --- /dev/null +++ b/support/route_test.go @@ -0,0 +1,31 @@ +package support + +import ( + "testing" + + "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + routev1 "github.com/openshift/api/route/v1" +) + +func TestGetRoute(t *testing.T) { + + test := NewTest(t) + + route := &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-1", + Namespace: "my-namespace", + }, + } + + test.client.Route().RouteV1().Routes("my-namespace").Create(test.ctx, route, metav1.CreateOptions{}) + + routes := GetRoute(test, "my-namespace", "test-1") + + test.Expect(routes.Name).To(gomega.Equal("test-1")) + test.Expect(routes.Namespace).To(gomega.Equal("my-namespace")) + +} diff --git a/support/test.go b/support/test.go index 683b0f2..042c828 100644 --- a/support/test.go +++ b/support/test.go @@ -93,11 +93,13 @@ func (t *T) Ctx() context.Context { func (t *T) Client() Client { t.T().Helper() t.once.client.Do(func() { - c, err := newTestClient() - if err != nil { - t.T().Fatalf("Error creating client: %v", err) + if t.client == nil { + c, err := newTestClient() + if err != nil { + t.T().Fatalf("Error creating client: %v", err) + } + t.client = c } - t.client = c }) return t.client }