Files
actions-runner-controller/controllers/actions.github.com/lazycopy_test.go
T

114 lines
3.7 KiB
Go

package actionsgithubcom
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestLazyCopyDoesNotCopyUntilMutated(t *testing.T) {
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
lazy := newLazyCopy(pod)
assert.False(t, lazy.Modified())
assert.Panics(t, func() { lazy.MergeFrom() })
}
func TestLazyCopySnapshotsBeforeTheFirstMutation(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Annotations: map[string]string{"key": "old"},
},
}
lazy := newLazyCopy(pod)
lazy.Mutate().Annotations["key"] = "new"
require.True(t, lazy.Modified())
data, err := lazy.MergeFrom().Data(pod)
require.NoError(t, err)
assert.JSONEq(t, `{"metadata":{"annotations":{"key":"new"}}}`, string(data))
}
func TestLazyCopySnapshotsOnlyOnce(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Annotations: map[string]string{"first": "old", "second": "old"},
},
}
lazy := newLazyCopy(pod)
lazy.Mutate().Annotations["first"] = "new"
// The second mutation must diff against the state before the first one,
// otherwise the earlier change is dropped from the patch.
lazy.Mutate().Annotations["second"] = "new"
data, err := lazy.MergeFrom().Data(pod)
require.NoError(t, err)
assert.JSONEq(t, `{"metadata":{"annotations":{"first":"new","second":"new"}}}`, string(data))
}
func TestLazyCopyMergeFromIsAMergePatch(t *testing.T) {
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}}
lazy := newLazyCopy(pod)
lazy.Mutate().Labels = map[string]string{"key": "value"}
assert.Equal(t, client.MergeFrom(pod).Type(), lazy.MergeFrom().Type())
}
// Pins the hazard documented on lazyCopy: the caller keeps the pointer it
// handed to newLazyCopy, so it can write through it without going via Mutate.
// A write that lands before the first Mutate is already in the snapshot, so it
// is absent from the patch and never reaches the API server. The type cannot
// prevent this, which is why the ordering is a caller invariant rather than a
// guarantee.
func TestLazyCopyDropsWritesMadeBeforeTheFirstMutate(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
Annotations: map[string]string{"smuggled": "old", "declared": "old"},
},
}
lazy := newLazyCopy(pod)
// Bypasses Mutate, so it is captured by the snapshot taken below.
pod.Annotations["smuggled"] = "new"
lazy.Mutate().Annotations["declared"] = "new"
data, err := lazy.MergeFrom().Data(pod)
require.NoError(t, err)
assert.JSONEq(t, `{"metadata":{"annotations":{"declared":"new"}}}`, string(data))
}
// The status patch in patchAppliedActionableRevisionStatus relies on being able
// to attach an optimistic lock, so that a patch computed from a stale read is
// rejected by the API server rather than silently moving the applied revision
// backwards. Without the precondition the patch can never conflict, so the
// surrounding RetryOnConflict would never fire.
func TestLazyCopyMergeFromForwardsMergeOptions(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod",
ResourceVersion: "42",
},
}
lazy := newLazyCopy(pod)
lazy.Mutate().Labels = map[string]string{"key": "value"}
data, err := lazy.MergeFrom(client.MergeFromWithOptimisticLock{}).Data(pod)
require.NoError(t, err)
assert.JSONEq(
t,
`{"metadata":{"labels":{"key":"value"},"resourceVersion":"42"}}`,
string(data),
"the lock is carried as a resourceVersion precondition in the patch body, taken from the snapshot rather than the mutated object",
)
}