github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/k8srollout/podmonitor_test.go (about)

     1  package k8srollout
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/jonboulle/clockwork"
    13  
    14  	"github.com/tilt-dev/tilt/pkg/apis"
    15  
    16  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    17  
    18  	"github.com/stretchr/testify/assert"
    19  	v1 "k8s.io/api/core/v1"
    20  
    21  	"github.com/tilt-dev/tilt/internal/store"
    22  	"github.com/tilt-dev/tilt/internal/testutils/bufsync"
    23  	"github.com/tilt-dev/tilt/internal/testutils/manifestutils"
    24  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    25  	"github.com/tilt-dev/tilt/pkg/logger"
    26  	"github.com/tilt-dev/tilt/pkg/model"
    27  )
    28  
    29  // NOTE(han): set at runtime with:
    30  // go test -ldflags="-X 'github.com/tilt-dev/tilt/internal/engine/k8srollout.PodmonitorWriteGoldenMaster=1'" ./internal/engine/k8srollout
    31  var PodmonitorWriteGoldenMaster = "0"
    32  
    33  func TestMonitorReady(t *testing.T) {
    34  	f := newPMFixture(t)
    35  
    36  	start := f.clock.Now()
    37  	p := v1alpha1.Pod{
    38  		Name:      "pod-id",
    39  		CreatedAt: apis.NewTime(start),
    40  		Conditions: []v1alpha1.PodCondition{
    41  			{
    42  				Type:               string(v1.PodScheduled),
    43  				Status:             string(v1.ConditionTrue),
    44  				LastTransitionTime: apis.NewTime(start.Add(time.Second)),
    45  			},
    46  			{
    47  				Type:               string(v1.PodInitialized),
    48  				Status:             string(v1.ConditionTrue),
    49  				LastTransitionTime: apis.NewTime(start.Add(5 * time.Second)),
    50  			},
    51  			{
    52  				Type:               string(v1.PodReady),
    53  				Status:             string(v1.ConditionTrue),
    54  				LastTransitionTime: apis.NewTime(start.Add(10 * time.Second)),
    55  			},
    56  		},
    57  	}
    58  
    59  	state := store.NewState()
    60  	state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod(
    61  		model.Manifest{Name: "server"}, p))
    62  	f.store.SetState(*state)
    63  
    64  	_ = f.pm.OnChange(f.ctx, f.store, store.LegacyChangeSummary())
    65  
    66  	assertSnapshot(t, f.out.String())
    67  }
    68  
    69  func TestAttachExisting(t *testing.T) {
    70  	f := newPMFixture(t)
    71  
    72  	start := f.clock.Now()
    73  	p := v1alpha1.Pod{
    74  		Name:      "pod-id",
    75  		CreatedAt: apis.NewTime(start.Add(-10 * time.Second)),
    76  		Conditions: []v1alpha1.PodCondition{
    77  			{
    78  				Type:               string(v1.PodScheduled),
    79  				Status:             string(v1.ConditionTrue),
    80  				LastTransitionTime: apis.NewTime(start.Add(-10 * time.Second)),
    81  			},
    82  			{
    83  				Type:               string(v1.PodInitialized),
    84  				Status:             string(v1.ConditionTrue),
    85  				LastTransitionTime: apis.NewTime(start.Add(-10 * time.Second)),
    86  			},
    87  			{
    88  				Type:               string(v1.PodReady),
    89  				Status:             string(v1.ConditionTrue),
    90  				LastTransitionTime: apis.NewTime(start.Add(-10 * time.Second)),
    91  			},
    92  		},
    93  	}
    94  
    95  	state := store.NewState()
    96  	state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod(
    97  		model.Manifest{Name: "server"}, p))
    98  	f.store.SetState(*state)
    99  
   100  	_ = f.pm.OnChange(f.ctx, f.store, store.LegacyChangeSummary())
   101  
   102  	assertSnapshot(t, f.out.String())
   103  }
   104  
   105  // https://github.com/tilt-dev/tilt/issues/3513
   106  func TestJobCompleted(t *testing.T) {
   107  	f := newPMFixture(t)
   108  
   109  	start := f.clock.Now()
   110  	p := v1alpha1.Pod{
   111  		Name:      "pod-id",
   112  		CreatedAt: apis.NewTime(start),
   113  		Conditions: []v1alpha1.PodCondition{
   114  			{
   115  				Type:               string(v1.PodScheduled),
   116  				Status:             string(v1.ConditionTrue),
   117  				LastTransitionTime: apis.NewTime(start.Add(time.Second)),
   118  				Reason:             "PodCompleted",
   119  			},
   120  			{
   121  				Type:               string(v1.PodInitialized),
   122  				Status:             string(v1.ConditionTrue),
   123  				LastTransitionTime: apis.NewTime(start.Add(5 * time.Second)),
   124  				Reason:             "PodCompleted",
   125  			},
   126  			{
   127  				Type:               string(v1.PodReady),
   128  				Status:             string(v1.ConditionFalse),
   129  				LastTransitionTime: apis.NewTime(start.Add(10 * time.Second)),
   130  				Reason:             "PodCompleted",
   131  			},
   132  		},
   133  	}
   134  
   135  	state := store.NewState()
   136  	state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod(
   137  		model.Manifest{Name: "server"}, p))
   138  	f.store.SetState(*state)
   139  
   140  	_ = f.pm.OnChange(f.ctx, f.store, store.LegacyChangeSummary())
   141  
   142  	assertSnapshot(t, f.out.String())
   143  }
   144  
   145  func TestJobCompletedAfterReady(t *testing.T) {
   146  	f := newPMFixture(t)
   147  
   148  	start := f.clock.Now()
   149  	p := v1alpha1.Pod{
   150  		Name:      "pod-id",
   151  		CreatedAt: apis.NewTime(start),
   152  		Conditions: []v1alpha1.PodCondition{
   153  			{
   154  				Type:               string(v1.PodScheduled),
   155  				Status:             string(v1.ConditionTrue),
   156  				LastTransitionTime: apis.NewTime(start.Add(time.Second)),
   157  			},
   158  			{
   159  				Type:               string(v1.PodInitialized),
   160  				Status:             string(v1.ConditionTrue),
   161  				LastTransitionTime: apis.NewTime(start.Add(5 * time.Second)),
   162  			},
   163  			{
   164  				Type:               string(v1.PodReady),
   165  				Status:             string(v1.ConditionTrue),
   166  				LastTransitionTime: apis.NewTime(start.Add(10 * time.Second)),
   167  			},
   168  		},
   169  	}
   170  
   171  	state := store.NewState()
   172  	state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod(
   173  		model.Manifest{Name: "server"}, p))
   174  	f.store.SetState(*state)
   175  	_ = f.pm.OnChange(f.ctx, f.store, store.LegacyChangeSummary())
   176  
   177  	p.Conditions[2].Status = string(v1.ConditionFalse)
   178  	p.Conditions[2] = v1alpha1.PodCondition{
   179  		Type:               string(v1.PodReady),
   180  		Status:             string(v1.ConditionFalse),
   181  		LastTransitionTime: apis.NewTime(start.Add(20 * time.Second)),
   182  		Reason:             "PodCompleted",
   183  	}
   184  	state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod(
   185  		model.Manifest{Name: "server"}, p))
   186  	f.store.SetState(*state)
   187  	_ = f.pm.OnChange(f.ctx, f.store, store.LegacyChangeSummary())
   188  
   189  	assertSnapshot(t, f.out.String())
   190  }
   191  
   192  type pmFixture struct {
   193  	*tempdir.TempDirFixture
   194  	ctx    context.Context
   195  	pm     *PodMonitor
   196  	cancel func()
   197  	out    *bufsync.ThreadSafeBuffer
   198  	store  *testStore
   199  	clock  clockwork.FakeClock
   200  }
   201  
   202  func newPMFixture(t *testing.T) *pmFixture {
   203  	f := tempdir.NewTempDirFixture(t)
   204  
   205  	out := bufsync.NewThreadSafeBuffer()
   206  	st := NewTestingStore(out)
   207  	clock := clockwork.NewFakeClock()
   208  	pm := NewPodMonitor(clock)
   209  
   210  	ctx, cancel := context.WithCancel(context.Background())
   211  	ctx = logger.WithLogger(ctx, logger.NewTestLogger(out))
   212  
   213  	ret := &pmFixture{
   214  		TempDirFixture: f,
   215  		pm:             pm,
   216  		ctx:            ctx,
   217  		cancel:         cancel,
   218  		out:            out,
   219  		store:          st,
   220  		clock:          clock,
   221  	}
   222  	clock.Advance(time.Second)
   223  
   224  	t.Cleanup(ret.TearDown)
   225  
   226  	return ret
   227  }
   228  
   229  func (f *pmFixture) TearDown() {
   230  	f.cancel()
   231  }
   232  
   233  type testStore struct {
   234  	*store.TestingStore
   235  	out io.Writer
   236  }
   237  
   238  func NewTestingStore(out io.Writer) *testStore {
   239  	return &testStore{
   240  		TestingStore: store.NewTestingStore(),
   241  		out:          out,
   242  	}
   243  }
   244  
   245  func (s *testStore) Dispatch(action store.Action) {
   246  	s.TestingStore.Dispatch(action)
   247  
   248  	logAction, ok := action.(store.LogAction)
   249  	if ok {
   250  		_, _ = s.out.Write(logAction.Message())
   251  	}
   252  }
   253  
   254  func assertSnapshot(t *testing.T, output string) {
   255  	d1 := []byte(output)
   256  	gmPath := fmt.Sprintf("testdata/%s_master", t.Name())
   257  	if PodmonitorWriteGoldenMaster == "1" {
   258  		err := os.WriteFile(gmPath, d1, 0644)
   259  		if err != nil {
   260  			t.Fatal(err)
   261  		}
   262  	}
   263  	expected, err := os.ReadFile(gmPath)
   264  	if err != nil {
   265  		t.Fatal(err)
   266  	}
   267  
   268  	assert.Equal(t, normalize(string(expected)), normalize(output))
   269  }
   270  
   271  func normalize(s string) string {
   272  	return strings.ReplaceAll(s, "\r\n", "\n")
   273  }