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  }