mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-09-30 01:31:27 +02:00
506 lines
20 KiB
Go
506 lines
20 KiB
Go
package actionsgithubcom
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
|
"github.com/stretchr/testify/assert"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/event"
|
|
)
|
|
|
|
func TestAutoscalingRunnerSetOwnedEphemeralRunnerSetPredicate(t *testing.T) {
|
|
base := func() *v1alpha1.EphemeralRunnerSet {
|
|
return &v1alpha1.EphemeralRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner-set",
|
|
Namespace: "default",
|
|
Generation: 1,
|
|
Labels: map[string]string{"app": "runner"},
|
|
Annotations: map[string]string{AnnotationKeyPatchID: "1"},
|
|
Finalizers: []string{"finalizer"},
|
|
},
|
|
Spec: v1alpha1.EphemeralRunnerSetSpec{
|
|
Replicas: 2,
|
|
PatchID: 3,
|
|
ActionableRevision: 4,
|
|
},
|
|
Status: v1alpha1.EphemeralRunnerSetStatus{
|
|
Phase: v1alpha1.EphemeralRunnerSetPhaseRunning,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Every field the AutoscalingRunnerSet reconciler reads off an owned
|
|
// EphemeralRunnerSet has to wake it up.
|
|
for name, mutate := range map[string]func(*v1alpha1.EphemeralRunnerSet){
|
|
"replicas": func(s *v1alpha1.EphemeralRunnerSet) { s.Spec.Replicas = 5 },
|
|
"patch id": func(s *v1alpha1.EphemeralRunnerSet) { s.Spec.PatchID = 9 },
|
|
"actionable revision": func(s *v1alpha1.EphemeralRunnerSet) { s.Spec.ActionableRevision = 7 },
|
|
"ephemeral runner spec": func(s *v1alpha1.EphemeralRunnerSet) {
|
|
s.Spec.EphemeralRunnerSpec.GitHubConfigURL = "https://github.com/org"
|
|
},
|
|
"ephemeral runner meta": func(s *v1alpha1.EphemeralRunnerSet) {
|
|
s.Spec.EphemeralRunnerMetadata = &v1alpha1.ResourceMeta{Labels: map[string]string{"a": "b"}}
|
|
},
|
|
"labels": func(s *v1alpha1.EphemeralRunnerSet) { s.Labels["app"] = "changed" },
|
|
"annotations": func(s *v1alpha1.EphemeralRunnerSet) { s.Annotations[AnnotationKeyPatchID] = "2" },
|
|
"finalizers": func(s *v1alpha1.EphemeralRunnerSet) { s.Finalizers = nil },
|
|
"deletion timestamp": func(s *v1alpha1.EphemeralRunnerSet) { s.DeletionTimestamp = &metav1.Time{Time: time.Now()} },
|
|
"generation": func(s *v1alpha1.EphemeralRunnerSet) { s.Generation = 2 },
|
|
"owner references": func(s *v1alpha1.EphemeralRunnerSet) { s.OwnerReferences = []metav1.OwnerReference{{Name: "owner"}} },
|
|
"phase": func(s *v1alpha1.EphemeralRunnerSet) {
|
|
s.Status.Phase = v1alpha1.EphemeralRunnerSetPhaseOutdated
|
|
},
|
|
"applied actionable revision": func(s *v1alpha1.EphemeralRunnerSet) {
|
|
s.Status.AppliedActionableRevision = 9
|
|
},
|
|
} {
|
|
t.Run("reconciles on "+name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
assert.True(t, autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
|
|
t.Run("ignores the finished runner cleanup patch id", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.Status.FinishedRunnerCleanupPatchID = 3
|
|
updated.ResourceVersion = "2"
|
|
assert.False(t, autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &corev1.Pod{},
|
|
ObjectNew: &corev1.Pod{},
|
|
}))
|
|
})
|
|
|
|
t.Run("does not filter create, delete or generic events", func(t *testing.T) {
|
|
p := autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate()
|
|
assert.True(t, p.Create(event.CreateEvent{Object: base()}))
|
|
assert.True(t, p.Delete(event.DeleteEvent{Object: base()}))
|
|
assert.True(t, p.Generic(event.GenericEvent{Object: base()}))
|
|
})
|
|
}
|
|
|
|
func TestEphemeralRunnerSetOwnedEphemeralRunnerPredicate(t *testing.T) {
|
|
base := func() *v1alpha1.EphemeralRunner {
|
|
return &v1alpha1.EphemeralRunner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Namespace: "default",
|
|
Generation: 1,
|
|
Annotations: map[string]string{
|
|
AnnotationKeyPatchID: "1",
|
|
AnnotationKeyActionableRevision: "1",
|
|
},
|
|
Finalizers: []string{"finalizer"},
|
|
},
|
|
Status: v1alpha1.EphemeralRunnerStatus{
|
|
Phase: v1alpha1.EphemeralRunnerPhaseRunning,
|
|
RunnerID: 42,
|
|
},
|
|
}
|
|
}
|
|
|
|
// The EphemeralRunnerSet reconciler groups runners by phase, reads the patch
|
|
// id and actionable revision annotations, and needs the runner id to remove
|
|
// the runner from the service.
|
|
for name, mutate := range map[string]func(*v1alpha1.EphemeralRunner){
|
|
"phase": func(r *v1alpha1.EphemeralRunner) { r.Status.Phase = v1alpha1.EphemeralRunnerPhaseSucceeded },
|
|
"runner id": func(r *v1alpha1.EphemeralRunner) { r.Status.RunnerID = 43 },
|
|
"job id": func(r *v1alpha1.EphemeralRunner) { r.Status.JobID = "job" },
|
|
"patch id annotation": func(r *v1alpha1.EphemeralRunner) { r.Annotations[AnnotationKeyPatchID] = "2" },
|
|
"actionable revision": func(r *v1alpha1.EphemeralRunner) { r.Annotations[AnnotationKeyActionableRevision] = "2" },
|
|
"labels": func(r *v1alpha1.EphemeralRunner) { r.Labels = map[string]string{"a": "b"} },
|
|
"finalizers": func(r *v1alpha1.EphemeralRunner) { r.Finalizers = nil },
|
|
"deletion timestamp": func(r *v1alpha1.EphemeralRunner) { r.DeletionTimestamp = &metav1.Time{Time: time.Now()} },
|
|
"generation": func(r *v1alpha1.EphemeralRunner) { r.Generation = 2 },
|
|
"spec": func(r *v1alpha1.EphemeralRunner) { r.Spec.GitHubConfigURL = "https://github.com/org" },
|
|
} {
|
|
t.Run("reconciles on "+name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
assert.True(t, ephemeralRunnerSetOwnedEphemeralRunnerPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
|
|
t.Run("ignores runner status the runner set never reads", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.Status.Ready = true
|
|
updated.Status.Reason = "reason"
|
|
updated.Status.Message = "message"
|
|
updated.Status.RunnerName = "runner-name"
|
|
updated.Status.Failures = map[string]metav1.Time{"pod": metav1.Now()}
|
|
updated.Status.JobRequestID = 7
|
|
updated.Status.JobDisplayName = "display"
|
|
updated.Status.JobRepositoryName = "org/repo"
|
|
updated.Status.JobWorkflowRef = "ref"
|
|
updated.Status.WorkflowRunID = 12
|
|
updated.ResourceVersion = "2"
|
|
assert.False(t, ephemeralRunnerSetOwnedEphemeralRunnerPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, ephemeralRunnerSetOwnedEphemeralRunnerPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &corev1.Pod{},
|
|
ObjectNew: &corev1.Pod{},
|
|
}))
|
|
})
|
|
}
|
|
|
|
func TestEphemeralRunnerPredicate(t *testing.T) {
|
|
base := func() *v1alpha1.EphemeralRunner {
|
|
return &v1alpha1.EphemeralRunner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Namespace: "default",
|
|
Generation: 1,
|
|
Finalizers: []string{"finalizer"},
|
|
},
|
|
Spec: v1alpha1.EphemeralRunnerSpec{GitHubConfigURL: "https://github.com/org/repo"},
|
|
Status: v1alpha1.EphemeralRunnerStatus{
|
|
Phase: v1alpha1.EphemeralRunnerPhasePending,
|
|
Ready: true,
|
|
RunnerID: 42,
|
|
},
|
|
}
|
|
}
|
|
|
|
t.Run("reconciles on listener job assignment", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.Status.JobID = "job"
|
|
|
|
assert.True(t, ephemeralRunnerPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on metadata and spec changes", func(t *testing.T) {
|
|
for name, mutate := range map[string]func(*v1alpha1.EphemeralRunner){
|
|
"finalizer": func(r *v1alpha1.EphemeralRunner) { r.Finalizers = nil },
|
|
"spec": func(r *v1alpha1.EphemeralRunner) { r.Spec.GitHubConfigURL = "https://github.com/other/repo" },
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
|
|
assert.True(t, ephemeralRunnerPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("ignores controller-owned status writes", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.Status.Phase = v1alpha1.EphemeralRunnerPhaseRunning
|
|
updated.Status.Ready = false
|
|
updated.Status.Reason = "reason"
|
|
updated.Status.Message = "message"
|
|
updated.Status.RunnerID = 43
|
|
updated.Status.RunnerName = "runner-name"
|
|
updated.Status.Failures = map[string]metav1.Time{"pod": metav1.Now()}
|
|
|
|
assert.False(t, ephemeralRunnerPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, ephemeralRunnerPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &corev1.Pod{},
|
|
ObjectNew: &corev1.Pod{},
|
|
}))
|
|
})
|
|
}
|
|
|
|
func TestEphemeralRunnerOwnedPodPredicate(t *testing.T) {
|
|
base := func() *corev1.Pod {
|
|
return &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Namespace: "default",
|
|
UID: types.UID("uid"),
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionTrue,
|
|
LastTransitionTime: metav1.NewTime(time.Now()),
|
|
},
|
|
},
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: v1alpha1.EphemeralRunnerContainerName,
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init",
|
|
State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Every pod field the EphemeralRunner reconciler branches on has to wake it up.
|
|
for name, mutate := range map[string]func(*corev1.Pod){
|
|
"phase": func(p *corev1.Pod) { p.Status.Phase = corev1.PodFailed },
|
|
"reason": func(p *corev1.Pod) { p.Status.Reason = "Evicted" },
|
|
"message": func(p *corev1.Pod) { p.Status.Message = "evicted" },
|
|
"uid": func(p *corev1.Pod) { p.UID = types.UID("other") },
|
|
"deletion timestamp": func(p *corev1.Pod) {
|
|
p.DeletionTimestamp = &metav1.Time{Time: time.Now()}
|
|
},
|
|
"runner container terminated": func(p *corev1.Pod) {
|
|
p.Status.ContainerStatuses[0].State = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 1}}
|
|
},
|
|
"runner container exit code": func(p *corev1.Pod) {
|
|
p.Status.ContainerStatuses[0].State = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 7}}
|
|
},
|
|
"runner container readiness": func(p *corev1.Pod) {
|
|
p.Status.ContainerStatuses[0].Ready = false
|
|
},
|
|
"init container status": func(p *corev1.Pod) {
|
|
p.Status.InitContainerStatuses[0].State = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 1}}
|
|
},
|
|
"ready condition": func(p *corev1.Pod) {
|
|
p.Status.Conditions[0].Status = corev1.ConditionFalse
|
|
},
|
|
} {
|
|
t.Run("reconciles on "+name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
assert.True(t, ephemeralRunnerOwnedPodPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
|
|
t.Run("ignores pod noise the runner never reads", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.ResourceVersion = "2"
|
|
updated.Labels = map[string]string{"a": "b"}
|
|
updated.Status.PodIP = "10.0.0.1"
|
|
updated.Status.PodIPs = []corev1.PodIP{{IP: "10.0.0.1"}}
|
|
updated.Status.HostIP = "10.0.0.2"
|
|
updated.Status.StartTime = &metav1.Time{Time: time.Now()}
|
|
updated.Status.Conditions = append(updated.Status.Conditions,
|
|
corev1.PodCondition{Type: corev1.PodScheduled, Status: corev1.ConditionTrue},
|
|
corev1.PodCondition{Type: corev1.ContainersReady, Status: corev1.ConditionTrue},
|
|
)
|
|
assert.False(t, ephemeralRunnerOwnedPodPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("ready condition transition time alone does not reconcile", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.Status.Conditions[0].LastTransitionTime = metav1.NewTime(time.Now().Add(time.Hour))
|
|
assert.False(t, ephemeralRunnerOwnedPodPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, ephemeralRunnerOwnedPodPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &v1alpha1.EphemeralRunner{},
|
|
ObjectNew: &v1alpha1.EphemeralRunner{},
|
|
}))
|
|
})
|
|
}
|
|
|
|
func TestAutoscalingListenerOwnedPodPredicate(t *testing.T) {
|
|
base := func() *corev1.Pod {
|
|
return &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "listener",
|
|
Namespace: "default",
|
|
Generation: 1,
|
|
Labels: map[string]string{"app": "listener"},
|
|
Annotations: map[string]string{AnnotationKeyListenerConfigResourceVersion: "1"},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
ServiceAccountName: "listener",
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: autoscalingListenerContainerName,
|
|
Image: "listener:1",
|
|
},
|
|
},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: autoscalingListenerContainerName,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Every listener pod field the AutoscalingListener reconciler branches on has
|
|
// to wake it up.
|
|
for name, mutate := range map[string]func(*corev1.Pod){
|
|
"phase": func(p *corev1.Pod) { p.Status.Phase = corev1.PodFailed },
|
|
"reason": func(p *corev1.Pod) { p.Status.Reason = "Evicted" },
|
|
"message": func(p *corev1.Pod) { p.Status.Message = "evicted" },
|
|
"labels": func(p *corev1.Pod) { p.Labels["app"] = "changed" },
|
|
"listener config resource version": func(p *corev1.Pod) {
|
|
p.Annotations[AnnotationKeyListenerConfigResourceVersion] = "2"
|
|
},
|
|
"deletion timestamp": func(p *corev1.Pod) {
|
|
p.DeletionTimestamp = &metav1.Time{Time: time.Now()}
|
|
},
|
|
"generation": func(p *corev1.Pod) { p.Generation = 2 },
|
|
"owner references": func(p *corev1.Pod) { p.OwnerReferences = []metav1.OwnerReference{{Name: "owner"}} },
|
|
"finalizers": func(p *corev1.Pod) { p.Finalizers = []string{"finalizer"} },
|
|
"image": func(p *corev1.Pod) { p.Spec.Containers[0].Image = "listener:2" },
|
|
"container ports": func(p *corev1.Pod) {
|
|
p.Spec.Containers[0].Ports = []corev1.ContainerPort{{ContainerPort: 8080}}
|
|
},
|
|
"service account name": func(p *corev1.Pod) { p.Spec.ServiceAccountName = "other" },
|
|
"listener container terminated": func(p *corev1.Pod) {
|
|
p.Status.ContainerStatuses[0].State = corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 1}}
|
|
},
|
|
} {
|
|
t.Run("reconciles on "+name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
assert.True(t, autoscalingListenerOwnedPodPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
|
|
t.Run("ignores pod noise the listener never reads", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.ResourceVersion = "2"
|
|
updated.Status.PodIP = "10.0.0.1"
|
|
updated.Status.PodIPs = []corev1.PodIP{{IP: "10.0.0.1"}}
|
|
updated.Status.HostIP = "10.0.0.2"
|
|
updated.Status.StartTime = &metav1.Time{Time: time.Now()}
|
|
updated.Status.NominatedNodeName = "node"
|
|
updated.Status.QOSClass = corev1.PodQOSBestEffort
|
|
updated.Status.Conditions = []corev1.PodCondition{
|
|
{Type: corev1.PodReady, Status: corev1.ConditionTrue},
|
|
{Type: corev1.PodScheduled, Status: corev1.ConditionTrue},
|
|
}
|
|
updated.Status.InitContainerStatuses = []corev1.ContainerStatus{
|
|
{Name: "init", State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}}},
|
|
}
|
|
assert.False(t, autoscalingListenerOwnedPodPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, autoscalingListenerOwnedPodPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &v1alpha1.AutoscalingListener{},
|
|
ObjectNew: &v1alpha1.AutoscalingListener{},
|
|
}))
|
|
})
|
|
|
|
t.Run("does not filter create, delete or generic events", func(t *testing.T) {
|
|
p := autoscalingListenerOwnedPodPredicate()
|
|
assert.True(t, p.Create(event.CreateEvent{Object: base()}))
|
|
assert.True(t, p.Delete(event.DeleteEvent{Object: base()}))
|
|
assert.True(t, p.Generic(event.GenericEvent{Object: base()}))
|
|
})
|
|
}
|
|
|
|
func TestAutoscalingListenerOwnedServiceAccountPredicate(t *testing.T) {
|
|
base := func() *corev1.ServiceAccount {
|
|
return &corev1.ServiceAccount{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "listener",
|
|
Namespace: "default",
|
|
Generation: 1,
|
|
Labels: map[string]string{"app": "listener"},
|
|
Annotations: map[string]string{"annotation": "1"},
|
|
},
|
|
}
|
|
}
|
|
|
|
// The reconciler merges labels and annotations back onto the service account,
|
|
// so a change to either has to wake it up.
|
|
for name, mutate := range map[string]func(*corev1.ServiceAccount){
|
|
"labels": func(sa *corev1.ServiceAccount) { sa.Labels["app"] = "changed" },
|
|
"annotations": func(sa *corev1.ServiceAccount) { sa.Annotations["annotation"] = "2" },
|
|
"deletion timestamp": func(sa *corev1.ServiceAccount) {
|
|
sa.DeletionTimestamp = &metav1.Time{Time: time.Now()}
|
|
},
|
|
"finalizers": func(sa *corev1.ServiceAccount) { sa.Finalizers = []string{"finalizer"} },
|
|
"owner references": func(sa *corev1.ServiceAccount) { sa.OwnerReferences = []metav1.OwnerReference{{Name: "owner"}} },
|
|
} {
|
|
t.Run("reconciles on "+name, func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
mutate(updated)
|
|
assert.True(t, autoscalingListenerOwnedServiceAccountPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
}
|
|
|
|
t.Run("ignores service account noise the listener never reads", func(t *testing.T) {
|
|
old, updated := base(), base()
|
|
updated.ResourceVersion = "2"
|
|
updated.Secrets = []corev1.ObjectReference{{Name: "listener-token"}}
|
|
updated.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "pull"}}
|
|
automount := true
|
|
updated.AutomountServiceAccountToken = &automount
|
|
assert.False(t, autoscalingListenerOwnedServiceAccountPredicate().Update(event.UpdateEvent{ObjectOld: old, ObjectNew: updated}))
|
|
})
|
|
|
|
t.Run("reconciles on unexpected types", func(t *testing.T) {
|
|
assert.True(t, autoscalingListenerOwnedServiceAccountPredicate().Update(event.UpdateEvent{
|
|
ObjectOld: &corev1.Pod{},
|
|
ObjectNew: &corev1.Pod{},
|
|
}))
|
|
})
|
|
}
|
|
|
|
// The predicates above are projections of the status fields their reconcilers
|
|
// read, so a field added to either status has to be classified: either it wakes
|
|
// the reconciler up and belongs in the tables above, or it is deliberately
|
|
// ignored. Nothing else forces that decision, so the field names are pinned
|
|
// here and adding one fails until somebody updates this list and the tables.
|
|
//
|
|
// The pin is a tripwire on the type, not a proof of correspondence: it cannot
|
|
// tell whether a reconciler started branching on a field the matching predicate
|
|
// still drops. Only the tables above assert the behaviour.
|
|
func TestPredicateProjectionsCoverEveryStatusField(t *testing.T) {
|
|
fieldNames := func(v any) []string {
|
|
typ := reflect.TypeOf(v)
|
|
names := make([]string, 0, typ.NumField())
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
names = append(names, typ.Field(i).Name)
|
|
}
|
|
sort.Strings(names)
|
|
return names
|
|
}
|
|
|
|
t.Run("ephemeral runner set status", func(t *testing.T) {
|
|
assert.Equal(t, []string{
|
|
"AppliedActionableRevision",
|
|
"FinishedRunnerCleanupPatchID",
|
|
"Phase",
|
|
}, fieldNames(v1alpha1.EphemeralRunnerSetStatus{}))
|
|
})
|
|
|
|
t.Run("ephemeral runner status", func(t *testing.T) {
|
|
assert.Equal(t, []string{
|
|
"Failures",
|
|
"JobDisplayName",
|
|
"JobID",
|
|
"JobRepositoryName",
|
|
"JobRequestID",
|
|
"JobWorkflowRef",
|
|
"Message",
|
|
"Phase",
|
|
"Ready",
|
|
"Reason",
|
|
"RunnerID",
|
|
"RunnerName",
|
|
"WorkflowRunID",
|
|
}, fieldNames(v1alpha1.EphemeralRunnerStatus{}))
|
|
})
|
|
}
|