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  }