github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/runtimelog/podlogmanager_test.go (about) 1 package runtimelog 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 v1 "k8s.io/api/core/v1" 12 13 "github.com/windmilleng/tilt/internal/testutils/manifestutils" 14 15 "github.com/windmilleng/tilt/internal/container" 16 "github.com/windmilleng/tilt/internal/k8s" 17 "github.com/windmilleng/tilt/internal/store" 18 "github.com/windmilleng/tilt/internal/testutils/bufsync" 19 "github.com/windmilleng/tilt/internal/testutils/tempdir" 20 "github.com/windmilleng/tilt/pkg/logger" 21 "github.com/windmilleng/tilt/pkg/model" 22 ) 23 24 var podID = k8s.PodID("pod-id") 25 var cName = container.Name("cname") 26 var cID = container.ID("cid") 27 28 func TestLogs(t *testing.T) { 29 f := newPLMFixture(t) 30 defer f.TearDown() 31 32 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 33 34 start := time.Now() 35 state := f.store.LockMutableStateForTesting() 36 state.TiltStartTime = start 37 state.WatchFiles = true 38 39 p := store.Pod{ 40 PodID: podID, 41 Phase: v1.PodRunning, 42 } 43 p = PodWithContainer(p, cName, cID) 44 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 45 model.Manifest{Name: "server"}, p)) 46 f.store.UnlockMutableState() 47 48 f.plm.OnChange(f.ctx, f.store) 49 f.AssertOutputContains("hello world!") 50 assert.Equal(t, start, f.kClient.LastPodLogStartTime) 51 } 52 53 func TestLogActions(t *testing.T) { 54 f := newPLMFixture(t) 55 defer f.TearDown() 56 57 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!\ngoodbye world!\n") 58 59 state := f.store.LockMutableStateForTesting() 60 state.WatchFiles = true 61 62 p := store.Pod{ 63 PodID: podID, 64 Phase: v1.PodRunning, 65 } 66 p = PodWithContainer(p, cName, cID) 67 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 68 model.Manifest{Name: "server"}, p)) 69 f.store.UnlockMutableState() 70 71 f.plm.OnChange(f.ctx, f.store) 72 f.ConsumeLogActionsUntil("hello world!") 73 } 74 75 func TestLogsFailed(t *testing.T) { 76 f := newPLMFixture(t) 77 defer f.TearDown() 78 79 f.kClient.ContainerLogsError = fmt.Errorf("my-error") 80 81 state := f.store.LockMutableStateForTesting() 82 state.WatchFiles = true 83 84 p := store.Pod{ 85 PodID: podID, 86 Phase: v1.PodRunning, 87 } 88 p = PodWithContainer(p, cName, cID) 89 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 90 model.Manifest{Name: "server"}, p)) 91 f.store.UnlockMutableState() 92 93 f.plm.OnChange(f.ctx, f.store) 94 f.AssertOutputContains("Error streaming server logs") 95 assert.Contains(t, f.out.String(), "my-error") 96 } 97 98 func TestLogsCanceledUnexpectedly(t *testing.T) { 99 f := newPLMFixture(t) 100 defer f.TearDown() 101 102 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!\n") 103 104 state := f.store.LockMutableStateForTesting() 105 state.WatchFiles = true 106 107 p := store.Pod{ 108 PodID: podID, 109 Phase: v1.PodRunning, 110 } 111 p = PodWithContainer(p, cName, cID) 112 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 113 model.Manifest{Name: "server"}, p)) 114 f.store.UnlockMutableState() 115 116 f.plm.OnChange(f.ctx, f.store) 117 f.AssertOutputContains("hello world!\n") 118 119 // Previous log stream has finished, so the first pod watch has been canceled, 120 // but not cleaned up; check that we start a new watch .OnChange 121 f.kClient.SetLogsForPodContainer(podID, cName, "goodbye world!\n") 122 f.plm.OnChange(f.ctx, f.store) 123 f.AssertOutputContains("goodbye world!\n") 124 } 125 126 func TestMultiContainerLogs(t *testing.T) { 127 f := newPLMFixture(t) 128 defer f.TearDown() 129 130 f.kClient.SetLogsForPodContainer(podID, "cont1", "hello world!") 131 f.kClient.SetLogsForPodContainer(podID, "cont2", "goodbye world!") 132 133 state := f.store.LockMutableStateForTesting() 134 state.WatchFiles = true 135 136 p := store.Pod{ 137 PodID: podID, 138 Phase: v1.PodRunning, 139 Containers: []store.Container{ 140 store.Container{Name: "cont1", ID: "cid1"}, 141 store.Container{Name: "cont2", ID: "cid2"}, 142 }, 143 } 144 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 145 model.Manifest{Name: "server"}, p)) 146 f.store.UnlockMutableState() 147 148 f.plm.OnChange(f.ctx, f.store) 149 f.AssertOutputContains("hello world!") 150 f.AssertOutputContains("goodbye world!") 151 } 152 153 func TestContainerPrefixes(t *testing.T) { 154 f := newPLMFixture(t) 155 defer f.TearDown() 156 157 pID1 := k8s.PodID("pod1") 158 cNamePrefix1 := container.Name("yes-prefix-1") 159 cNamePrefix2 := container.Name("yes-prefix-2") 160 f.kClient.SetLogsForPodContainer(pID1, cNamePrefix1, "hello world!") 161 f.kClient.SetLogsForPodContainer(pID1, cNamePrefix2, "goodbye world!") 162 163 pID2 := k8s.PodID("pod2") 164 cNameNoPrefix := container.Name("no-prefix") 165 f.kClient.SetLogsForPodContainer(pID2, cNameNoPrefix, "hello jupiter!") 166 167 state := f.store.LockMutableStateForTesting() 168 state.WatchFiles = true 169 170 podMultiC := store.Pod{ 171 PodID: pID1, 172 Phase: v1.PodRunning, 173 Containers: []store.Container{ 174 // Pod with multiple containers -- logs should be prefixed with container name 175 store.Container{Name: cNamePrefix1, ID: "cid1"}, 176 store.Container{Name: cNamePrefix2, ID: "cid2"}, 177 }, 178 } 179 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 180 model.Manifest{Name: "multiContainer"}, podMultiC)) 181 182 podSingleC := store.Pod{ 183 PodID: pID2, 184 Phase: v1.PodRunning, 185 Containers: []store.Container{ 186 // Pod with just one container -- logs should NOT be prefixed with container name 187 store.Container{Name: cNameNoPrefix, ID: "cid3"}, 188 }, 189 } 190 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 191 model.Manifest{Name: "singleContainer"}, 192 podSingleC)) 193 f.store.UnlockMutableState() 194 195 f.plm.OnChange(f.ctx, f.store) 196 197 // Make sure we have expected logs 198 f.AssertOutputContains("hello world!") 199 f.AssertOutputContains("goodbye world!") 200 f.AssertOutputContains("hello jupiter!") 201 202 // Check for un/expected prefixes 203 f.AssertOutputContains(cNamePrefix1.String()) 204 f.AssertOutputContains(cNamePrefix2.String()) 205 f.AssertOutputDoesNotContain(cNameNoPrefix.String()) 206 } 207 208 func TestLogsByPodPhase(t *testing.T) { 209 for _, test := range []struct { 210 phase v1.PodPhase 211 expectLogs bool 212 }{ 213 {v1.PodPending, false}, 214 {v1.PodRunning, true}, 215 {v1.PodSucceeded, true}, 216 {v1.PodFailed, true}, 217 {v1.PodUnknown, false}, 218 } { 219 t.Run(string(test.phase), func(t *testing.T) { 220 f := newPLMFixture(t) 221 defer f.TearDown() 222 223 f.kClient.SetLogsForPodContainer(podID, cName, "hello world!") 224 225 state := f.store.LockMutableStateForTesting() 226 state.WatchFiles = true 227 228 p := store.Pod{ 229 PodID: podID, 230 Phase: test.phase, 231 } 232 p = PodWithContainer(p, cName, cID) 233 state.UpsertManifestTarget(manifestutils.NewManifestTargetWithPod( 234 model.Manifest{Name: "server"}, p)) 235 f.store.UnlockMutableState() 236 237 f.plm.OnChange(f.ctx, f.store) 238 if test.expectLogs { 239 f.AssertOutputContains("hello world!") 240 } else { 241 f.AssertOutputDoesNotContain("hello world!") 242 } 243 }) 244 } 245 } 246 247 type plmFixture struct { 248 *tempdir.TempDirFixture 249 ctx context.Context 250 kClient *k8s.FakeK8sClient 251 plm *PodLogManager 252 cancel func() 253 out *bufsync.ThreadSafeBuffer 254 store *store.Store 255 } 256 257 func newPLMFixture(t *testing.T) *plmFixture { 258 f := tempdir.NewTempDirFixture(t) 259 kClient := k8s.NewFakeK8sClient() 260 261 out := bufsync.NewThreadSafeBuffer() 262 reducer := func(ctx context.Context, state *store.EngineState, action store.Action) { 263 podLog, ok := action.(PodLogAction) 264 if !ok { 265 t.Errorf("Expected action type PodLogAction. Actual: %T", action) 266 } 267 out.Write(podLog.LogEvent.Message()) 268 } 269 270 st := store.NewStore(store.Reducer(reducer), store.LogActionsFlag(false)) 271 plm := NewPodLogManager(kClient) 272 273 ctx, cancel := context.WithCancel(context.Background()) 274 l := logger.NewLogger(logger.DebugLvl, out) 275 ctx = logger.WithLogger(ctx, l) 276 go st.Loop(ctx) 277 278 return &plmFixture{ 279 TempDirFixture: f, 280 kClient: kClient, 281 plm: plm, 282 ctx: ctx, 283 cancel: cancel, 284 out: out, 285 store: st, 286 } 287 } 288 289 func (f *plmFixture) ConsumeLogActionsUntil(expected string) { 290 start := time.Now() 291 for time.Since(start) < time.Second { 292 f.store.RLockState() 293 done := strings.Contains(f.out.String(), expected) 294 f.store.RUnlockState() 295 296 if done { 297 return 298 } 299 300 time.Sleep(10 * time.Millisecond) 301 } 302 303 f.T().Fatalf("Timeout. Collected output: %s", f.out.String()) 304 } 305 306 func (f *plmFixture) TearDown() { 307 f.cancel() 308 f.kClient.TearDown() 309 f.TempDirFixture.TearDown() 310 } 311 312 func (f *plmFixture) AssertOutputContains(s string) { 313 err := f.out.WaitUntilContains(s, time.Second) 314 if err != nil { 315 f.T().Fatal(err) 316 } 317 } 318 319 func (f *plmFixture) AssertOutputDoesNotContain(s string) { 320 time.Sleep(10 * time.Millisecond) 321 assert.NotContains(f.T(), f.out.String(), s) 322 } 323 324 func PodWithContainer(pod store.Pod, name container.Name, id container.ID) store.Pod { 325 c := store.Container{Name: name, ID: id} 326 pod.Containers = []store.Container{c} 327 return pod 328 }