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 }