github.com/grahambrereton-form3/tilt@v0.10.18/internal/store/subscriber_test.go (about)

     1  package store
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  )
    10  
    11  func TestSubscriber(t *testing.T) {
    12  	st, _ := NewStoreForTesting()
    13  	ctx := context.Background()
    14  	s := newFakeSubscriber()
    15  	st.AddSubscriber(ctx, s)
    16  
    17  	st.NotifySubscribers(ctx)
    18  	call := <-s.onChange
    19  	close(call.done)
    20  }
    21  
    22  func TestSubscriberInterleavedCalls(t *testing.T) {
    23  	st, _ := NewStoreForTesting()
    24  	ctx := context.Background()
    25  	s := newFakeSubscriber()
    26  	st.AddSubscriber(ctx, s)
    27  
    28  	st.NotifySubscribers(ctx)
    29  	call := <-s.onChange
    30  	st.NotifySubscribers(ctx)
    31  	st.NotifySubscribers(ctx)
    32  	time.Sleep(10 * time.Millisecond)
    33  	close(call.done)
    34  
    35  	call = <-s.onChange
    36  	close(call.done)
    37  
    38  	select {
    39  	case <-s.onChange:
    40  		t.Fatal("Expected no more onChange calls")
    41  	case <-time.After(10 * time.Millisecond):
    42  	}
    43  }
    44  
    45  func TestAddSubscriberToAlreadySetUpListCallsSetUp(t *testing.T) {
    46  	st, _ := NewStoreForTesting()
    47  	ctx := context.Background()
    48  	st.subscribers.SetUp(ctx)
    49  
    50  	s := newFakeSubscriber()
    51  	st.AddSubscriber(ctx, s)
    52  
    53  	assert.Equal(t, 1, s.setupCount)
    54  }
    55  
    56  func TestAddSubscriberBeforeSetupNoop(t *testing.T) {
    57  	st, _ := NewStoreForTesting()
    58  	ctx := context.Background()
    59  
    60  	s := newFakeSubscriber()
    61  	st.AddSubscriber(ctx, s)
    62  
    63  	// We haven't called SetUp on subscriber list as a whole, so won't call it on a new individual subscriber
    64  	assert.Equal(t, 0, s.setupCount)
    65  }
    66  
    67  func TestRemoveSubscriber(t *testing.T) {
    68  	st, _ := NewStoreForTesting()
    69  	ctx := context.Background()
    70  	s := newFakeSubscriber()
    71  
    72  	st.AddSubscriber(ctx, s)
    73  	st.NotifySubscribers(ctx)
    74  	s.assertOnChangeCount(t, 1)
    75  
    76  	err := st.RemoveSubscriber(ctx, s)
    77  	assert.NoError(t, err)
    78  	st.NotifySubscribers(ctx)
    79  	s.assertOnChangeCount(t, 0)
    80  }
    81  
    82  func TestRemoveSubscriberNotFound(t *testing.T) {
    83  	st, _ := NewStoreForTesting()
    84  	s := newFakeSubscriber()
    85  	ctx := context.Background()
    86  	err := st.RemoveSubscriber(ctx, s)
    87  	if assert.Error(t, err) {
    88  		assert.Contains(t, err.Error(), "Subscriber not found")
    89  	}
    90  }
    91  
    92  func TestSubscriberSetup(t *testing.T) {
    93  	st, _ := NewStoreForTesting()
    94  	ctx := context.Background()
    95  	s := newFakeSubscriber()
    96  	st.AddSubscriber(ctx, s)
    97  
    98  	st.subscribers.SetUp(ctx)
    99  
   100  	assert.Equal(t, 1, s.setupCount)
   101  }
   102  
   103  func TestSubscriberTeardown(t *testing.T) {
   104  	st, _ := NewStoreForTesting()
   105  	ctx := context.Background()
   106  	s := newFakeSubscriber()
   107  	st.AddSubscriber(ctx, s)
   108  
   109  	go st.Dispatch(NewErrorAction(context.Canceled))
   110  	err := st.Loop(ctx)
   111  	if assert.Error(t, err) {
   112  		assert.Contains(t, err.Error(), "context canceled")
   113  	}
   114  
   115  	assert.Equal(t, 1, s.teardownCount)
   116  }
   117  
   118  func TestSubscriberTeardownOnRemove(t *testing.T) {
   119  	st, _ := NewStoreForTesting()
   120  	ctx := context.Background()
   121  	s := newFakeSubscriber()
   122  	st.AddSubscriber(ctx, s)
   123  
   124  	errChan := make(chan error)
   125  	go func() {
   126  		err := st.Loop(ctx)
   127  		errChan <- err
   128  	}()
   129  
   130  	// Make sure the loop has started.
   131  	st.NotifySubscribers(ctx)
   132  	s.assertOnChangeCount(t, 1)
   133  
   134  	// Remove the subscriber and make sure it doesn't get a change.
   135  	_ = st.RemoveSubscriber(ctx, s)
   136  	st.NotifySubscribers(ctx)
   137  	s.assertOnChangeCount(t, 0)
   138  
   139  	assert.Equal(t, 1, s.teardownCount)
   140  
   141  	st.Dispatch(NewErrorAction(context.Canceled))
   142  
   143  	err := <-errChan
   144  	if assert.Error(t, err) {
   145  		assert.Contains(t, err.Error(), "context canceled")
   146  	}
   147  	assert.Equal(t, 1, s.teardownCount)
   148  }
   149  
   150  type fakeSubscriber struct {
   151  	onChange      chan onChangeCall
   152  	setupCount    int
   153  	teardownCount int
   154  }
   155  
   156  func newFakeSubscriber() *fakeSubscriber {
   157  	return &fakeSubscriber{
   158  		onChange: make(chan onChangeCall),
   159  	}
   160  }
   161  
   162  type onChangeCall struct {
   163  	done chan bool
   164  }
   165  
   166  func (f *fakeSubscriber) assertOnChangeCount(t *testing.T, count int) {
   167  	t.Helper()
   168  
   169  	for i := 0; i < count; i++ {
   170  		f.assertOnChange(t)
   171  	}
   172  
   173  	select {
   174  	case <-time.After(50 * time.Millisecond):
   175  		return
   176  
   177  	case call := <-f.onChange:
   178  		close(call.done)
   179  		t.Fatalf("Expected only %d OnChange calls. Got: %d", count, count+1)
   180  	}
   181  }
   182  
   183  func (f *fakeSubscriber) assertOnChange(t *testing.T) {
   184  	t.Helper()
   185  
   186  	select {
   187  	case <-time.After(50 * time.Millisecond):
   188  		t.Fatalf("timed out waiting for subscriber.OnChange")
   189  	case call := <-f.onChange:
   190  		close(call.done)
   191  	}
   192  }
   193  
   194  func (f *fakeSubscriber) OnChange(ctx context.Context, st RStore) {
   195  	call := onChangeCall{done: make(chan bool)}
   196  	f.onChange <- call
   197  	<-call.done
   198  }
   199  
   200  func (f *fakeSubscriber) SetUp(ctx context.Context) {
   201  	f.setupCount++
   202  }
   203  
   204  func (f *fakeSubscriber) TearDown(ctx context.Context) {
   205  	f.teardownCount++
   206  }