mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-09-30 12:13:33 +02:00
Filter owned-resource events in the workqueue (#4647)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot App
parent
9ce3169df3
commit
ff8bfdee15
@@ -0,0 +1,445 @@
|
||||
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 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{}))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user