github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/store/testing_store.go (about)

     1  package store
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/tilt-dev/tilt/pkg/model"
    11  )
    12  
    13  var _ RStore = &TestingStore{}
    14  
    15  type TestingStore struct {
    16  	state   *EngineState
    17  	stateMu sync.RWMutex
    18  
    19  	actions   []Action
    20  	actionsMu sync.RWMutex
    21  }
    22  
    23  func NewTestingStore() *TestingStore {
    24  	return &TestingStore{
    25  		state: NewState(),
    26  	}
    27  }
    28  
    29  func (s *TestingStore) LockMutableStateForTesting() *EngineState {
    30  	s.stateMu.Lock()
    31  	return s.state
    32  }
    33  
    34  func (s *TestingStore) UnlockMutableState() {
    35  	s.stateMu.Unlock()
    36  }
    37  
    38  func (s *TestingStore) SetState(state EngineState) {
    39  	s.stateMu.Lock()
    40  	defer s.stateMu.Unlock()
    41  	s.state = &state
    42  }
    43  
    44  func (s *TestingStore) WithState(f func(state *EngineState)) {
    45  	s.stateMu.Lock()
    46  	defer s.stateMu.Unlock()
    47  	f(s.state)
    48  }
    49  
    50  func (s *TestingStore) WithManifestState(name model.ManifestName, f func(ms *ManifestState)) {
    51  	s.WithState(func(state *EngineState) {
    52  		mt, ok := state.ManifestTargets[name]
    53  		if !ok {
    54  			panic(fmt.Sprintf("no manifest target for %s", name))
    55  		}
    56  		f(mt.State)
    57  	})
    58  }
    59  
    60  func (s *TestingStore) StateMutex() *sync.RWMutex {
    61  	return &s.stateMu
    62  }
    63  
    64  func (s *TestingStore) RLockState() EngineState {
    65  	s.stateMu.RLock()
    66  	return *(s.state)
    67  }
    68  
    69  func (s *TestingStore) RUnlockState() {
    70  	s.stateMu.RUnlock()
    71  }
    72  
    73  func (s *TestingStore) Dispatch(action Action) {
    74  	s.actionsMu.Lock()
    75  	defer s.actionsMu.Unlock()
    76  
    77  	s.actions = append(s.actions, action)
    78  }
    79  
    80  func (s *TestingStore) ClearActions() {
    81  	s.actionsMu.Lock()
    82  	defer s.actionsMu.Unlock()
    83  
    84  	s.actions = nil
    85  }
    86  
    87  func (s *TestingStore) Actions() []Action {
    88  	s.actionsMu.RLock()
    89  	defer s.actionsMu.RUnlock()
    90  
    91  	return append([]Action{}, s.actions...)
    92  }
    93  
    94  func (s *TestingStore) AssertNoErrorActions(t testing.TB) bool {
    95  	t.Helper()
    96  	s.actionsMu.RLock()
    97  	defer s.actionsMu.RUnlock()
    98  	for _, action := range s.actions {
    99  		errAction, ok := action.(ErrorAction)
   100  		if ok {
   101  			t.Errorf("Error action: %s", errAction)
   102  			return false
   103  		}
   104  	}
   105  	return true
   106  }
   107  
   108  func (s *TestingStore) WaitForAction(t testing.TB, typ reflect.Type) Action {
   109  	return WaitForAction(t, typ, s.Actions)
   110  }
   111  
   112  // for use by tests (with a real channel-based store, NOT a TestingStore), to wait until
   113  // an action of the specified type comes out of the given chan at some point we might want
   114  // it to return the index it was found at, and then take an index, so that we can start
   115  // searching from the next index
   116  func WaitForAction(t testing.TB, typ reflect.Type, getActions func() []Action) Action {
   117  	start := time.Now()
   118  	timeout := 500 * time.Millisecond
   119  
   120  	current := 0
   121  	for time.Since(start) < timeout {
   122  		actions := getActions()
   123  		for ; current < len(actions); current++ {
   124  			a := actions[current]
   125  			if reflect.TypeOf(a) == typ {
   126  				return a
   127  			} else if la, ok := a.(LogAction); ok {
   128  				fmt.Println(string(la.Message()))
   129  			}
   130  		}
   131  	}
   132  	t.Fatalf("timed out waiting for action of type %s. Saw the following actions while waiting: %+v",
   133  		typ.Name(),
   134  		getActions())
   135  	return nil
   136  }
   137  
   138  // for use by tests (with a real channel-based store, NOT a TestingStore). Assert that
   139  // we don't see an action of the given type
   140  func AssertNoActionOfType(t testing.TB, typ reflect.Type, getActions func() []Action) Action {
   141  	start := time.Now()
   142  	timeout := 300 * time.Millisecond
   143  
   144  	current := 0
   145  	for time.Since(start) < timeout {
   146  		actions := getActions()
   147  		for ; current < len(actions); current++ {
   148  			a := actions[current]
   149  			if reflect.TypeOf(a) == typ {
   150  				t.Fatalf("Found action of type %s where none was expected: %+v", typ.Name(), a)
   151  			} else if la, ok := a.(LogAction); ok {
   152  				fmt.Println(string(la.Message()))
   153  			}
   154  		}
   155  	}
   156  	return nil
   157  }