github.com/erda-project/erda-infra@v1.0.9/base/servicehub/hub_test.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package servicehub 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "sort" 22 "strings" 23 "sync" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 28 "github.com/erda-project/erda-infra/base/logs" 29 ) 30 31 type testBaseProvider struct{} 32 33 type testInitProvider struct { 34 initialized chan interface{} 35 } 36 37 func (p *testInitProvider) Init(ctx Context) error { 38 p.initialized <- nil 39 return nil 40 } 41 42 type testRunProvider struct { 43 running chan interface{} 44 exited chan interface{} 45 } 46 47 func (p *testRunProvider) Run(ctx context.Context) error { 48 for { 49 select { 50 case p.running <- nil: 51 case <-ctx.Done(): 52 p.exited <- nil 53 return nil 54 } 55 } 56 } 57 58 type testStartProvider struct { 59 started chan interface{} 60 closed chan interface{} 61 } 62 63 func (p *testStartProvider) Start() error { 64 p.started <- nil 65 return nil 66 } 67 68 func (p *testStartProvider) Close() error { 69 p.closed <- nil 70 return nil 71 } 72 73 type testDefine struct { 74 name string 75 spec *Spec 76 } 77 78 func testProviderName(name string) string { return "hub-" + name + "-provider" } 79 80 func testRegister(name string, deps []string, optdeps []string, creator func() Provider) testDefine { 81 if creator == nil { 82 creator = func() Provider { 83 return struct{}{} 84 } 85 } 86 return testDefine{ 87 testProviderName(name), 88 &Spec{ 89 Services: []string{name}, 90 Dependencies: deps, 91 OptionalDependencies: optdeps, 92 Creator: creator, 93 }, 94 } 95 } 96 97 func testContent(names ...string) string { 98 sb := strings.Builder{} 99 for _, name := range names { 100 sb.WriteString(testProviderName(name) + ":\n") 101 } 102 return sb.String() 103 } 104 105 func TestHub(t *testing.T) { 106 type testItem struct { 107 d testDefine 108 wait func() 109 check func() error 110 closeWait func() 111 } 112 type testFunc func() *testItem 113 114 runProvider := func(name string) func() *testItem { 115 return func() *testItem { 116 createCh := make(chan interface{}, 1) 117 configCh := make(chan interface{}, 1) 118 initCh := make(chan interface{}, 1) 119 startCh := make(chan interface{}, 1) 120 closeCh := make(chan interface{}, 1) 121 runCh := make(chan interface{}, 1) 122 exitCh := make(chan interface{}, 1) 123 p := &struct { 124 testInitProvider 125 testStartProvider 126 testRunProvider 127 }{ 128 testInitProvider: testInitProvider{initCh}, 129 testStartProvider: testStartProvider{startCh, closeCh}, 130 testRunProvider: testRunProvider{runCh, exitCh}, 131 } 132 item := &testItem{ 133 d: testDefine{ 134 testProviderName(name), 135 &Spec{ 136 ConfigFunc: func() interface{} { 137 configCh <- nil 138 return nil 139 }, 140 Creator: func() Provider { 141 createCh <- p 142 return p 143 }, 144 }, 145 }, 146 wait: func() { 147 <-createCh 148 <-configCh 149 <-p.initialized 150 <-p.started 151 <-p.running 152 }, 153 closeWait: func() { 154 <-p.closed 155 <-p.exited 156 }, 157 } 158 return item 159 } 160 } 161 tests := []struct { 162 name string 163 funcs []testFunc 164 content string 165 }{ 166 { 167 name: "empty", 168 funcs: []testFunc{ 169 func() *testItem { 170 createCh := make(chan interface{}) 171 item := &testItem{ 172 d: testDefine{ 173 testProviderName("test1"), 174 &Spec{ 175 Creator: func() Provider { 176 p := &testBaseProvider{} 177 createCh <- p 178 return p 179 }, 180 }, 181 }, 182 wait: func() { 183 <-createCh 184 }, 185 } 186 return item 187 }, 188 }, 189 content: testContent("test1"), 190 }, 191 { 192 name: "config", 193 funcs: []testFunc{ 194 func() *testItem { 195 createCh := make(chan interface{}) 196 configCh := make(chan interface{}) 197 item := &testItem{ 198 d: testDefine{ 199 testProviderName("test1"), 200 &Spec{ 201 ConfigFunc: func() interface{} { 202 configCh <- nil 203 return nil 204 }, 205 Creator: func() Provider { 206 p := &testBaseProvider{} 207 createCh <- p 208 return p 209 }, 210 }, 211 }, 212 wait: func() { 213 <-createCh 214 <-configCh 215 }, 216 } 217 return item 218 }, 219 }, 220 content: testContent("test1"), 221 }, 222 { 223 name: "init", 224 funcs: []testFunc{ 225 func() *testItem { 226 createCh := make(chan interface{}) 227 configCh := make(chan interface{}) 228 initCh := make(chan interface{}) 229 p := &testInitProvider{initCh} 230 item := &testItem{ 231 d: testDefine{ 232 testProviderName("test1"), 233 &Spec{ 234 ConfigFunc: func() interface{} { 235 configCh <- nil 236 return nil 237 }, 238 Creator: func() Provider { 239 createCh <- p 240 return p 241 }, 242 }, 243 }, 244 wait: func() { 245 <-createCh 246 <-configCh 247 <-p.initialized 248 }, 249 } 250 return item 251 }, 252 }, 253 content: testContent("test1"), 254 }, 255 { 256 name: "start", 257 funcs: []testFunc{ 258 func() *testItem { 259 createCh := make(chan interface{}) 260 configCh := make(chan interface{}) 261 initCh := make(chan interface{}) 262 startCh := make(chan interface{}) 263 closeCh := make(chan interface{}) 264 p := &struct { 265 testInitProvider 266 testStartProvider 267 }{ 268 testInitProvider: testInitProvider{initCh}, 269 testStartProvider: testStartProvider{startCh, closeCh}, 270 } 271 item := &testItem{ 272 d: testDefine{ 273 testProviderName("test1"), 274 &Spec{ 275 ConfigFunc: func() interface{} { 276 configCh <- nil 277 return nil 278 }, 279 Creator: func() Provider { 280 createCh <- p 281 return p 282 }, 283 }, 284 }, 285 wait: func() { 286 <-createCh 287 <-configCh 288 <-p.initialized 289 <-p.started 290 }, 291 closeWait: func() { 292 <-p.closed 293 }, 294 } 295 return item 296 }, 297 }, 298 content: testContent("test1"), 299 }, 300 { 301 name: "run", 302 funcs: []testFunc{ 303 runProvider("test1"), 304 }, 305 content: testContent("test1"), 306 }, 307 { 308 name: "run many", 309 funcs: []testFunc{ 310 runProvider("test1"), 311 runProvider("test2"), 312 runProvider("test3"), 313 }, 314 content: testContent("test1", "test2", "test3"), 315 }, 316 { 317 name: "check config and log", 318 funcs: []testFunc{ 319 func() *testItem { 320 type config struct { 321 Name string `default:"name1"` 322 } 323 type provider struct { 324 Cfg *config 325 Log logs.Logger 326 testInitProvider 327 } 328 initCh := make(chan interface{}) 329 cfg := &config{} 330 p := &provider{testInitProvider: testInitProvider{initCh}} 331 item := &testItem{ 332 d: testDefine{ 333 testProviderName("test1"), 334 &Spec{ 335 ConfigFunc: func() interface{} { return cfg }, 336 Creator: func() Provider { return p }, 337 }, 338 }, 339 wait: func() { 340 <-initCh 341 }, 342 check: func() error { 343 if p.Cfg != cfg { 344 return fmt.Errorf("config field not match") 345 } 346 if p.Cfg.Name != "name1" { 347 return fmt.Errorf("invalid config value, want config.name=%q, but got %q", "name1", p.Cfg.Name) 348 } 349 if p.Log == nil { 350 return fmt.Errorf("log field not setup") 351 } 352 return nil 353 }, 354 } 355 return item 356 }, 357 }, 358 content: testContent("test1"), 359 }, 360 } 361 for _, tt := range tests { 362 t.Run(tt.name, func(t *testing.T) { 363 var list []*testItem 364 for _, fn := range tt.funcs { 365 item := fn() 366 list = append(list, item) 367 Register(item.d.name, item.d.spec) 368 } 369 hub := New() 370 go func() { 371 hub.RunWithOptions(&RunOptions{Content: tt.content}) 372 }() 373 for _, item := range list { 374 item.wait() 375 } 376 for _, item := range list { 377 if item.check != nil { 378 err := item.check() 379 if err != nil { 380 t.Errorf("check provider error: %v", err) 381 } 382 } 383 } 384 wg := &sync.WaitGroup{} 385 for _, item := range list { 386 if item.closeWait != nil { 387 wg.Add(1) 388 go func(item *testItem) { 389 defer wg.Done() 390 item.closeWait() 391 }(item) 392 } 393 } 394 if err := hub.Close(); err != nil { 395 t.Errorf("Hub.Close() = %v, want nil", err) 396 } 397 wg.Wait() 398 for _, item := range list { 399 delete(serviceProviders, item.d.name) 400 } 401 }) 402 } 403 } 404 405 func TestHub_Dependencies(t *testing.T) { 406 tests := []struct { 407 name string 408 providers []testDefine 409 content string 410 hasErr bool 411 }{ 412 { 413 name: "Dependencies", 414 providers: []testDefine{ 415 testRegister("test1", nil, nil, nil), 416 testRegister("test2", []string{"test1"}, nil, nil), 417 }, 418 content: testContent("test1", "test2"), 419 }, 420 { 421 name: "Miss Dependencies", 422 providers: []testDefine{ 423 testRegister("test1", nil, nil, nil), 424 testRegister("test2", []string{"test1"}, nil, nil), 425 }, 426 content: testContent("test2"), 427 hasErr: true, 428 }, 429 { 430 name: "Dependencies And Optional Dependencies", 431 providers: []testDefine{ 432 testRegister("test1", nil, nil, nil), 433 testRegister("test2", []string{"test1"}, nil, nil), 434 testRegister("test3", []string{"test1"}, []string{"test2", "test4"}, nil), 435 }, 436 content: testContent("test1", "test2", "test3"), 437 }, 438 { 439 name: "Optional Dependencies", 440 providers: []testDefine{ 441 testRegister("test1", nil, nil, nil), 442 testRegister("test3", nil, []string{"test2", "test4"}, nil), 443 }, 444 content: testContent("test1", "test3"), 445 }, 446 { 447 name: "Circular Dependency", 448 providers: []testDefine{ 449 testRegister("test1", nil, nil, nil), 450 testRegister("test2", nil, nil, nil), 451 testRegister("test3", []string{"test2", "test4"}, nil, nil), 452 testRegister("test4", []string{"test3"}, nil, nil), 453 }, 454 content: testContent("test1", "testt2", "test3", "test4"), 455 hasErr: true, 456 }, 457 } 458 for _, tt := range tests { 459 t.Run(tt.name, func(t *testing.T) { 460 for _, p := range tt.providers { 461 Register(p.name, p.spec) 462 } 463 hub := New() 464 events := hub.Events() 465 go func() { 466 hub.RunWithOptions(&RunOptions{Content: tt.content}) 467 }() 468 err := <-events.Initialized() 469 if (err != nil) != tt.hasErr { 470 if tt.hasErr { 471 t.Errorf("got error %q, want err != nil", err) 472 } else { 473 t.Errorf("got error %q, want err == nil", err) 474 } 475 } 476 if err := hub.Close(); err != nil { 477 t.Errorf("Hub.Close() = %v, want nil", err) 478 } 479 for _, p := range tt.providers { 480 delete(serviceProviders, p.name) 481 } 482 }) 483 } 484 } 485 486 func Test_boolTagValue(t *testing.T) { 487 type args struct { 488 tag reflect.StructTag 489 key string 490 defval bool 491 } 492 tests := []struct { 493 name string 494 args args 495 want bool 496 wantErr bool 497 }{ 498 { 499 args: args{ 500 tag: reflect.StructTag(`test-key:""`), 501 key: "test-key", 502 defval: false, 503 }, 504 want: false, 505 }, 506 { 507 args: args{ 508 tag: reflect.StructTag(`test-key:""`), 509 key: "test-key", 510 defval: true, 511 }, 512 want: true, 513 }, 514 { 515 args: args{ 516 tag: reflect.StructTag(``), 517 key: "test-key", 518 defval: true, 519 }, 520 want: true, 521 }, 522 { 523 args: args{ 524 tag: reflect.StructTag(`test-key:"true"`), 525 key: "test-key", 526 defval: false, 527 }, 528 want: true, 529 }, 530 { 531 args: args{ 532 tag: reflect.StructTag(`test-key:"false"`), 533 key: "test-key", 534 defval: true, 535 }, 536 want: false, 537 }, 538 { 539 args: args{ 540 tag: reflect.StructTag(`test-key:"error"`), 541 key: "test-key", 542 defval: true, 543 }, 544 want: true, 545 wantErr: true, 546 }, 547 } 548 for _, tt := range tests { 549 t.Run(tt.name, func(t *testing.T) { 550 got, err := boolTagValue(tt.args.tag, tt.args.key, tt.args.defval) 551 if (err != nil) != tt.wantErr { 552 t.Errorf("boolTagValue() error = %v, wantErr %v", err, tt.wantErr) 553 return 554 } 555 if got != tt.want { 556 t.Errorf("boolTagValue() = %v, want %v", got, tt.want) 557 } 558 }) 559 } 560 } 561 562 func TestHub_addProblematicProvider(t *testing.T) { 563 h := Hub{} 564 assert.Equal(t, 0, len(h.problematicProviderNames)) 565 someProviders := []string{"p2", "p1", "p3"} 566 var wg sync.WaitGroup 567 for _, p := range someProviders { 568 wg.Add(1) 569 go func(p string) { 570 defer wg.Done() 571 h.addProblematicProvider(p) 572 }(p) 573 } 574 wg.Wait() 575 assert.Equal(t, 3, len(h.problematicProviderNames)) 576 sort.Strings(h.problematicProviderNames) 577 assert.Equal(t, []string{"p1", "p2", "p3"}, h.problematicProviderNames) 578 }