Repository: grafana/k6-operator
Pull request #53 (2021-07-22 12:29:06)

tests: write tests for jobs package (87.8%)
Signed-off-by: Hans Knecht <Hans.Knecht@missionlane.com>

KnechtionsCoding: Works towards solving #37 At least the jobs package is reasonably covered. Not touched the controller or the containers package yet.
dgzlopes: Happy to merge once conflicts are resolved :rocket: @KnechtionsCoding 

Patch:
diff --git a/api/v1alpha1/k6_types.go b/api/v1alpha1/k6_types.go
index 9eaa78c..4363a33 100644
--- a/api/v1alpha1/k6_types.go
+++ b/api/v1alpha1/k6_types.go
@@ -37,13 +37,14 @@ type Pod struct {
 
 // K6Spec defines the desired state of K6
 type K6Spec struct {
-	Script      K6Script               `json:"script"`
-	Parallelism int32                  `json:"parallelism"`
-	Separate    bool                   `json:"separate,omitempty"`
-	Arguments   string                 `json:"arguments,omitempty"`
-	Ports       []corev1.ContainerPort `json:"ports,omitempty"`
-	Starter     Pod                    `json:"starter,omitempty"`
-	Runner      Pod                    `json:"runner,omitempty"`
+	Script             K6Script               `json:"script"`
+	Parallelism        int32                  `json:"parallelism"`
+	Separate           bool                   `json:"separate,omitempty"`
+	Arguments          string                 `json:"arguments,omitempty"`
+	Ports              []corev1.ContainerPort `json:"ports,omitempty"`
+	ServiceAccountName string                 `json:"serviceaccount,omitempty"`
+	Starter            Pod                    `json:"starter,omitempty"`
+	Runner             Pod                    `json:"runner,omitempty"`
 	//	Cleanup     Cleanup `json:"cleanup,omitempty"` // TODO
 }
 
diff --git a/config/crd/bases/k6.io_k6s.yaml b/config/crd/bases/k6.io_k6s.yaml
index 2316073..8423ea7 100644
--- a/config/crd/bases/k6.io_k6s.yaml
+++ b/config/crd/bases/k6.io_k6s.yaml
@@ -855,6 +855,8 @@ spec:
               type: object
             separate:
               type: boolean
+            serviceaccount:
+              type: string
             starter:
               properties:
                 affinity:
diff --git a/go.mod b/go.mod
index 8538c2f..4da9eee 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13
 
 require (
 	github.com/go-logr/logr v0.1.0
+	github.com/go-test/deep v1.0.7 // indirect
 	github.com/onsi/ginkgo v1.12.1
 	github.com/onsi/gomega v1.10.1
 	k8s.io/api v0.18.6
@@ -12,4 +13,4 @@ require (
 	sigs.k8s.io/controller-runtime v0.6.2
 )
 
-replace github.com/k6io/operator => ./
\ No newline at end of file
+replace github.com/k6io/operator => ./
diff --git a/go.sum b/go.sum
index d697bf9..34a63f8 100644
--- a/go.sum
+++ b/go.sum
@@ -121,6 +121,8 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
+github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=


Test patch:
diff --git a/pkg/resources/jobs/helpers_test.go b/pkg/resources/jobs/helpers_test.go
new file mode 100644
index 0000000..0bd2b3f
--- /dev/null
+++ b/pkg/resources/jobs/helpers_test.go
@@ -0,0 +1,18 @@
+package jobs
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestNewLabels(t *testing.T) {
+
+	expectedOutcome := map[string]string{
+		"app":   "k6",
+		"k6_cr": "test",
+	}
+	labels := newLabels("test")
+	if !reflect.DeepEqual(labels, expectedOutcome) {
+		t.Errorf("new labels were incorrect, got: %v, want: %v.", labels, expectedOutcome)
+	}
+}
diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go
new file mode 100644
index 0000000..9a24a29
--- /dev/null
+++ b/pkg/resources/jobs/runner_test.go
@@ -0,0 +1,329 @@
+package jobs
+
+import (
+	"errors"
+	"reflect"
+	"testing"
+
+	deep "github.com/go-test/deep"
+	"github.com/k6io/operator/api/v1alpha1"
+	batchv1 "k8s.io/api/batch/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestNewScriptVolumeClaim(t *testing.T) {
+
+	expectedOutcome := &Script{
+		Name: "Test",
+		File: "thing.js",
+		Type: "VolumeClaim",
+	}
+
+	k6 := v1alpha1.K6Spec{
+		Script: v1alpha1.K6Script{
+			VolumeClaim: v1alpha1.K6VolumeClaim{
+				Name: "Test",
+				File: "thing.js",
+			},
+		},
+	}
+
+	script, err := newScript(k6)
+	if err != nil {
+		t.Errorf("NewScript with Volume Claim errored, got: %v, want: %v", err, expectedOutcome)
+	}
+	if !reflect.DeepEqual(script, expectedOutcome) {
+		t.Errorf("NewScript with VolumeClaim failed to return expected output, got: %v, expected: %v", script, expectedOutcome)
+	}
+}
+
+func TestNewScriptConfigMap(t *testing.T) {
+
+	expectedOutcome := &Script{
+		Name: "Test",
+		File: "thing.js",
+		Type: "ConfigMap",
+	}
+
+	k6 := v1alpha1.K6Spec{
+		Script: v1alpha1.K6Script{
+			ConfigMap: v1alpha1.K6Configmap{
+				Name: "Test",
+				File: "thing.js",
+			},
+		},
+	}
+
+	script, err := newScript(k6)
+	if err != nil {
+		t.Errorf("NewScript with ConfigMap errored, got: %v, want: %v", err, expectedOutcome)
+	}
+	if !reflect.DeepEqual(script, expectedOutcome) {
+		t.Errorf("NewScript with ConfigMap failed to return expected output, got: %v, expected: %v", script, expectedOutcome)
+	}
+}
+
+func TestNewScriptNoScript(t *testing.T) {
+
+	k6 := v1alpha1.K6Spec{}
+
+	script, err := newScript(k6)
+	if err == nil && script != nil {
+		t.Errorf("Expected Error from NewScript, got: %v, want: %v", err, errors.New("configMap or VolumeClaim not provided in script definition"))
+	}
+}
+
+func TestNewVolumeSpecVolumeClaim(t *testing.T) {
+	expectedOutcome := []corev1.Volume{{
+		Name: "k6-test-volume",
+		VolumeSource: corev1.VolumeSource{
+			PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
+				ClaimName: "test",
+			},
+		},
+	}}
+
+	k6 := &Script{
+		Type: "VolumeClaim",
+		Name: "test",
+	}
+
+	volumeSpec := newVolumeSpec(k6)
+	if !reflect.DeepEqual(volumeSpec, expectedOutcome) {
+		t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome)
+	}
+}
+
+func TestNewVolumeSpecConfigMap(t *testing.T) {
+	expectedOutcome := []corev1.Volume{{
+		Name: "k6-test-volume",
+		VolumeSource: corev1.VolumeSource{
+			ConfigMap: &corev1.ConfigMapVolumeSource{
+				LocalObjectReference: corev1.LocalObjectReference{
+					Name: "test",
+				},
+			},
+		},
+	}}
+
+	k6 := &Script{
+		Type: "ConfigMap",
+		Name: "test",
+	}
+
+	volumeSpec := newVolumeSpec(k6)
+	if !reflect.DeepEqual(volumeSpec, expectedOutcome) {
+		t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome)
+	}
+}
+
+func TestNewVolumeSpecNoType(t *testing.T) {
+	expectedOutcome := []corev1.Volume{{
+		Name: "k6-test-volume",
+		VolumeSource: corev1.VolumeSource{
+			ConfigMap: &corev1.ConfigMapVolumeSource{
+				LocalObjectReference: corev1.LocalObjectReference{
+					Name: "test",
+				},
+			},
+		},
+	}}
+
+	k6 := &Script{
+		Name: "test",
+	}
+
+	volumeSpec := newVolumeSpec(k6)
+	if !reflect.DeepEqual(volumeSpec, expectedOutcome) {
+		t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome)
+	}
+
+}
+
+func TestNewAntiAffinity(t *testing.T) {
+	expectedOutcome := &corev1.Affinity{
+		PodAntiAffinity: &corev1.PodAntiAffinity{
+			RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
+				{
+					LabelSelector: &metav1.LabelSelector{
+						MatchExpressions: []metav1.LabelSelectorRequirement{
+							{
+								Key:      "app",
+								Operator: "In",
+								Values: []string{
+									"k6",
+								},
+							},
+						},
+					},
+					TopologyKey: "kubernetes.io/hostname",
+				},
+			},
+		},
+	}
+
+	antiAffinity := newAntiAffinity()
+
+	if !reflect.DeepEqual(antiAffinity, expectedOutcome) {
+		t.Errorf("AntiAffinity returning unexpected values, got: %v, expected: %v", antiAffinity, expectedOutcome)
+	}
+}
+
+func TestNewRunnerService(t *testing.T) {
+	expectedOutcome := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-service-1",
+			Namespace: "test",
+			Labels: map[string]string{
+				"app":    "k6",
+				"k6_cr":  "test",
+				"label1": "awesome",
+			},
+			Annotations: map[string]string{
+				"awesomeAnnotation": "dope",
+			}},
+		Spec: corev1.ServiceSpec{
+			Ports: []corev1.ServicePort{{
+				Name:     "http-api",
+				Port:     6565,
+				Protocol: "TCP",
+			}},
+			Selector: map[string]string{
+				"job-name": "test-1",
+			},
+		},
+	}
+
+	k6 := &v1alpha1.K6{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "test",
+		},
+		Spec: v1alpha1.K6Spec{
+			Runner: v1alpha1.Pod{
+				Metadata: v1alpha1.PodMetadata{
+					Labels: map[string]string{
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+			},
+		},
+	}
+
+	service, err := NewRunnerService(k6, 1)
+	if err != nil {
+		t.Errorf("NewRunnerService with errored, got: %v, want: %v", err, expectedOutcome)
+	}
+	if diff := deep.Equal(service, expectedOutcome); diff != nil {
+		t.Errorf("NewRunnerService returned unexpected data, diff: %s", diff)
+	}
+}
+
+func TestNewRunnerJob(t *testing.T) {
+	script := &Script{
+		Name: "test",
+		File: "thing.js",
+		Type: "ConfigMap",
+	}
+
+	var zero int64 = 0
+	automountServiceAccountToken := true
+
+	expectedOutcome := &batchv1.Job{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-1",
+			Namespace: "test",
+			Labels: map[string]string{
+				"app":    "k6",
+				"k6_cr":  "test",
+				"label1": "awesome",
+			},
+			Annotations: map[string]string{
+				"awesomeAnnotation": "dope",
+			},
+		},
+		Spec: batchv1.JobSpec{
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"app":    "k6",
+						"k6_cr":  "test",
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+				Spec: corev1.PodSpec{
+					Hostname:                     "test-1",
+					RestartPolicy:                corev1.RestartPolicyNever,
+					Affinity:                     nil,
+					NodeSelector:                 nil,
+					ServiceAccountName:           "default",
+					AutomountServiceAccountToken: &automountServiceAccountToken,
+					Containers: []corev1.Container{{
+						Image:   "loadimpact/k6:latest",
+						Name:    "k6",
+						Command: []string{"scuttle", "k6", "run", "--quiet", "/test/test.js", "--address=0.0.0.0:6565", "--paused"},
+						Env: []corev1.EnvVar{{
+							Name:  "ISTIO_QUIT_API",
+							Value: "http://127.0.0.1:15020",
+						},
+							{
+								Name:  "ENVOY_ADMIN_API",
+								Value: "http://127.0.0.1:15000",
+							}, {
+								Name:  "WAIT_FOR_ENVOY_TIMEOUT",
+								Value: "15",
+							},
+						},
+						Resources: corev1.ResourceRequirements{},
+						VolumeMounts: []corev1.VolumeMount{{
+							Name:      "k6-test-volume",
+							MountPath: "/test",
+						}},
+						Ports: []corev1.ContainerPort{{ContainerPort: 6565}},
+					}},
+					TerminationGracePeriodSeconds: &zero,
+					Volumes:                       newVolumeSpec(script),
+				},
+			},
+		},
+	}
+	k6 := &v1alpha1.K6{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "test",
+		},
+		Spec: v1alpha1.K6Spec{
+			Script: v1alpha1.K6Script{
+				ConfigMap: v1alpha1.K6Configmap{
+					Name: "test",
+					File: "test.js",
+				},
+			},
+			Runner: v1alpha1.Pod{
+				Metadata: v1alpha1.PodMetadata{
+					Labels: map[string]string{
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+			},
+		},
+	}
+
+	job, err := NewRunnerJob(k6, 1)
+	if err != nil {
+		t.Errorf("NewRunnerJob errored, got: %v", err)
+	}
+	if diff := deep.Equal(job, expectedOutcome); diff != nil {
+		t.Errorf("NewRunnerJob returned unexpected data, diff: %s", diff)
+	}
+}
diff --git a/pkg/resources/jobs/starter_test.go b/pkg/resources/jobs/starter_test.go
new file mode 100644
index 0000000..ffeb4bf
--- /dev/null
+++ b/pkg/resources/jobs/starter_test.go
@@ -0,0 +1,87 @@
+package jobs
+
+import (
+	"testing"
+
+	deep "github.com/go-test/deep"
+	"github.com/k6io/operator/api/v1alpha1"
+	"github.com/k6io/operator/pkg/resources/containers"
+	batchv1 "k8s.io/api/batch/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestNewStarterJob(t *testing.T) {
+	automountServiceAccountToken := true
+
+	expectedOutcome := &batchv1.Job{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-starter",
+			Namespace: "test",
+			Labels: map[string]string{
+				"app":    "k6",
+				"k6_cr":  "test",
+				"label1": "awesome",
+			},
+			Annotations: map[string]string{
+				"awesomeAnnotation": "dope",
+			},
+		},
+		Spec: batchv1.JobSpec{
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"app":    "k6",
+						"k6_cr":  "test",
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+				Spec: corev1.PodSpec{
+					ServiceAccountName:           "default",
+					AutomountServiceAccountToken: &automountServiceAccountToken,
+					Affinity:                     nil,
+					NodeSelector:                 nil,
+					RestartPolicy:                corev1.RestartPolicyNever,
+					Containers: []corev1.Container{
+						containers.NewCurlContainer([]string{"testing"}, "image"),
+					},
+				},
+			},
+		},
+	}
+
+	k6 := &v1alpha1.K6{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "test",
+		},
+		Spec: v1alpha1.K6Spec{
+			Script: v1alpha1.K6Script{
+				ConfigMap: v1alpha1.K6Configmap{
+					Name: "test",
+					File: "test.js",
+				},
+			},
+			Starter: v1alpha1.Pod{
+				Metadata: v1alpha1.PodMetadata{
+					Labels: map[string]string{
+						"label1": "awesome",
+					},
+					Annotations: map[string]string{
+						"awesomeAnnotation": "dope",
+					},
+				},
+				Image: "image",
+			},
+		},
+	}
+
+	job := NewStarterJob(k6, []string{"testing"})
+	if diff := deep.Equal(job, expectedOutcome); diff != nil {
+		t.Error(diff)
+	}
+
+}