github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/structs/broadcaster_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/nomad/ci"
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  // TestAllocBroadcaster_SendRecv asserts the latest sends to a broadcaster are
    15  // received by listeners.
    16  func TestAllocBroadcaster_SendRecv(t *testing.T) {
    17  	ci.Parallel(t)
    18  
    19  	b := NewAllocBroadcaster(testlog.HCLogger(t))
    20  	defer b.Close()
    21  
    22  	// Create a listener and assert it blocks until an update
    23  	l := b.Listen()
    24  	defer l.Close()
    25  	select {
    26  	case <-l.Ch():
    27  		t.Fatalf("unexpected initial alloc")
    28  	case <-time.After(10 * time.Millisecond):
    29  		// Ok! Ch is empty until a Send
    30  	}
    31  
    32  	// Send an update
    33  	alloc := mock.Alloc()
    34  	alloc.AllocModifyIndex = 10
    35  	require.NoError(t, b.Send(alloc.Copy()))
    36  	recvd := <-l.Ch()
    37  	require.Equal(t, alloc.AllocModifyIndex, recvd.AllocModifyIndex)
    38  
    39  	// Send two now copies and assert only the last was received
    40  	alloc.AllocModifyIndex = 30
    41  	require.NoError(t, b.Send(alloc.Copy()))
    42  	alloc.AllocModifyIndex = 40
    43  	require.NoError(t, b.Send(alloc.Copy()))
    44  
    45  	recvd = <-l.Ch()
    46  	require.Equal(t, alloc.AllocModifyIndex, recvd.AllocModifyIndex)
    47  }
    48  
    49  // TestAllocBroadcaster_RecvBlocks asserts listeners are blocked until a send occurs.
    50  func TestAllocBroadcaster_RecvBlocks(t *testing.T) {
    51  	ci.Parallel(t)
    52  
    53  	alloc := mock.Alloc()
    54  	b := NewAllocBroadcaster(testlog.HCLogger(t))
    55  	defer b.Close()
    56  
    57  	l1 := b.Listen()
    58  	defer l1.Close()
    59  
    60  	l2 := b.Listen()
    61  	defer l2.Close()
    62  
    63  	done := make(chan int, 2)
    64  
    65  	// Subsequent listens should block until a subsequent send
    66  	go func() {
    67  		<-l1.Ch()
    68  		done <- 1
    69  	}()
    70  
    71  	go func() {
    72  		<-l2.Ch()
    73  		done <- 1
    74  	}()
    75  
    76  	select {
    77  	case <-done:
    78  		t.Fatalf("unexpected receive by a listener")
    79  	case <-time.After(10 * time.Millisecond):
    80  	}
    81  
    82  	// Do a Send and expect both listeners to receive it
    83  	b.Send(alloc)
    84  	<-done
    85  	<-done
    86  }
    87  
    88  // TestAllocBroadcaster_Concurrency asserts that the broadcaster behaves
    89  // correctly with concurrent listeners being added and closed.
    90  func TestAllocBroadcaster_Concurrency(t *testing.T) {
    91  	ci.Parallel(t)
    92  
    93  	alloc := mock.Alloc()
    94  	b := NewAllocBroadcaster(testlog.HCLogger(t))
    95  	defer b.Close()
    96  
    97  	errs := make(chan error, 10)
    98  	listeners := make([]*AllocListener, 10)
    99  	for i := 0; i < len(listeners); i++ {
   100  		l := b.Listen()
   101  		defer l.Close()
   102  
   103  		listeners[i] = l
   104  		go func(index uint64, listener *AllocListener) {
   105  			defer listener.Close()
   106  			for {
   107  				a, ok := <-listener.Ch()
   108  				if !ok {
   109  					return
   110  				}
   111  
   112  				if a.AllocModifyIndex < index {
   113  					errs <- fmt.Errorf("index=%d < %d", a.AllocModifyIndex, index)
   114  					return
   115  				}
   116  				index = a.AllocModifyIndex
   117  			}
   118  		}(alloc.AllocModifyIndex, l)
   119  	}
   120  
   121  	for i := 0; i < 100; i++ {
   122  		alloc.AllocModifyIndex++
   123  		require.NoError(t, b.Send(alloc.Copy()))
   124  	}
   125  
   126  	if len(errs) > 0 {
   127  		t.Fatalf("%d listener errors. First error:\n%v", len(errs), <-errs)
   128  	}
   129  
   130  	// Closing a couple shouldn't cause errors
   131  	listeners[0].Close()
   132  	listeners[1].Close()
   133  
   134  	for i := 0; i < 100; i++ {
   135  		alloc.AllocModifyIndex++
   136  		require.NoError(t, b.Send(alloc.Copy()))
   137  	}
   138  
   139  	if len(errs) > 0 {
   140  		t.Fatalf("%d listener errors. First error:\n%v", len(errs), <-errs)
   141  	}
   142  
   143  	// Closing the broadcaster *should* error
   144  	b.Close()
   145  	require.Equal(t, ErrAllocBroadcasterClosed, b.Send(alloc))
   146  
   147  	// All Listeners should be closed
   148  	for _, l := range listeners {
   149  		select {
   150  		case _, ok := <-l.Ch():
   151  			if ok {
   152  				// This check can beat the goroutine above to
   153  				// recv'ing the final update. Listener must be
   154  				// closed on next recv.
   155  				if _, ok := <-l.Ch(); ok {
   156  					t.Fatalf("expected listener to be closed")
   157  				}
   158  			}
   159  		default:
   160  			t.Fatalf("expected listener to be closed; not blocking")
   161  		}
   162  	}
   163  }
   164  
   165  // TestAllocBroadcaster_PrimeListener asserts that newly created listeners are
   166  // primed with the last sent alloc.
   167  func TestAllocBroadcaster_PrimeListener(t *testing.T) {
   168  	ci.Parallel(t)
   169  
   170  	b := NewAllocBroadcaster(testlog.HCLogger(t))
   171  	defer b.Close()
   172  
   173  	alloc := mock.Alloc()
   174  
   175  	// Send an update before creating a listener
   176  	require.NoError(t, b.Send(alloc))
   177  
   178  	// Create a listener and assert it immediately receives an update
   179  	l := b.Listen()
   180  	defer l.Close()
   181  	select {
   182  	case recv := <-l.Ch():
   183  		require.Equal(t, alloc, recv)
   184  	case <-time.After(10 * time.Millisecond):
   185  		t.Fatalf("expected to receive initial value")
   186  	}
   187  }
   188  
   189  // TestAllocBroadcaster_Closed asserts that newly created listeners are
   190  // primed with the last sent alloc even when the broadcaster is closed.
   191  func TestAllocBroadcaster_Closed(t *testing.T) {
   192  	ci.Parallel(t)
   193  
   194  	b := NewAllocBroadcaster(testlog.HCLogger(t))
   195  
   196  	alloc := mock.Alloc()
   197  
   198  	// Send an update before creating a listener
   199  	require.NoError(t, b.Send(alloc))
   200  
   201  	// Close the broadcaster after sending a single update
   202  	b.Close()
   203  
   204  	// Create a listener and assert it immediately receives an update
   205  	l := b.Listen()
   206  	defer l.Close()
   207  	select {
   208  	case recv := <-l.Ch():
   209  		require.Equal(t, alloc, recv)
   210  	case <-time.After(10 * time.Millisecond):
   211  		t.Fatalf("expected to receive initial value")
   212  	}
   213  
   214  	// Ch should now be closed.
   215  	select {
   216  	case _, ok := <-l.Ch():
   217  		require.False(t, ok)
   218  	case <-time.After(10 * time.Millisecond):
   219  		t.Fatalf("expected Ch() to be closed")
   220  	}
   221  }