ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c

View file

@ -0,0 +1,367 @@
package main
import (
"context"
"errors"
"reflect"
"slices"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
)
type admitClient struct {
dynamic.Interface
before func(*unstructured.Unstructured)
afterList func()
}
type admitScope struct {
dynamic.NamespaceableResourceInterface
before func(*unstructured.Unstructured)
afterList func()
}
type admitResource struct {
dynamic.ResourceInterface
before func(*unstructured.Unstructured)
afterList func()
}
func (c admitClient) Resource(gvr schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
resource := c.Interface.Resource(gvr)
if gvr == workflowGVR {
return admitScope{NamespaceableResourceInterface: resource, before: c.before, afterList: c.afterList}
}
return resource
}
func (r admitScope) Namespace(namespace string) dynamic.ResourceInterface {
return admitResource{ResourceInterface: r.NamespaceableResourceInterface.Namespace(namespace), before: r.before, afterList: r.afterList}
}
func (r admitResource) Create(ctx context.Context, object *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
if r.before != nil {
r.before(object)
}
return r.ResourceInterface.Create(ctx, object, options, subresources...)
}
func TestLateAdmission(t *testing.T) {
ctx := context.Background()
instance, template := bundleFixture(t)
peer := *instance
previous, err := peer.readyTemplate(ctx, "delivery", template.GetName())
if err != nil {
t.Fatal(err)
}
old, err := peer.client.Resource(templateGVR).Namespace("delivery").Get(ctx, previous, metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
published := ""
instance.client = admitClient{Interface: peer.client, before: func(_ *unstructured.Unstructured) {
changeImage(t, &peer, template.GetName(), "example.invalid/builder:v2")
published, err = peer.readyTemplate(ctx, "delivery", template.GetName())
if err != nil {
t.Fatal(err)
}
}}
delivery, err := decodeProjectDelivery(continuousDelivery())
if err != nil {
t.Fatal(err)
}
if err := instance.ensureWorkflow(ctx, &delivery, "late", template.GetName(), "build", nil, nil); err != nil {
t.Fatal(err)
}
captured := boundTemplate(t, instance, "late")
if !reflect.DeepEqual(old.Object["spec"], captured.Object["spec"]) {
t.Fatal("captured content changed")
}
if captured.GetName() == published {
t.Fatal("the schedule did not overlap two published revisions")
}
}
func (r admitResource) List(ctx context.Context, options metav1.ListOptions) (*unstructured.UnstructuredList, error) {
items, err := r.ResourceInterface.List(ctx, options)
if err == nil && r.afterList != nil {
r.afterList()
}
return items, err
}
func TestPublishOverlap(t *testing.T) {
ctx := context.Background()
instance, template := bundleFixture(t)
peer := *instance
base := peer.client
previous, err := peer.readyTemplate(ctx, "delivery", template.GetName())
if err != nil {
t.Fatal(err)
}
published := ""
instance.client = admitClient{Interface: base, before: func(workflow *unstructured.Unstructured) {
changeImage(t, &peer, template.GetName(), "example.invalid/builder:v2")
peer.client = admitClient{Interface: base, afterList: func() {
if _, err := base.Resource(workflowGVR).Namespace("delivery").Create(ctx, workflow.DeepCopy(), metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
}}
published, err = peer.readyTemplate(ctx, "delivery", template.GetName())
if err != nil {
t.Fatal(err)
}
}}
delivery, err := decodeProjectDelivery(continuousDelivery())
if err != nil {
t.Fatal(err)
}
if err := instance.ensureWorkflow(ctx, &delivery, "late", template.GetName(), "build", nil, nil); err != nil {
t.Fatal(err)
}
captured := boundTemplate(t, instance, "late")
if captured.GetName() != previous {
t.Fatal("execution lost its captured revision")
}
if published == previous {
t.Fatal("the schedule did not publish during an in-flight admission")
}
}
func TestQuietSameBundle(t *testing.T) {
ctx := context.Background()
instance, template := bundleFixture(t)
now := instance.now()
instance.now = func() time.Time { return now }
instance.config.templateQuiet = time.Minute
if err := unstructured.SetNestedField(template.Object, "nodeSelector: {}", "spec", "podSpecPatch"); err != nil {
t.Fatal(err)
}
if _, err := instance.client.Resource(templateGVR).Namespace("delivery").Update(ctx, template, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
if _, err := instance.readyTemplate(ctx, "delivery", template.GetName()); !errors.Is(err, templatePending) {
t.Fatalf("initial observation = %v", err)
}
now = now.Add(time.Minute)
previous, err := instance.readyTemplate(ctx, "delivery", template.GetName())
if err != nil {
t.Fatal(err)
}
template, err = instance.client.Resource(templateGVR).Namespace("delivery").Get(ctx, template.GetName(), metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
template.SetGeneration(template.GetGeneration() + 1)
if err := unstructured.SetNestedField(template.Object, "{nodeSelector: {}}\n", "spec", "podSpecPatch"); err != nil {
t.Fatal(err)
}
if _, err := instance.client.Resource(templateGVR).Namespace("delivery").Update(ctx, template, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
bundle, err := instance.captureBundle(ctx, template)
if err != nil {
t.Fatal(err)
}
if bundle.name != previous {
t.Fatal("equivalent patches changed captured revision")
}
published, err := instance.readyTemplate(ctx, "delivery", template.GetName())
if err != nil || published != previous {
t.Fatalf("unchanged revision restarted quiet interval: published=%q error=%v", published, err)
}
}
func TestPointCheckpoint(t *testing.T) {
ctx := context.Background()
now := time.Date(2026, 10, 2, 0, 0, 0, 0, time.UTC)
revisions := []string{"0123456789abcdef0123456789abcdef01234567", "1123456789abcdef0123456789abcdef01234567"}
object := pointDelivery(revisions...)
instance := testController(t, object, &mutableRefs{}, &now)
delivery, err := decodeProjectDelivery(object)
if err != nil {
t.Fatal(err)
}
for _, revision := range revisions {
attempt := &attemptStatus{Revision: revision}
if err := instance.ensureWorkflow(ctx, &delivery, attemptName(&delivery, attempt), delivery.Spec.WorkflowTemplateRef.Name, "build", map[string]string{"source-ref": "", "source-revision": revision}, nil); err != nil {
t.Fatal(err)
}
}
if err := instance.reconcileKey(ctx, "delivery", "point"); err != nil {
t.Fatal(err)
}
stored, err := decodeProjectDelivery(getDelivery(t, instance, "point"))
if err != nil {
t.Fatal(err)
}
if len(stored.Status.Points) != 1 {
t.Fatalf("Point checkpoint count = %d", len(stored.Status.Points))
}
first := stored.Status.Points[0].WorkflowName
for _, revision := range revisions {
name := attemptName(&delivery, &attemptStatus{Revision: revision})
workflow, err := instance.client.Resource(workflowGVR).Namespace("delivery").Get(ctx, name, metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
held := slices.Contains(workflow.GetFinalizers(), recordKey)
if held != (name != first) {
t.Fatalf("checkpoint protection for %s = %t", name, held)
}
}
setNamedWorkflowStatus(t, instance, first, "Succeeded", nil)
for range 2 {
if err := instance.reconcileKey(ctx, "delivery", "point"); err != nil {
t.Fatal(err)
}
}
stored, err = decodeProjectDelivery(getDelivery(t, instance, "point"))
if err != nil {
t.Fatal(err)
}
if len(stored.Status.Points) != 2 {
t.Fatalf("recovered checkpoint count = %d", len(stored.Status.Points))
}
}
func TestRetryOldMode(t *testing.T) {
ctx := context.Background()
now := time.Date(2026, 10, 2, 0, 0, 0, 0, time.UTC)
revision := "0123456789abcdef0123456789abcdef01234567"
instance := testController(t, continuousDelivery(), &mutableRefs{values: map[string]string{"refs/heads/main": revision}}, &now)
for range 3 {
instance.reconcileAll(ctx)
now = now.Add(time.Minute)
}
object := getDelivery(t, instance, "continuous")
delivery, err := decodeProjectDelivery(object)
if err != nil {
t.Fatal(err)
}
first := delivery.Status.Continuous.Attempt.WorkflowName
setNamedWorkflowStatus(t, instance, first, "Failed", nil)
instance.reconcileAll(ctx)
object = getDelivery(t, instance, "continuous")
unstructured.RemoveNestedField(object.Object, "spec", "source", "continuous")
if err := unstructured.SetNestedMap(object.Object, map[string]any{"revisions": []any{revision}}, "spec", "source", "point"); err != nil {
t.Fatal(err)
}
annotations := object.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[retryAnnotation] = first
object.SetAnnotations(annotations)
if _, err := instance.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(ctx, object, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
if err := instance.reconcileKey(ctx, "delivery", "continuous"); err != nil {
t.Fatal(err)
}
stored, err := decodeProjectDelivery(getDelivery(t, instance, "continuous"))
if err != nil {
t.Fatal(err)
}
retry := stored.Status.Continuous.Attempt
if stored.Spec.Source.Point == nil || retry.RetryOf != first || retry.Ref != "refs/heads/main" || retry.WorkflowName == first {
t.Fatalf("old-mode retry did not preserve captured selection: %+v", retry)
}
t.Log("retry accepted in Continuous mode while the declared source mode is Point")
}
func TestTreeOverlap(t *testing.T) {
ctx := context.Background()
instance, _ := bundleFixture(t)
peer := *instance
delivery, err := decodeProjectDelivery(continuousDelivery())
if err != nil {
t.Fatal(err)
}
first := &attemptStatus{Ref: "refs/heads/main", Revision: "0123456789abcdef0123456789abcdef01234567", SourceIdentity: "sha256:tree"}
second := &attemptStatus{Ref: first.Ref, Revision: "1123456789abcdef0123456789abcdef01234567", SourceIdentity: first.SourceIdentity}
instance.client = admitClient{Interface: peer.client, before: func(_ *unstructured.Unstructured) {
if err := peer.advanceAttempt(ctx, &delivery, second); err != nil {
t.Fatal(err)
}
}}
if err := instance.advanceAttempt(ctx, &delivery, first); err != nil {
t.Fatal(err)
}
if count := countWorkflows(t, instance, "build"); count != 1 {
t.Fatalf("one source identity produced %d accepted workflows from overlapping SHA observations", count)
}
if first.WorkflowName != second.WorkflowName {
t.Fatal("overlapping observations did not converge on one captured execution")
}
if first.Revision != first.Input["source-revision"] {
t.Fatalf("accepted Workflow SHA %q differs from recorded attempt SHA %q", first.Input["source-revision"], first.Revision)
}
}
func TestRefOverlap(t *testing.T) {
ctx := context.Background()
instance, _ := bundleFixture(t)
peer := *instance
delivery, err := decodeProjectDelivery(continuousDelivery())
if err != nil {
t.Fatal(err)
}
first := &attemptStatus{Ref: "refs/heads/main", Revision: "0123456789abcdef0123456789abcdef01234567", SourceIdentity: "0123456789abcdef0123456789abcdef01234567"}
second := &attemptStatus{Ref: "refs/heads/release", Revision: first.Revision, SourceIdentity: first.SourceIdentity}
instance.client = admitClient{Interface: peer.client, before: func(_ *unstructured.Unstructured) {
if err := peer.advanceAttempt(ctx, &delivery, second); err != nil {
t.Fatal(err)
}
}}
if err := instance.advanceAttempt(ctx, &delivery, first); err != nil {
t.Fatal(err)
}
if count := countWorkflows(t, instance, "build"); count != 1 {
t.Fatalf("one trigger identity produced %d workflows after a ref change", count)
}
if first.Ref != first.Input["source-ref"] || first.WorkflowName != second.WorkflowName {
t.Fatal("captured ref did not follow the accepted execution")
}
}
func TestDetachedPublish(t *testing.T) {
ctx := context.Background()
instance, template := bundleFixture(t)
delivery, err := decodeProjectDelivery(continuousDelivery())
if err != nil {
t.Fatal(err)
}
if err := instance.ensureWorkflow(ctx, &delivery, "old", template.GetName(), "build", nil, nil); err != nil {
t.Fatal(err)
}
changeImage(t, instance, template.GetName(), "example.invalid/builder:v2")
if _, err := instance.readyTemplate(ctx, "delivery", template.GetName()); !errors.Is(err, templatePending) {
t.Fatalf("pending update = %v", err)
}
object := getDelivery(t, instance, "continuous")
if err := unstructured.SetNestedField(object.Object, "other-delivery", "spec", "workflowTemplateRef", "name"); err != nil {
t.Fatal(err)
}
if _, err := instance.client.Resource(projectDeliveryGVR).Namespace("delivery").Update(ctx, object, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
setNamedWorkflowStatus(t, instance, "old", "Succeeded", nil)
for range 2 {
if err := instance.reconcileTemplates(ctx); err != nil {
t.Fatal(err)
}
}
stored, err := instance.client.Resource(templateGVR).Namespace("delivery").Get(ctx, template.GetName(), metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
if stored.GetAnnotations()[publishedKey] != stored.GetAnnotations()[candidateKey] {
t.Fatal("unreferenced template retained its update but never published it after drain")
}
}