github.com/evdatsion/aphelion-dpos-bft@v0.32.1/libs/pubsub/pubsub_test.go (about)

     1  package pubsub_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime/debug"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    14  
    15  	"github.com/evdatsion/aphelion-dpos-bft/libs/pubsub"
    16  	"github.com/evdatsion/aphelion-dpos-bft/libs/pubsub/query"
    17  )
    18  
    19  const (
    20  	clientID = "test-client"
    21  )
    22  
    23  func TestSubscribe(t *testing.T) {
    24  	s := pubsub.NewServer()
    25  	s.SetLogger(log.TestingLogger())
    26  	s.Start()
    27  	defer s.Stop()
    28  
    29  	ctx := context.Background()
    30  	subscription, err := s.Subscribe(ctx, clientID, query.Empty{})
    31  	require.NoError(t, err)
    32  
    33  	assert.Equal(t, 1, s.NumClients())
    34  	assert.Equal(t, 1, s.NumClientSubscriptions(clientID))
    35  
    36  	err = s.Publish(ctx, "Ka-Zar")
    37  	require.NoError(t, err)
    38  	assertReceive(t, "Ka-Zar", subscription.Out())
    39  
    40  	published := make(chan struct{})
    41  	go func() {
    42  		defer close(published)
    43  
    44  		err := s.Publish(ctx, "Quicksilver")
    45  		assert.NoError(t, err)
    46  
    47  		err = s.Publish(ctx, "Asylum")
    48  		assert.NoError(t, err)
    49  
    50  		err = s.Publish(ctx, "Ivan")
    51  		assert.NoError(t, err)
    52  	}()
    53  
    54  	select {
    55  	case <-published:
    56  		assertReceive(t, "Quicksilver", subscription.Out())
    57  		assertCancelled(t, subscription, pubsub.ErrOutOfCapacity)
    58  	case <-time.After(3 * time.Second):
    59  		t.Fatal("Expected Publish(Asylum) not to block")
    60  	}
    61  }
    62  
    63  func TestSubscribeWithCapacity(t *testing.T) {
    64  	s := pubsub.NewServer()
    65  	s.SetLogger(log.TestingLogger())
    66  	s.Start()
    67  	defer s.Stop()
    68  
    69  	ctx := context.Background()
    70  	assert.Panics(t, func() {
    71  		s.Subscribe(ctx, clientID, query.Empty{}, -1)
    72  	})
    73  	assert.Panics(t, func() {
    74  		s.Subscribe(ctx, clientID, query.Empty{}, 0)
    75  	})
    76  	subscription, err := s.Subscribe(ctx, clientID, query.Empty{}, 1)
    77  	require.NoError(t, err)
    78  	err = s.Publish(ctx, "Aggamon")
    79  	require.NoError(t, err)
    80  	assertReceive(t, "Aggamon", subscription.Out())
    81  }
    82  
    83  func TestSubscribeUnbuffered(t *testing.T) {
    84  	s := pubsub.NewServer()
    85  	s.SetLogger(log.TestingLogger())
    86  	s.Start()
    87  	defer s.Stop()
    88  
    89  	ctx := context.Background()
    90  	subscription, err := s.SubscribeUnbuffered(ctx, clientID, query.Empty{})
    91  	require.NoError(t, err)
    92  
    93  	published := make(chan struct{})
    94  	go func() {
    95  		defer close(published)
    96  
    97  		err := s.Publish(ctx, "Ultron")
    98  		assert.NoError(t, err)
    99  
   100  		err = s.Publish(ctx, "Darkhawk")
   101  		assert.NoError(t, err)
   102  	}()
   103  
   104  	select {
   105  	case <-published:
   106  		t.Fatal("Expected Publish(Darkhawk) to block")
   107  	case <-time.After(3 * time.Second):
   108  		assertReceive(t, "Ultron", subscription.Out())
   109  		assertReceive(t, "Darkhawk", subscription.Out())
   110  	}
   111  }
   112  
   113  func TestSlowClientIsRemovedWithErrOutOfCapacity(t *testing.T) {
   114  	s := pubsub.NewServer()
   115  	s.SetLogger(log.TestingLogger())
   116  	s.Start()
   117  	defer s.Stop()
   118  
   119  	ctx := context.Background()
   120  	subscription, err := s.Subscribe(ctx, clientID, query.Empty{})
   121  	require.NoError(t, err)
   122  	err = s.Publish(ctx, "Fat Cobra")
   123  	require.NoError(t, err)
   124  	err = s.Publish(ctx, "Viper")
   125  	require.NoError(t, err)
   126  
   127  	assertCancelled(t, subscription, pubsub.ErrOutOfCapacity)
   128  }
   129  
   130  func TestDifferentClients(t *testing.T) {
   131  	s := pubsub.NewServer()
   132  	s.SetLogger(log.TestingLogger())
   133  	s.Start()
   134  	defer s.Stop()
   135  
   136  	ctx := context.Background()
   137  	subscription1, err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type='NewBlock'"))
   138  	require.NoError(t, err)
   139  	err = s.PublishWithEvents(ctx, "Iceman", map[string][]string{"tm.events.type": {"NewBlock"}})
   140  	require.NoError(t, err)
   141  	assertReceive(t, "Iceman", subscription1.Out())
   142  
   143  	subscription2, err := s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type='NewBlock' AND abci.account.name='Igor'"))
   144  	require.NoError(t, err)
   145  	err = s.PublishWithEvents(ctx, "Ultimo", map[string][]string{"tm.events.type": {"NewBlock"}, "abci.account.name": {"Igor"}})
   146  	require.NoError(t, err)
   147  	assertReceive(t, "Ultimo", subscription1.Out())
   148  	assertReceive(t, "Ultimo", subscription2.Out())
   149  
   150  	subscription3, err := s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type='NewRoundStep' AND abci.account.name='Igor' AND abci.invoice.number = 10"))
   151  	require.NoError(t, err)
   152  	err = s.PublishWithEvents(ctx, "Valeria Richards", map[string][]string{"tm.events.type": {"NewRoundStep"}})
   153  	require.NoError(t, err)
   154  	assert.Zero(t, len(subscription3.Out()))
   155  }
   156  
   157  func TestSubscribeDuplicateKeys(t *testing.T) {
   158  	ctx := context.Background()
   159  	s := pubsub.NewServer()
   160  	s.SetLogger(log.TestingLogger())
   161  	require.NoError(t, s.Start())
   162  	defer s.Stop()
   163  
   164  	testCases := []struct {
   165  		query    string
   166  		expected interface{}
   167  	}{
   168  		{
   169  			"withdraw.rewards='17'",
   170  			"Iceman",
   171  		},
   172  		{
   173  			"withdraw.rewards='22'",
   174  			"Iceman",
   175  		},
   176  		{
   177  			"withdraw.rewards='1' AND withdraw.rewards='22'",
   178  			"Iceman",
   179  		},
   180  		{
   181  			"withdraw.rewards='100'",
   182  			nil,
   183  		},
   184  	}
   185  
   186  	for i, tc := range testCases {
   187  		sub, err := s.Subscribe(ctx, fmt.Sprintf("client-%d", i), query.MustParse(tc.query))
   188  		require.NoError(t, err)
   189  
   190  		err = s.PublishWithEvents(
   191  			ctx,
   192  			"Iceman",
   193  			map[string][]string{
   194  				"transfer.sender":  {"foo", "bar", "baz"},
   195  				"withdraw.rewards": {"1", "17", "22"},
   196  			},
   197  		)
   198  		require.NoError(t, err)
   199  
   200  		if tc.expected != nil {
   201  			assertReceive(t, tc.expected, sub.Out())
   202  		} else {
   203  			require.Zero(t, len(sub.Out()))
   204  		}
   205  	}
   206  }
   207  
   208  func TestClientSubscribesTwice(t *testing.T) {
   209  	s := pubsub.NewServer()
   210  	s.SetLogger(log.TestingLogger())
   211  	s.Start()
   212  	defer s.Stop()
   213  
   214  	ctx := context.Background()
   215  	q := query.MustParse("tm.events.type='NewBlock'")
   216  
   217  	subscription1, err := s.Subscribe(ctx, clientID, q)
   218  	require.NoError(t, err)
   219  	err = s.PublishWithEvents(ctx, "Goblin Queen", map[string][]string{"tm.events.type": {"NewBlock"}})
   220  	require.NoError(t, err)
   221  	assertReceive(t, "Goblin Queen", subscription1.Out())
   222  
   223  	subscription2, err := s.Subscribe(ctx, clientID, q)
   224  	require.Error(t, err)
   225  	require.Nil(t, subscription2)
   226  
   227  	err = s.PublishWithEvents(ctx, "Spider-Man", map[string][]string{"tm.events.type": {"NewBlock"}})
   228  	require.NoError(t, err)
   229  	assertReceive(t, "Spider-Man", subscription1.Out())
   230  }
   231  
   232  func TestUnsubscribe(t *testing.T) {
   233  	s := pubsub.NewServer()
   234  	s.SetLogger(log.TestingLogger())
   235  	s.Start()
   236  	defer s.Stop()
   237  
   238  	ctx := context.Background()
   239  	subscription, err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   240  	require.NoError(t, err)
   241  	err = s.Unsubscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   242  	require.NoError(t, err)
   243  
   244  	err = s.Publish(ctx, "Nick Fury")
   245  	require.NoError(t, err)
   246  	assert.Zero(t, len(subscription.Out()), "Should not receive anything after Unsubscribe")
   247  
   248  	assertCancelled(t, subscription, pubsub.ErrUnsubscribed)
   249  }
   250  
   251  func TestClientUnsubscribesTwice(t *testing.T) {
   252  	s := pubsub.NewServer()
   253  	s.SetLogger(log.TestingLogger())
   254  	s.Start()
   255  	defer s.Stop()
   256  
   257  	ctx := context.Background()
   258  	_, err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   259  	require.NoError(t, err)
   260  	err = s.Unsubscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   261  	require.NoError(t, err)
   262  
   263  	err = s.Unsubscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   264  	assert.Equal(t, pubsub.ErrSubscriptionNotFound, err)
   265  	err = s.UnsubscribeAll(ctx, clientID)
   266  	assert.Equal(t, pubsub.ErrSubscriptionNotFound, err)
   267  }
   268  
   269  func TestResubscribe(t *testing.T) {
   270  	s := pubsub.NewServer()
   271  	s.SetLogger(log.TestingLogger())
   272  	s.Start()
   273  	defer s.Stop()
   274  
   275  	ctx := context.Background()
   276  	subscription, err := s.Subscribe(ctx, clientID, query.Empty{})
   277  	require.NoError(t, err)
   278  	err = s.Unsubscribe(ctx, clientID, query.Empty{})
   279  	require.NoError(t, err)
   280  	subscription, err = s.Subscribe(ctx, clientID, query.Empty{})
   281  	require.NoError(t, err)
   282  
   283  	err = s.Publish(ctx, "Cable")
   284  	require.NoError(t, err)
   285  	assertReceive(t, "Cable", subscription.Out())
   286  }
   287  
   288  func TestUnsubscribeAll(t *testing.T) {
   289  	s := pubsub.NewServer()
   290  	s.SetLogger(log.TestingLogger())
   291  	s.Start()
   292  	defer s.Stop()
   293  
   294  	ctx := context.Background()
   295  	subscription1, err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"))
   296  	require.NoError(t, err)
   297  	subscription2, err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlockHeader'"))
   298  	require.NoError(t, err)
   299  
   300  	err = s.UnsubscribeAll(ctx, clientID)
   301  	require.NoError(t, err)
   302  
   303  	err = s.Publish(ctx, "Nick Fury")
   304  	require.NoError(t, err)
   305  	assert.Zero(t, len(subscription1.Out()), "Should not receive anything after UnsubscribeAll")
   306  	assert.Zero(t, len(subscription2.Out()), "Should not receive anything after UnsubscribeAll")
   307  
   308  	assertCancelled(t, subscription1, pubsub.ErrUnsubscribed)
   309  	assertCancelled(t, subscription2, pubsub.ErrUnsubscribed)
   310  }
   311  
   312  func TestBufferCapacity(t *testing.T) {
   313  	s := pubsub.NewServer(pubsub.BufferCapacity(2))
   314  	s.SetLogger(log.TestingLogger())
   315  
   316  	assert.Equal(t, 2, s.BufferCapacity())
   317  
   318  	ctx := context.Background()
   319  	err := s.Publish(ctx, "Nighthawk")
   320  	require.NoError(t, err)
   321  	err = s.Publish(ctx, "Sage")
   322  	require.NoError(t, err)
   323  
   324  	ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
   325  	defer cancel()
   326  	err = s.Publish(ctx, "Ironclad")
   327  	if assert.Error(t, err) {
   328  		assert.Equal(t, context.DeadlineExceeded, err)
   329  	}
   330  }
   331  
   332  func Benchmark10Clients(b *testing.B)   { benchmarkNClients(10, b) }
   333  func Benchmark100Clients(b *testing.B)  { benchmarkNClients(100, b) }
   334  func Benchmark1000Clients(b *testing.B) { benchmarkNClients(1000, b) }
   335  
   336  func Benchmark10ClientsOneQuery(b *testing.B)   { benchmarkNClientsOneQuery(10, b) }
   337  func Benchmark100ClientsOneQuery(b *testing.B)  { benchmarkNClientsOneQuery(100, b) }
   338  func Benchmark1000ClientsOneQuery(b *testing.B) { benchmarkNClientsOneQuery(1000, b) }
   339  
   340  func benchmarkNClients(n int, b *testing.B) {
   341  	s := pubsub.NewServer()
   342  	s.Start()
   343  	defer s.Stop()
   344  
   345  	ctx := context.Background()
   346  	for i := 0; i < n; i++ {
   347  		subscription, err := s.Subscribe(ctx, clientID, query.MustParse(fmt.Sprintf("abci.Account.Owner = 'Ivan' AND abci.Invoices.Number = %d", i)))
   348  		if err != nil {
   349  			b.Fatal(err)
   350  		}
   351  		go func() {
   352  			for {
   353  				select {
   354  				case <-subscription.Out():
   355  					continue
   356  				case <-subscription.Cancelled():
   357  					return
   358  				}
   359  			}
   360  		}()
   361  	}
   362  
   363  	b.ReportAllocs()
   364  	b.ResetTimer()
   365  	for i := 0; i < b.N; i++ {
   366  		s.PublishWithEvents(ctx, "Gamora", map[string][]string{"abci.Account.Owner": {"Ivan"}, "abci.Invoices.Number": {string(i)}})
   367  	}
   368  }
   369  
   370  func benchmarkNClientsOneQuery(n int, b *testing.B) {
   371  	s := pubsub.NewServer()
   372  	s.Start()
   373  	defer s.Stop()
   374  
   375  	ctx := context.Background()
   376  	q := query.MustParse("abci.Account.Owner = 'Ivan' AND abci.Invoices.Number = 1")
   377  	for i := 0; i < n; i++ {
   378  		subscription, err := s.Subscribe(ctx, clientID, q)
   379  		if err != nil {
   380  			b.Fatal(err)
   381  		}
   382  		go func() {
   383  			for {
   384  				select {
   385  				case <-subscription.Out():
   386  					continue
   387  				case <-subscription.Cancelled():
   388  					return
   389  				}
   390  			}
   391  		}()
   392  	}
   393  
   394  	b.ReportAllocs()
   395  	b.ResetTimer()
   396  	for i := 0; i < b.N; i++ {
   397  		s.PublishWithEvents(ctx, "Gamora", map[string][]string{"abci.Account.Owner": {"Ivan"}, "abci.Invoices.Number": {"1"}})
   398  	}
   399  }
   400  
   401  ///////////////////////////////////////////////////////////////////////////////
   402  /// HELPERS
   403  ///////////////////////////////////////////////////////////////////////////////
   404  
   405  func assertReceive(t *testing.T, expected interface{}, ch <-chan pubsub.Message, msgAndArgs ...interface{}) {
   406  	select {
   407  	case actual := <-ch:
   408  		assert.Equal(t, expected, actual.Data(), msgAndArgs...)
   409  	case <-time.After(1 * time.Second):
   410  		t.Errorf("Expected to receive %v from the channel, got nothing after 1s", expected)
   411  		debug.PrintStack()
   412  	}
   413  }
   414  
   415  func assertCancelled(t *testing.T, subscription *pubsub.Subscription, err error) {
   416  	_, ok := <-subscription.Cancelled()
   417  	assert.False(t, ok)
   418  	assert.Equal(t, err, subscription.Err())
   419  }