diff --git a/api/v1alpha1/api_test_helpers_test.go b/api/v1alpha1/api_test_helpers_test.go new file mode 100644 index 00000000000..e558b0d43ca --- /dev/null +++ b/api/v1alpha1/api_test_helpers_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func apiGVKFor(object runtime.Object) (schema.GroupVersionKind, error) { + gvks, _, err := UnitTestScheme.ObjectKinds(object) + if err != nil { + return schema.GroupVersionKind{}, err + } + + Expect(gvks).NotTo(BeEmpty()) + return gvks[0], nil +} diff --git a/api/v1alpha1/common_test.go b/api/v1alpha1/common_test.go new file mode 100644 index 00000000000..65ec17f8feb --- /dev/null +++ b/api/v1alpha1/common_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("common API helpers", func() { + Describe("MetadataSyncPolicy.AutoSyncEnabled", func() { + It("defaults to true when auto sync is not configured", func() { + policy := &MetadataSyncPolicy{} + + Expect(policy.AutoSyncEnabled()).To(BeTrue()) + }) + + It("returns true when auto sync is explicitly enabled", func() { + enabled := true + policy := &MetadataSyncPolicy{AutoSync: &enabled} + + Expect(policy.AutoSyncEnabled()).To(BeTrue()) + }) + + It("returns false when auto sync is explicitly disabled", func() { + disabled := false + policy := &MetadataSyncPolicy{AutoSync: &disabled} + + Expect(policy.AutoSyncEnabled()).To(BeFalse()) + }) + }) + +}) diff --git a/api/v1alpha1/container_network_test.go b/api/v1alpha1/container_network_test.go index 256ed1faa1f..791fd7acbd3 100644 --- a/api/v1alpha1/container_network_test.go +++ b/api/v1alpha1/container_network_test.go @@ -16,31 +16,18 @@ limitations under the License. package v1alpha1 -import "testing" +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) -func TestIsHostNetwork(t *testing.T) { - testCases := map[string]struct { - n NetworkMode - want bool - }{ - "test host network case 1": { - n: HostNetworkMode, - want: true, +var _ = Describe("IsHostNetwork", func() { + DescribeTable("reports whether the network mode uses the host network", + func(mode NetworkMode, expected bool) { + Expect(IsHostNetwork(mode)).To(Equal(expected)) }, - "test host network case 2": { - n: "", - want: true, - }, - "test container network case 1": { - n: ContainerNetworkMode, - want: false, - }, - } - - for k, v := range testCases { - got := IsHostNetwork(v.n) - if v.want != got { - t.Errorf("check %s failure, got:%t,want:%t", k, got, v.want) - } - } -} + Entry("host network mode", HostNetworkMode, true), + Entry("default network mode", DefaultNetworkMode, true), + Entry("container network mode", ContainerNetworkMode, false), + ) +}) diff --git a/api/v1alpha1/databackup_types_test.go b/api/v1alpha1/databackup_types_test.go new file mode 100644 index 00000000000..294900e0334 --- /dev/null +++ b/api/v1alpha1/databackup_types_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + "github.com/fluid-cloudnative/fluid/pkg/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("DataBackup types", func() { + Describe("scheme registration", func() { + It("registers DataBackup and DataBackupList with the package group version", func() { + dataBackupGVK, err := apiGVKFor(&DataBackup{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataBackupGVK).To(Equal(GroupVersion.WithKind("DataBackup"))) + + dataBackupListGVK, err := apiGVKFor(&DataBackupList{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataBackupListGVK).To(Equal(GroupVersion.WithKind("DataBackupList"))) + }) + }) + + Describe("DeepCopyObject", func() { + It("returns a distinct runtime object for DataBackup and DataBackupList", func() { + uid := int64(1000) + gid := int64(1000) + dataBackup := &DataBackup{ + TypeMeta: metav1.TypeMeta{APIVersion: GroupVersion.String(), Kind: "DataBackup"}, + ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "fluid"}, + Spec: DataBackupSpec{ + Dataset: "imagenet", + BackupPath: "s3://bucket/checkpoints", + RunAs: &User{UID: &uid, GID: &gid}, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "prepare-backup", + }}, + }, + Status: OperationStatus{Phase: common.PhaseComplete, Duration: "30s"}, + } + + copiedObject := dataBackup.DeepCopyObject() + copiedDataBackup, ok := copiedObject.(*DataBackup) + Expect(ok).To(BeTrue()) + Expect(copiedDataBackup).NotTo(BeIdenticalTo(dataBackup)) + Expect(copiedDataBackup.Spec).To(Equal(dataBackup.Spec)) + // Verify deep copy of nested pointers. + Expect(copiedDataBackup.Spec.RunAs).NotTo(BeIdenticalTo(dataBackup.Spec.RunAs)) + Expect(copiedDataBackup.Spec.RunAfter).NotTo(BeIdenticalTo(dataBackup.Spec.RunAfter)) + Expect(copiedDataBackup.Status).To(Equal(dataBackup.Status)) + Expect(copiedDataBackup.Spec.RunAs.UID).NotTo(BeIdenticalTo(dataBackup.Spec.RunAs.UID)) + Expect(copiedDataBackup.Spec.RunAs.GID).NotTo(BeIdenticalTo(dataBackup.Spec.RunAs.GID)) + + dataBackupList := &DataBackupList{Items: []DataBackup{*dataBackup}} + copiedListObject := dataBackupList.DeepCopyObject() + copiedList, ok := copiedListObject.(*DataBackupList) + Expect(ok).To(BeTrue()) + Expect(copiedList).NotTo(BeIdenticalTo(dataBackupList)) + Expect(copiedList.Items).To(HaveLen(1)) + Expect(copiedList.Items[0].Spec.BackupPath).To(Equal("s3://bucket/checkpoints")) + }) + }) + + Describe("representative spec and status construction", func() { + It("captures backup target, workflow dependency, and backup location info", func() { + ttlSeconds := int32(120) + dataBackup := DataBackup{ + Spec: DataBackupSpec{ + Dataset: "imagenet", + BackupPath: "oss://archive/imagenet", + RunAs: &User{UserName: "fluid"}, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "freeze-dataset", + }}, + TTLSecondsAfterFinished: &ttlSeconds, + }, + Status: OperationStatus{ + Phase: common.PhaseComplete, + Duration: "3m", + Infos: map[string]string{ + "BackupLocationPath": "/archive/imagenet", + "BackupLocationNodeName": "worker-0", + }, + }, + } + + Expect(dataBackup.Spec.Dataset).To(Equal("imagenet")) + Expect(dataBackup.Spec.RunAfter).NotTo(BeNil()) + Expect(dataBackup.Spec.RunAfter.ObjectRef).To(Equal(ObjectRef{Kind: "DataLoad", Name: "freeze-dataset"})) + Expect(dataBackup.Spec.RunAs).NotTo(BeNil()) + Expect(dataBackup.Spec.RunAs.UserName).To(Equal("fluid")) + Expect(dataBackup.Status.Phase).To(Equal(common.PhaseComplete)) + Expect(dataBackup.Status.Infos).To(HaveKeyWithValue("BackupLocationNodeName", "worker-0")) + }) + }) +}) diff --git a/api/v1alpha1/dataload_types_test.go b/api/v1alpha1/dataload_types_test.go new file mode 100644 index 00000000000..8820d296be5 --- /dev/null +++ b/api/v1alpha1/dataload_types_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + "github.com/fluid-cloudnative/fluid/pkg/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("DataLoad types", func() { + Describe("scheme registration", func() { + It("registers DataLoad and DataLoadList with the package group version", func() { + dataLoadGVK, err := apiGVKFor(&DataLoad{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataLoadGVK).To(Equal(GroupVersion.WithKind("DataLoad"))) + + dataLoadListGVK, err := apiGVKFor(&DataLoadList{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataLoadListGVK).To(Equal(GroupVersion.WithKind("DataLoadList"))) + }) + }) + + Describe("DeepCopyObject", func() { + It("returns a distinct runtime object for DataLoad and DataLoadList", func() { + dataLoad := &DataLoad{ + TypeMeta: metav1.TypeMeta{APIVersion: GroupVersion.String(), Kind: "DataLoad"}, + ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "fluid"}, + Spec: DataLoadSpec{ + Dataset: TargetDataset{Name: "dataset", Namespace: "fluid"}, + Target: []TargetPath{{Path: "/training", Replicas: 2}}, + Options: map[string]string{"threads": "8"}, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "prepare", + }}, + Policy: Once, + }, + Status: OperationStatus{Phase: common.PhaseComplete, Duration: "1m"}, + } + + copiedObject := dataLoad.DeepCopyObject() + copiedDataLoad, ok := copiedObject.(*DataLoad) + Expect(ok).To(BeTrue()) + Expect(copiedDataLoad).NotTo(BeIdenticalTo(dataLoad)) + Expect(copiedDataLoad.Spec).To(Equal(dataLoad.Spec)) + // Verify deep copy of nested pointers. + Expect(copiedDataLoad.Spec.RunAfter).NotTo(BeIdenticalTo(dataLoad.Spec.RunAfter)) + dataLoad.Spec.RunAfter.Name = "changed-after-copy" + Expect(copiedDataLoad.Spec.RunAfter.ObjectRef).To(Equal(ObjectRef{Kind: "DataLoad", Name: "prepare"})) + Expect(copiedDataLoad.Status).To(Equal(dataLoad.Status)) + + dataLoadList := &DataLoadList{Items: []DataLoad{*dataLoad}} + copiedListObject := dataLoadList.DeepCopyObject() + copiedList, ok := copiedListObject.(*DataLoadList) + Expect(ok).To(BeTrue()) + Expect(copiedList).NotTo(BeIdenticalTo(dataLoadList)) + Expect(copiedList.Items).To(HaveLen(1)) + Expect(copiedList.Items[0].Spec.Dataset).To(Equal(dataLoad.Spec.Dataset)) + }) + }) + + Describe("representative spec and status construction", func() { + It("captures the dataset target, workflow dependency, and operation progress", func() { + ttlSeconds := int32(60) + dataLoad := DataLoad{ + Spec: DataLoadSpec{ + Dataset: TargetDataset{Name: "imagenet", Namespace: "fluid"}, + LoadMetadata: true, + Target: []TargetPath{{Path: "/train", Replicas: 2}}, + Options: map[string]string{"format": "csv"}, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "prepare", + }}, + TTLSecondsAfterFinished: &ttlSeconds, + Policy: Once, + }, + Status: OperationStatus{ + Phase: common.PhaseComplete, + Duration: "45s", + Infos: map[string]string{ + "cached": "true", + }, + }, + } + + Expect(dataLoad.Spec.Dataset.Name).To(Equal("imagenet")) + Expect(dataLoad.Spec.Target).To(ContainElement(TargetPath{Path: "/train", Replicas: 2})) + Expect(dataLoad.Spec.RunAfter).NotTo(BeNil()) + Expect(dataLoad.Spec.RunAfter.ObjectRef).To(Equal(ObjectRef{Kind: "DataLoad", Name: "prepare"})) + Expect(*dataLoad.Spec.TTLSecondsAfterFinished).To(Equal(ttlSeconds)) + Expect(dataLoad.Status.Phase).To(Equal(common.PhaseComplete)) + Expect(dataLoad.Status.Infos).To(HaveKeyWithValue("cached", "true")) + }) + }) +}) diff --git a/api/v1alpha1/datamigrate_types_test.go b/api/v1alpha1/datamigrate_types_test.go new file mode 100644 index 00000000000..12d2ed11c71 --- /dev/null +++ b/api/v1alpha1/datamigrate_types_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + "github.com/fluid-cloudnative/fluid/pkg/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("DataMigrate types", func() { + Describe("scheme registration", func() { + It("registers DataMigrate and DataMigrateList with the package group version", func() { + dataMigrateGVK, err := apiGVKFor(&DataMigrate{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataMigrateGVK).To(Equal(GroupVersion.WithKind("DataMigrate"))) + + dataMigrateListGVK, err := apiGVKFor(&DataMigrateList{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataMigrateListGVK).To(Equal(GroupVersion.WithKind("DataMigrateList"))) + }) + }) + + Describe("DeepCopyObject", func() { + It("returns a distinct runtime object for DataMigrate and DataMigrateList", func() { + dataMigrate := &DataMigrate{ + TypeMeta: metav1.TypeMeta{APIVersion: GroupVersion.String(), Kind: "DataMigrate"}, + ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "fluid"}, + Spec: DataMigrateSpec{ + From: DataToMigrate{DataSet: &DatasetToMigrate{Name: "source", Namespace: "fluid", Path: "/raw"}}, + To: DataToMigrate{ExternalStorage: &ExternalStorage{URI: "s3://bucket/archive"}}, + RuntimeType: "alluxio", + Options: map[string]string{"bandwidth": "high"}, + Policy: Once, + Parallelism: 4, + ParallelOptions: map[string]string{"sshPort": "22"}, + }, + Status: OperationStatus{Phase: common.PhaseComplete, Duration: "2m"}, + } + + copiedObject := dataMigrate.DeepCopyObject() + copiedDataMigrate, ok := copiedObject.(*DataMigrate) + Expect(ok).To(BeTrue()) + Expect(copiedDataMigrate).NotTo(BeIdenticalTo(dataMigrate)) + Expect(copiedDataMigrate.Spec.From).To(Equal(dataMigrate.Spec.From)) + // Verify deep copy of nested pointers. + Expect(copiedDataMigrate.Spec.From.DataSet).NotTo(BeIdenticalTo(dataMigrate.Spec.From.DataSet)) + Expect(copiedDataMigrate.Spec.To).To(Equal(dataMigrate.Spec.To)) + Expect(copiedDataMigrate.Spec.To.ExternalStorage).NotTo(BeIdenticalTo(dataMigrate.Spec.To.ExternalStorage)) + Expect(copiedDataMigrate.Status).To(Equal(dataMigrate.Status)) + + dataMigrateList := &DataMigrateList{Items: []DataMigrate{*dataMigrate}} + copiedListObject := dataMigrateList.DeepCopyObject() + copiedList, ok := copiedListObject.(*DataMigrateList) + Expect(ok).To(BeTrue()) + Expect(copiedList).NotTo(BeIdenticalTo(dataMigrateList)) + Expect(copiedList.Items).To(HaveLen(1)) + Expect(copiedList.Items[0].Spec.RuntimeType).To(Equal("alluxio")) + }) + }) + + Describe("representative spec and status construction", func() { + It("captures migration endpoints, workflow dependency, and launcher settings", func() { + ttlSeconds := int32(300) + dataMigrate := DataMigrate{ + Spec: DataMigrateSpec{ + From: DataToMigrate{DataSet: &DatasetToMigrate{Name: "source", Namespace: "fluid", Path: "/images"}}, + To: DataToMigrate{DataSet: &DatasetToMigrate{Name: "target", Namespace: "fluid", Path: "/archive"}}, + Block: true, + RuntimeType: "alluxio", + Options: map[string]string{"overwrite": "true"}, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "load-source", + }}, + TTLSecondsAfterFinished: &ttlSeconds, + Parallelism: 2, + ParallelOptions: map[string]string{"sshSecret": "migrate-ssh"}, + }, + Status: OperationStatus{ + Phase: common.PhaseComplete, + Duration: "5m", + Infos: map[string]string{"launcher": "parallel"}, + }, + } + + Expect(dataMigrate.Spec.From.DataSet).NotTo(BeNil()) + Expect(*dataMigrate.Spec.From.DataSet).To(Equal(DatasetToMigrate{Name: "source", Namespace: "fluid", Path: "/images"})) + Expect(dataMigrate.Spec.To.DataSet).NotTo(BeNil()) + Expect(*dataMigrate.Spec.To.DataSet).To(Equal(DatasetToMigrate{Name: "target", Namespace: "fluid", Path: "/archive"})) + Expect(dataMigrate.Spec.RunAfter).NotTo(BeNil()) + Expect(dataMigrate.Spec.RunAfter.ObjectRef).To(Equal(ObjectRef{Kind: "DataLoad", Name: "load-source"})) + Expect(dataMigrate.Spec.Parallelism).To(Equal(int32(2))) + Expect(dataMigrate.Status.Infos).To(HaveKeyWithValue("launcher", "parallel")) + Expect(dataMigrate.Status.Phase).To(Equal(common.PhaseComplete)) + }) + }) +}) diff --git a/api/v1alpha1/dataprocess_types_test.go b/api/v1alpha1/dataprocess_types_test.go new file mode 100644 index 00000000000..885a7535cff --- /dev/null +++ b/api/v1alpha1/dataprocess_types_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + "github.com/fluid-cloudnative/fluid/pkg/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("DataProcess types", func() { + Describe("scheme registration", func() { + It("registers DataProcess and DataProcessList with the package group version", func() { + dataProcessGVK, err := apiGVKFor(&DataProcess{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataProcessGVK).To(Equal(GroupVersion.WithKind("DataProcess"))) + + dataProcessListGVK, err := apiGVKFor(&DataProcessList{}) + Expect(err).NotTo(HaveOccurred()) + Expect(dataProcessListGVK).To(Equal(GroupVersion.WithKind("DataProcessList"))) + }) + }) + + Describe("DeepCopyObject", func() { + It("returns a distinct runtime object for DataProcess and DataProcessList", func() { + dataProcess := &DataProcess{ + TypeMeta: metav1.TypeMeta{APIVersion: GroupVersion.String(), Kind: "DataProcess"}, + ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "fluid"}, + Spec: DataProcessSpec{ + Dataset: TargetDatasetWithMountPath{ + TargetDataset: TargetDataset{Name: "dataset", Namespace: "fluid"}, + MountPath: "/data", + }, + Processor: Processor{ + Script: &ScriptProcessor{ + Source: "python /scripts/train.py", + RestartPolicy: corev1.RestartPolicyNever, + Command: []string{"/bin/sh", "-c"}, + }, + }, + }, + Status: OperationStatus{Phase: common.PhaseComplete, Duration: "90s"}, + } + + copiedObject := dataProcess.DeepCopyObject() + copiedDataProcess, ok := copiedObject.(*DataProcess) + Expect(ok).To(BeTrue()) + Expect(copiedDataProcess).NotTo(BeIdenticalTo(dataProcess)) + Expect(copiedDataProcess.Spec.Dataset).To(Equal(dataProcess.Spec.Dataset)) + // Verify deep copy of nested pointers. + Expect(copiedDataProcess.Spec.Processor.Script).NotTo(BeIdenticalTo(dataProcess.Spec.Processor.Script)) + Expect(*copiedDataProcess.Spec.Processor.Script).To(Equal(*dataProcess.Spec.Processor.Script)) + Expect(copiedDataProcess.Status).To(Equal(dataProcess.Status)) + + dataProcessList := &DataProcessList{Items: []DataProcess{*dataProcess}} + copiedListObject := dataProcessList.DeepCopyObject() + copiedList, ok := copiedListObject.(*DataProcessList) + Expect(ok).To(BeTrue()) + Expect(copiedList).NotTo(BeIdenticalTo(dataProcessList)) + Expect(copiedList.Items).To(HaveLen(1)) + Expect(copiedList.Items[0].Spec.Dataset.MountPath).To(Equal("/data")) + }) + }) + + Describe("representative spec and status construction", func() { + It("captures dataset mounting, script processor configuration, and workflow dependency", func() { + ttlSeconds := int32(600) + dataProcess := DataProcess{ + Spec: DataProcessSpec{ + Dataset: TargetDatasetWithMountPath{ + TargetDataset: TargetDataset{Name: "imagenet", Namespace: "fluid"}, + MountPath: "/workspace/dataset", + SubPath: "training", + }, + Processor: Processor{ + Script: &ScriptProcessor{ + Source: "python /workspace/train.py", + RestartPolicy: corev1.RestartPolicyOnFailure, + Command: []string{"/bin/bash", "-lc"}, + Env: []corev1.EnvVar{{Name: "EPOCHS", Value: "5"}}, + }, + }, + RunAfter: &OperationRef{ObjectRef: ObjectRef{ + Kind: "DataLoad", + Name: "load-training-data", + }}, + TTLSecondsAfterFinished: &ttlSeconds, + }, + Status: OperationStatus{ + Phase: common.PhaseComplete, + Duration: "8m", + Infos: map[string]string{"processor": "script"}, + }, + } + + Expect(dataProcess.Spec.Dataset.TargetDataset.Name).To(Equal("imagenet")) + Expect(dataProcess.Spec.Dataset.MountPath).To(Equal("/workspace/dataset")) + Expect(dataProcess.Spec.Processor.Script).NotTo(BeNil()) + Expect(dataProcess.Spec.Processor.Script.Env).To(ContainElement(corev1.EnvVar{Name: "EPOCHS", Value: "5"})) + Expect(dataProcess.Spec.RunAfter).NotTo(BeNil()) + Expect(dataProcess.Spec.RunAfter.ObjectRef).To(Equal(ObjectRef{Kind: "DataLoad", Name: "load-training-data"})) + Expect(dataProcess.Status.Infos).To(HaveKeyWithValue("processor", "script")) + }) + }) +}) diff --git a/api/v1alpha1/dataset_types_test.go b/api/v1alpha1/dataset_types_test.go index 8a567e49857..e7bcfc611d4 100644 --- a/api/v1alpha1/dataset_types_test.go +++ b/api/v1alpha1/dataset_types_test.go @@ -17,171 +17,124 @@ package v1alpha1 import ( - "testing" + "github.com/fluid-cloudnative/fluid/pkg/common" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +const ( + dataLoadOperation = "DataLoad" + dataMigrateOperation = "DataMigrate" ) -func TestDataset_RemoveDataOperationInProgress(t *testing.T) { - type fields struct { - TypeMeta v1.TypeMeta - ObjectMeta v1.ObjectMeta - Spec DatasetSpec - Status DatasetStatus - } - type args struct { - operationType string - name string - } - tests := []struct { - name string - fields fields - args args - want string - }{ - { - name: "test1", - fields: fields{ - Status: DatasetStatus{ - OperationRef: map[string]string{ - "DataLoad": "test1", - }, - }, - }, - args: args{ - operationType: "DataLoad", - name: "test1", - }, - want: "", - }, - { - name: "test2", - fields: fields{ - Status: DatasetStatus{ - OperationRef: map[string]string{ - "DataLoad": "test1,test2", - }, - }, - }, - args: args{ - operationType: "DataLoad", - name: "test1", +var _ = Describe("Dataset methods", func() { + Describe("RemoveDataOperationInProgress", func() { + DescribeTable("removes the target operation reference", + func(dataset *Dataset, operationType string, name string, expectedRemoved string, expectedCurrent string) { + Expect(dataset.RemoveDataOperationInProgress(operationType, name)).To(Equal(expectedRemoved)) + Expect(dataset.GetDataOperationInProgress(operationType)).To(Equal(expectedCurrent)) }, - want: "test2", - }, - { - name: "test3", - fields: fields{ - Status: DatasetStatus{}, - }, - args: args{ - operationType: "DataLoad", - name: "test1", + Entry("removes the only in-progress operation", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test1"}}}, + dataLoadOperation, + "test1", + "", + "", + ), + Entry("removes one operation from a list", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test1,test2"}}}, + dataLoadOperation, + "test1", + "test2", + "test2", + ), + Entry("returns empty when no operation refs are recorded", + &Dataset{Status: DatasetStatus{}}, + dataLoadOperation, + "test1", + "", + "", + ), + Entry("keeps the current refs when the target operation is not found", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test1,test2"}}}, + dataLoadOperation, + "missing", + "test1,test2", + "test1,test2", + ), + ) + }) + + Describe("SetDataOperationInProgress", func() { + DescribeTable("tracks the operation reference for the requested operation type", + func(dataset *Dataset, operationType string, name string, expected string) { + dataset.SetDataOperationInProgress(operationType, name) + + Expect(dataset.GetDataOperationInProgress(operationType)).To(Equal(expected)) }, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dataset := &Dataset{ - TypeMeta: tt.fields.TypeMeta, - ObjectMeta: tt.fields.ObjectMeta, - Spec: tt.fields.Spec, - Status: tt.fields.Status, - } - if got := dataset.RemoveDataOperationInProgress(tt.args.operationType, tt.args.name); got != tt.want { - t.Errorf("RemoveDataOperationInProgress() = %v, want %v", got, tt.want) - } + Entry("creates the first operation ref", + &Dataset{Status: DatasetStatus{}}, + dataLoadOperation, + "test1", + "test1", + ), + Entry("appends a new operation ref for the same type", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test1"}}}, + dataLoadOperation, + "test2", + "test1,test2", + ), + Entry("records a different operation type independently", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test1"}}}, + dataMigrateOperation, + "test", + "test", + ), + Entry("keeps an existing operation ref without duplication", + &Dataset{Status: DatasetStatus{OperationRef: map[string]string{dataLoadOperation: "test"}}}, + dataLoadOperation, + "test", + "test", + ), + ) + }) + + Describe("CanbeBound", func() { + It("returns true when no runtime is recorded", func() { + dataset := &Dataset{} + + Expect(dataset.CanbeBound("runtime", "fluid", common.AccelerateCategory)).To(BeTrue()) }) - } -} -func TestDataset_SetDataOperationInProgress(t *testing.T) { - type fields struct { - TypeMeta v1.TypeMeta - ObjectMeta v1.ObjectMeta - Spec DatasetSpec - Status DatasetStatus - } - type args struct { - operationType string - name string - } - tests := []struct { - name string - fields fields - args args - want string - }{ - { - name: "test1", - fields: fields{ - Status: DatasetStatus{}, - }, - args: args{ - operationType: "DataLoad", - name: "test1", - }, - want: "test1", - }, - { - name: "test2", - fields: fields{ - Status: DatasetStatus{ - OperationRef: map[string]string{ - "DataLoad": "test1", - }, - }, - }, - args: args{ - operationType: "DataLoad", - name: "test2", - }, - want: "test1,test2", - }, - { - name: "test3", - fields: fields{ - Status: DatasetStatus{ - OperationRef: map[string]string{ - "DataLoad": "test1", + DescribeTable("matches the runtime identity", + func(name, namespace string, category common.Category, expected bool) { + dataset := &Dataset{ + Status: DatasetStatus{ + Runtimes: []Runtime{ + {Name: "target", Namespace: "fluid", Category: common.AccelerateCategory}, + }, }, - }, - }, - args: args{ - operationType: "DataMigrate", - name: "test", - }, - want: "test", - }, - { - name: "test4", - fields: fields{ - Status: DatasetStatus{ - OperationRef: map[string]string{ - "DataLoad": "test", - }, - }, + } + + Expect(dataset.CanbeBound(name, namespace, category)).To(Equal(expected)) }, - args: args{ - operationType: "DataLoad", - name: "test", + Entry("matching identity", "target", "fluid", common.AccelerateCategory, true), + Entry("name mismatch", "other", "fluid", common.AccelerateCategory, false), + Entry("namespace mismatch", "target", "other", common.AccelerateCategory, false), + Entry("category mismatch", "target", "fluid", common.Category("other"), false), + ) + }) + + Describe("IsExclusiveMode", func() { + DescribeTable("reports whether the placement mode is exclusive", + func(mode PlacementMode, expected bool) { + dataset := &Dataset{Spec: DatasetSpec{PlacementMode: mode}} + + Expect(dataset.IsExclusiveMode()).To(Equal(expected)) }, - want: "test", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dataset := &Dataset{ - TypeMeta: tt.fields.TypeMeta, - ObjectMeta: tt.fields.ObjectMeta, - Spec: tt.fields.Spec, - Status: tt.fields.Status, - } - dataset.SetDataOperationInProgress(tt.args.operationType, tt.args.name) - if got := dataset.GetDataOperationInProgress(tt.args.operationType); got != tt.want { - t.Errorf("SetDataOperationInProgress() = %v, want %v", got, tt.want) - } - }) - } -} + Entry("default placement mode", DefaultMode, true), + Entry("exclusive placement mode", ExclusiveMode, true), + Entry("shared placement mode", ShareMode, false), + ) + }) +}) diff --git a/api/v1alpha1/suite_test.go b/api/v1alpha1/suite_test.go new file mode 100644 index 00000000000..b1b8238fb62 --- /dev/null +++ b/api/v1alpha1/suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2026 The Fluid Authors. + +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 v1alpha1 + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestV1alpha1(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "API V1alpha1 Suite") +}