github.com/mailgun/holster/v4@v4.20.0/syncutil/broadcast_test.go (about)

     1  package syncutil_test
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/mailgun/holster/v4/syncutil"
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  func TestBroadcaster(t *testing.T) {
    14  	t.Run("Happy path", func(t *testing.T) {
    15  		broadcaster := syncutil.NewBroadcaster()
    16  		ready := make(chan struct{}, 2)
    17  		done := make(chan struct{})
    18  		socket := make(chan string, 11)
    19  		var mutex sync.Mutex
    20  		var chat []string
    21  
    22  		// Start some simple chat clients that are responsible for
    23  		// sending the contents of the []chat slice to their clients
    24  		for i := 0; i < 2; i++ {
    25  			go func(idx int) {
    26  				var clientIndex int
    27  				var once sync.Once
    28  				for {
    29  					mutex.Lock()
    30  					if clientIndex != len(chat) {
    31  						// Pretend we are sending a message to our client via a socket
    32  						socket <- fmt.Sprintf("Client [%d] Chat: %s\n", idx, chat[clientIndex])
    33  						clientIndex++
    34  						mutex.Unlock()
    35  						continue
    36  					}
    37  					mutex.Unlock()
    38  
    39  					// Indicate the client is up and ready to receive broadcasts
    40  					once.Do(func() {
    41  						ready <- struct{}{}
    42  					})
    43  
    44  					// Wait for more chats to be added to chat[]
    45  					select {
    46  					case <-broadcaster.WaitChan(fmt.Sprint(idx)):
    47  					case <-done:
    48  						return
    49  					}
    50  				}
    51  			}(i)
    52  		}
    53  
    54  		// Wait for the clients to be ready
    55  		<-ready
    56  		<-ready
    57  
    58  		// Add some chat lines to the []chat slice
    59  		for i := 0; i < 5; i++ {
    60  			mutex.Lock()
    61  			chat = append(chat, fmt.Sprintf("Message '%d'", i))
    62  			mutex.Unlock()
    63  
    64  			// Notify any clients there are new chats to read
    65  			broadcaster.Broadcast()
    66  		}
    67  
    68  		var count int
    69  		for msg := range socket {
    70  			t.Log(msg)
    71  			count++
    72  			if count == 10 {
    73  				break
    74  			}
    75  		}
    76  
    77  		if count != 10 {
    78  			t.Errorf("count != 10")
    79  		}
    80  		// Tell the clients to quit
    81  		close(done)
    82  	})
    83  
    84  	t.Run("Remove()", func(t *testing.T) {
    85  		const client1 = "Foobar1"
    86  		const client2 = "Foobar2"
    87  
    88  		t.Run("When using WaitChan()", func(t *testing.T) {
    89  			broadcaster := syncutil.NewBroadcaster()
    90  
    91  			// Register broadcast clients with WaitChan().
    92  			ch1 := broadcaster.WaitChan(client1)
    93  			ch2 := broadcaster.WaitChan(client2)
    94  
    95  			// Test broadcast.
    96  			broadcaster.Broadcast()
    97  			assert.Len(t, ch1, 1)
    98  			assert.Len(t, ch2, 1)
    99  			<-ch1
   100  			<-ch2
   101  
   102  			// Remove a client.
   103  			broadcaster.Remove(client1)
   104  
   105  			// Test broadcast again.
   106  			// Verify client1 channel is unchanged.
   107  			broadcaster.Broadcast()
   108  			assert.Empty(t, ch1)
   109  			assert.Len(t, ch2, 1)
   110  		})
   111  
   112  		t.Run("When using Wait()", func(t *testing.T) {
   113  			var doneWg sync.WaitGroup
   114  			broadcaster := syncutil.NewBroadcaster()
   115  
   116  			// Register broadcast clients with Wait().
   117  			doneWg.Add(2)
   118  			var c1Count, c2Count int
   119  
   120  			go func() {
   121  				defer doneWg.Done()
   122  				broadcaster.Wait(client1)
   123  				c1Count++
   124  			}()
   125  			go func() {
   126  				defer doneWg.Done()
   127  				broadcaster.Wait(client2)
   128  				c2Count++
   129  			}()
   130  
   131  			for {
   132  				time.Sleep(1 * time.Millisecond)
   133  				if !broadcaster.Has(client1) {
   134  					continue
   135  				}
   136  				if !broadcaster.Has(client2) {
   137  					continue
   138  				}
   139  				break
   140  			}
   141  
   142  			// Test broadcast.
   143  			broadcaster.Broadcast()
   144  			doneWg.Wait()
   145  			assert.Equal(t, 1, c1Count)
   146  			assert.Equal(t, 1, c2Count)
   147  
   148  			// Get the generated channels.
   149  			ch1 := broadcaster.WaitChan(client1)
   150  			ch2 := broadcaster.WaitChan(client2)
   151  
   152  			// Remove a client.
   153  			broadcaster.Remove(client1)
   154  
   155  			// Test broadcast again.
   156  			// Verify client1 channel is unchanged.
   157  			broadcaster.Broadcast()
   158  			assert.Empty(t, ch1)
   159  			assert.Len(t, ch2, 1)
   160  		})
   161  	})
   162  
   163  	t.Run("WithChannelSize()", func(t *testing.T) {
   164  		t.Run("Default", func(t *testing.T) {
   165  			broadcaster := syncutil.NewBroadcaster()
   166  			ch := broadcaster.WaitChan("Foobar")
   167  			size := cap(ch)
   168  			assert.Equal(t, syncutil.DefaultChannelSize, size)
   169  		})
   170  
   171  		t.Run("Custom", func(t *testing.T) {
   172  			const expectedChannelSize = 0xc0ffee
   173  			broadcaster := syncutil.NewBroadcaster(syncutil.WithChannelSize(expectedChannelSize))
   174  			ch := broadcaster.WaitChan("Foobar")
   175  			size := cap(ch)
   176  			assert.Equal(t, expectedChannelSize, size)
   177  		})
   178  	})
   179  }