github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/filewatch/fsevent/fake_watcher.go (about)

     1  package fsevent
     2  
     3  import (
     4  	"path/filepath"
     5  	"sync"
     6  	"sync/atomic"
     7  
     8  	"github.com/tilt-dev/tilt/internal/ospath"
     9  	"github.com/tilt-dev/tilt/internal/watch"
    10  	"github.com/tilt-dev/tilt/pkg/logger"
    11  )
    12  
    13  type FakeMultiWatcher struct {
    14  	Events chan watch.FileEvent
    15  	Errors chan error
    16  
    17  	mu         sync.Mutex
    18  	watchers   []*FakeWatcher
    19  	subs       []chan watch.FileEvent
    20  	subsErrors []chan error
    21  }
    22  
    23  func NewFakeMultiWatcher() *FakeMultiWatcher {
    24  	r := &FakeMultiWatcher{
    25  		Events: make(chan watch.FileEvent, 20),
    26  		Errors: make(chan error, 20),
    27  	}
    28  	go r.loop()
    29  	return r
    30  }
    31  
    32  func (w *FakeMultiWatcher) NewSub(paths []string, ignore watch.PathMatcher, _ logger.Logger) (watch.Notify, error) {
    33  	subCh := make(chan watch.FileEvent)
    34  	errorCh := make(chan error)
    35  	w.mu.Lock()
    36  	defer w.mu.Unlock()
    37  
    38  	watcher := NewFakeWatcher(subCh, errorCh, paths, ignore)
    39  	w.watchers = append(w.watchers, watcher)
    40  	w.subs = append(w.subs, subCh)
    41  	w.subsErrors = append(w.subsErrors, errorCh)
    42  	return watcher, nil
    43  }
    44  
    45  func (w *FakeMultiWatcher) getSubs() []chan watch.FileEvent {
    46  	w.mu.Lock()
    47  	defer w.mu.Unlock()
    48  	return append([]chan watch.FileEvent{}, w.subs...)
    49  }
    50  
    51  func (w *FakeMultiWatcher) getSubErrors() []chan error {
    52  	w.mu.Lock()
    53  	defer w.mu.Unlock()
    54  	return append([]chan error{}, w.subsErrors...)
    55  }
    56  
    57  func (w *FakeMultiWatcher) loop() {
    58  	defer func() {
    59  		for _, sub := range w.getSubs() {
    60  			close(sub)
    61  		}
    62  
    63  		for _, sub := range w.getSubErrors() {
    64  			close(sub)
    65  		}
    66  	}()
    67  
    68  	for {
    69  		select {
    70  		case e, ok := <-w.Events:
    71  			if !ok {
    72  				return
    73  			}
    74  			w.mu.Lock()
    75  			for _, watcher := range w.watchers {
    76  				if watcher.Running && watcher.matches(e.Path()) {
    77  					watcher.inboundCh <- e
    78  				}
    79  			}
    80  			w.mu.Unlock()
    81  		case e, ok := <-w.Errors:
    82  			if !ok {
    83  				return
    84  			}
    85  			for _, sub := range w.getSubErrors() {
    86  				sub <- e
    87  			}
    88  		}
    89  	}
    90  }
    91  
    92  type FakeWatcher struct {
    93  	inboundCh  chan watch.FileEvent
    94  	outboundCh chan watch.FileEvent
    95  	errorCh    chan error
    96  	closeCh    chan bool
    97  
    98  	eventCount uint64
    99  
   100  	paths  []string
   101  	ignore watch.PathMatcher
   102  
   103  	Running  bool
   104  	StartErr error
   105  }
   106  
   107  func NewFakeWatcher(inboundCh chan watch.FileEvent, errorCh chan error, paths []string, ignore watch.PathMatcher) *FakeWatcher {
   108  	for i, path := range paths {
   109  		paths[i], _ = filepath.Abs(path)
   110  	}
   111  
   112  	return &FakeWatcher{
   113  		inboundCh:  inboundCh,
   114  		outboundCh: make(chan watch.FileEvent, 20),
   115  		errorCh:    errorCh,
   116  		paths:      paths,
   117  		ignore:     ignore,
   118  		closeCh:    make(chan bool),
   119  	}
   120  }
   121  
   122  func (w *FakeWatcher) matches(path string) bool {
   123  	ignore, _ := w.ignore.Matches(path)
   124  	if ignore {
   125  		return false
   126  	}
   127  
   128  	for _, watched := range w.paths {
   129  		if ospath.IsChild(watched, path) {
   130  			return true
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  func (w *FakeWatcher) Start() error {
   137  	w.Running = true
   138  	go w.loop()
   139  	if w.StartErr != nil {
   140  		return w.StartErr
   141  	}
   142  	return nil
   143  }
   144  
   145  func (w *FakeWatcher) Close() error {
   146  	close(w.closeCh)
   147  	<-w.outboundCh
   148  	return nil
   149  }
   150  
   151  func (w *FakeWatcher) Errors() chan error {
   152  	return w.errorCh
   153  }
   154  
   155  func (w *FakeWatcher) Events() chan watch.FileEvent {
   156  	return w.outboundCh
   157  }
   158  
   159  func (w *FakeWatcher) TotalEventCount() uint64 {
   160  	return atomic.LoadUint64(&w.eventCount)
   161  }
   162  
   163  func (w *FakeWatcher) QueuedCount() int {
   164  	return len(w.outboundCh)
   165  }
   166  
   167  func (w *FakeWatcher) loop() {
   168  	defer func() {
   169  		w.Running = false
   170  		close(w.outboundCh)
   171  	}()
   172  
   173  	var q []watch.FileEvent
   174  	for {
   175  		if len(q) == 0 {
   176  			select {
   177  			case e, ok := <-w.inboundCh:
   178  				if !ok {
   179  					return
   180  				}
   181  				q = append(q, e)
   182  			case <-w.closeCh:
   183  				return
   184  			}
   185  		} else {
   186  			e := q[0]
   187  			w.outboundCh <- e
   188  			atomic.AddUint64(&w.eventCount, 1)
   189  			q = q[1:]
   190  		}
   191  	}
   192  }
   193  
   194  var _ watch.Notify = &FakeWatcher{}