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 }