github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/bus/bus_test.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package bus
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"reflect"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  	"go.uber.org/atomic"
    31  )
    32  
    33  func TestBus(t *testing.T) {
    34  
    35  	const topic = "test/topic"
    36  
    37  	b := NewBus()
    38  
    39  	var counter = 0
    40  	var wg = sync.WaitGroup{}
    41  
    42  	// Test Subscribe
    43  	fn := func(topic string, arg1 string, arg2 string) {
    44  		counter++
    45  		wg.Done()
    46  	}
    47  	wg.Add(1)
    48  	err := b.Subscribe(topic, fn)
    49  	if err != nil {
    50  		t.Errorf("Subscribe returned an error: %v", err)
    51  	}
    52  
    53  	// Test Publish
    54  	b.Publish(topic, "hello", "world")
    55  
    56  	wg.Wait()
    57  
    58  	require.Equal(t, counter, 1)
    59  
    60  	// ------------------------------------------------------------
    61  
    62  	// Test Stat
    63  	stats, total, err := b.Stat(context.Background(), 999, 0, "", "")
    64  	if err != nil {
    65  		t.Errorf("Stat returned an error: %v", err)
    66  	}
    67  	if total != 1 {
    68  		t.Errorf("Stat returned a non-zero total: %d", total)
    69  	}
    70  	if len(stats) != 1 {
    71  		t.Errorf("Stat returned a non-empty stats slice: %v", stats)
    72  	}
    73  
    74  	require.Equal(t, stats[0].Topic, topic)
    75  	require.Equal(t, stats[0].Subscribers, 1)
    76  
    77  	// ------------------------------------------------------------
    78  
    79  	// Test Unsubscribe
    80  	err = b.Unsubscribe(topic, fn)
    81  	if err != nil {
    82  		t.Errorf("Unsubscribe returned an error: %v", err)
    83  	}
    84  
    85  	// Test Publish
    86  	b.Publish(topic, "hello", "world")
    87  
    88  	time.Sleep(time.Second)
    89  
    90  	require.Equal(t, counter, 1)
    91  
    92  	// ------------------------------------------------------------
    93  
    94  	// Test Stat
    95  	stats, total, err = b.Stat(context.Background(), 999, 0, "", "")
    96  	if err != nil {
    97  		t.Errorf("Stat returned an error: %v", err)
    98  	}
    99  	if total != 0 {
   100  		t.Errorf("Stat returned a non-zero total: %d", total)
   101  	}
   102  	if len(stats) != 0 {
   103  		t.Errorf("Stat returned a non-empty stats slice: %v", stats)
   104  	}
   105  
   106  	// ------------------------------------------------------------
   107  
   108  	// Test Subscribe
   109  	fn = func(topic string, arg1 string, arg2 string) {
   110  		counter++
   111  	}
   112  	err = b.Subscribe(topic, fn)
   113  	if err != nil {
   114  		t.Errorf("Subscribe returned an error: %v", err)
   115  	}
   116  
   117  	// Test Close
   118  	b.CloseTopic(topic)
   119  
   120  	// Test Publish
   121  	b.Publish(topic, "hello", "world")
   122  
   123  	time.Sleep(time.Second)
   124  
   125  	require.Equal(t, 1, counter)
   126  
   127  	// Test Stat
   128  	stats, total, err = b.Stat(context.Background(), 999, 0, "", "")
   129  	if err != nil {
   130  		t.Errorf("Stat returned an error: %v", err)
   131  	}
   132  	if total != 0 {
   133  		t.Errorf("Stat returned a non-zero total: %d", total)
   134  	}
   135  	if len(stats) != 0 {
   136  		t.Errorf("Stat returned a non-empty stats slice: %v", stats)
   137  	}
   138  
   139  	// ------------------------------------------------------------
   140  
   141  	fn = func(topic string, arg1 string, arg2 string) {
   142  		counter++
   143  	}
   144  	err = b.Subscribe(topic, fn)
   145  	if err != nil {
   146  		t.Errorf("Subscribe returned an error: %v", err)
   147  	}
   148  
   149  	// Test Close
   150  	b.Purge()
   151  
   152  	// Test Publish
   153  	b.Publish(topic, "hello", "world")
   154  
   155  	time.Sleep(time.Second)
   156  
   157  	require.Equal(t, counter, 1)
   158  
   159  	// Test Stat
   160  	stats, total, err = b.Stat(context.Background(), 999, 0, "", "")
   161  	if err != nil {
   162  		t.Errorf("Stat returned an error: %v", err)
   163  	}
   164  	if total != 0 {
   165  		t.Errorf("Stat returned a non-zero total: %d", total)
   166  	}
   167  	if len(stats) != 0 {
   168  		t.Errorf("Stat returned a non-empty stats slice: %v", stats)
   169  	}
   170  
   171  	// ------------------------------------------------------------
   172  
   173  	// Test buildHandlerArgs
   174  	args := buildHandlerArgs([]interface{}{topic, "hello", "world"})
   175  	if len(args) != 3 {
   176  		t.Errorf("buildHandlerArgs returned the wrong number of arguments: %v", args)
   177  	}
   178  	if args[0].String() != topic {
   179  		t.Errorf("buildHandlerArgs returned the wrong topic: %v", args[0])
   180  	}
   181  	if args[1].String() != "hello" {
   182  		t.Errorf("buildHandlerArgs returned the wrong arg1: %v", args[1])
   183  	}
   184  	if args[2].String() != "world" {
   185  		t.Errorf("buildHandlerArgs returned the wrong arg2: %v", args[2])
   186  	}
   187  
   188  	// Test reflection of buildHandlerArgs
   189  	if reflect.TypeOf(buildHandlerArgs).Kind() != reflect.Func {
   190  		t.Errorf("buildHandlerArgs is not a function")
   191  	}
   192  }
   193  
   194  func TestBus2(t *testing.T) {
   195  
   196  	const topic = "test/topic"
   197  
   198  	b := NewBus()
   199  
   200  	var counter atomic.Int32
   201  	var wg = sync.WaitGroup{}
   202  
   203  	// Test Subscribe
   204  	fn := func(topic string, arg1 string, arg2 string) {
   205  		fmt.Println("fn1")
   206  		counter.Inc()
   207  		wg.Done()
   208  	}
   209  
   210  	fn2 := func(topic string, arg1 string, arg2 string) {
   211  		fmt.Println("fn2")
   212  		counter.Inc()
   213  		wg.Done()
   214  	}
   215  
   216  	fn3 := func(topic string, arg1 string, arg2 string) {
   217  		fmt.Println("fn3")
   218  		counter.Inc()
   219  		wg.Done()
   220  	}
   221  
   222  	wg.Add(3)
   223  
   224  	err := b.Subscribe(topic, fn)
   225  	if err != nil {
   226  		t.Errorf("Subscribe returned an error: %v", err)
   227  	}
   228  	err = b.Subscribe(topic, fn2)
   229  	if err != nil {
   230  		t.Errorf("Subscribe returned an error: %v", err)
   231  	}
   232  	err = b.Subscribe(topic, fn3)
   233  	if err != nil {
   234  		t.Errorf("Subscribe returned an error: %v", err)
   235  	}
   236  
   237  	// Test Stat
   238  	stats, total, err := b.Stat(context.Background(), 999, 0, "", "")
   239  	if err != nil {
   240  		t.Errorf("Stat returned an error: %v", err)
   241  	}
   242  	if total != 1 {
   243  		t.Errorf("Stat returned a non-zero total: %d", total)
   244  	}
   245  	if len(stats) != 1 {
   246  		t.Errorf("Stat returned a non-empty stats slice: %v", stats)
   247  	}
   248  
   249  	// Test Publish
   250  	b.Publish(topic, "hello", "world")
   251  
   252  	wg.Wait()
   253  
   254  	require.Equal(t, int32(3), counter.Load())
   255  }
   256  
   257  func TestBus3(t *testing.T) {
   258  
   259  	bus := NewBus()
   260  
   261  	var counter atomic.Int32
   262  	var wg = sync.WaitGroup{}
   263  
   264  	const n = 1000
   265  
   266  	wg.Add(n)
   267  	fn := func(_ string, msg interface{}) {
   268  		//fmt.Println("msg", msg)
   269  		counter.Inc()
   270  		wg.Done()
   271  	}
   272  
   273  	for i := 0; i < n; i++ {
   274  		_ = bus.Subscribe(fmt.Sprintf("foo/bar/%d", i), fn)
   275  	}
   276  
   277  L:
   278  	stat, total, err := bus.Stat(context.Background(), 1000, 0, "", "")
   279  	require.NoError(t, err)
   280  
   281  	if total < 1000 {
   282  		goto L
   283  	}
   284  
   285  	require.NoError(t, err)
   286  	require.Equal(t, len(stat), n)
   287  	require.Equal(t, total, int64(n))
   288  	require.Equal(t, counter.Load(), int32(0))
   289  
   290  	counter.Store(0)
   291  
   292  	for i := 0; i < n; i++ {
   293  		bus.Publish(fmt.Sprintf("foo/bar/%d", i), i)
   294  	}
   295  
   296  L2:
   297  	if counter.Load() < 1000 {
   298  		time.Sleep(time.Second)
   299  		goto L2
   300  	}
   301  
   302  	for i := 0; i < n; i++ {
   303  		_ = bus.Unsubscribe(fmt.Sprintf("foo/bar/%d", i), fn)
   304  	}
   305  
   306  L3:
   307  	stat, total, err = bus.Stat(context.Background(), 1000, 0, "", "")
   308  	require.NoError(t, err)
   309  
   310  	if total != 0 {
   311  		goto L3
   312  	}
   313  
   314  	require.NoError(t, err)
   315  	require.Equal(t, len(stat), 0)
   316  	require.Equal(t, total, int64(0))
   317  	require.Equal(t, counter.Load(), int32(n))
   318  
   319  	counter.Store(0)
   320  
   321  	for i := 0; i < n; i++ {
   322  		bus.Publish(fmt.Sprintf("foo/bar/%d", i), i)
   323  	}
   324  
   325  	time.Sleep(time.Second * 5)
   326  
   327  	require.True(t, counter.Load() == 0)
   328  }
   329  
   330  func TestBus4(t *testing.T) {
   331  
   332  	bus := NewBus()
   333  
   334  	var counter1 atomic.Int32
   335  	var counter2 atomic.Int32
   336  	var wg1 = sync.WaitGroup{}
   337  	var wg2 = sync.WaitGroup{}
   338  
   339  	const n = 1
   340  
   341  	wg1.Add(n)
   342  	fn1 := func(_ string, msg interface{}) {
   343  		//fmt.Println("msg", msg)
   344  		counter1.Inc()
   345  		wg1.Done()
   346  	}
   347  	wg2.Add(n)
   348  	fn2 := func(_ string, msg interface{}) {
   349  		//fmt.Println("msg", msg)
   350  		counter2.Inc()
   351  		wg2.Done()
   352  	}
   353  
   354  	for i := 0; i < n; i++ {
   355  		_ = bus.Subscribe(fmt.Sprintf("foo/bar/%d", i), fn1)
   356  		_ = bus.Subscribe(fmt.Sprintf("foo/bar/%d", i), fn2)
   357  	}
   358  
   359  	time.Sleep(time.Second)
   360  
   361  	stat, total, err := bus.Stat(context.Background(), 999, 0, "", "")
   362  	require.NoError(t, err)
   363  	require.Equal(t, len(stat), n)
   364  	require.Equal(t, total, int64(n))
   365  	require.Equal(t, counter1.Load(), int32(0))
   366  	require.Equal(t, counter2.Load(), int32(0))
   367  
   368  	for i := 0; i < n; i++ {
   369  		bus.Publish(fmt.Sprintf("foo/bar/%d", i), i)
   370  	}
   371  
   372  	wg1.Wait()
   373  	wg2.Wait()
   374  
   375  	require.Equal(t, counter1.Load(), int32(n))
   376  	require.Equal(t, counter2.Load(), int32(n))
   377  
   378  	for i := 0; i < n; i++ {
   379  		_ = bus.Unsubscribe(fmt.Sprintf("foo/bar/%d", i), fn1)
   380  	}
   381  	time.Sleep(time.Second)
   382  
   383  	stat, total, err = bus.Stat(context.Background(), 999, 0, "", "")
   384  	require.NoError(t, err)
   385  	require.Equal(t, len(stat), n)
   386  	require.Equal(t, total, int64(n))
   387  
   388  	wg2.Add(n)
   389  	for i := 0; i < n; i++ {
   390  		bus.Publish(fmt.Sprintf("foo/bar/%d", i), i)
   391  	}
   392  
   393  	wg2.Wait()
   394  
   395  	require.Equal(t, counter1.Load(), int32(n))
   396  	require.Equal(t, counter2.Load(), int32(n*2))
   397  
   398  	stat, total, err = bus.Stat(context.Background(), 999, 0, "", "")
   399  	require.NoError(t, err)
   400  	require.Equal(t, len(stat), n)
   401  	require.Equal(t, total, int64(n))
   402  }
   403  
   404  func BenchmarkBus(b *testing.B) {
   405  
   406  	const topic = "test/topic"
   407  
   408  	bus := NewBus()
   409  
   410  	var counter atomic.Int32
   411  
   412  	// Test Subscribe
   413  	fn := func(topic string, arg1 string, arg2 string) {
   414  		counter.Inc()
   415  	}
   416  	err := bus.Subscribe(topic, fn)
   417  	require.NoError(b, err)
   418  
   419  	b.ResetTimer()
   420  	for i := 0; i < b.N; i++ {
   421  		bus.Publish(topic, "hello", "world")
   422  	}
   423  
   424  	time.Sleep(time.Second)
   425  
   426  	require.Equal(b, int32(b.N), counter.Load())
   427  }