github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/store/storetest/store_test_lib.go (about)

     1  package storetest
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"regexp"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/go-sql-driver/mysql"
    11  	"github.com/lib/pq"
    12  	"github.com/masterhung0112/hk_server/v5/model"
    13  	"github.com/masterhung0112/hk_server/v5/store"
    14  	"github.com/masterhung0112/hk_server/v5/store/searchtest"
    15  	"github.com/masterhung0112/hk_server/v5/store/sqlstore"
    16  	"github.com/mattermost/gorp"
    17  	"github.com/pkg/errors"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"github.com/stretchr/testify/suite"
    21  
    22  	_ "github.com/mattn/go-sqlite3"
    23  )
    24  
    25  type SqlStore interface {
    26  	GetMaster() *gorp.DbMap
    27  	DriverName() string
    28  }
    29  
    30  type StoreType struct {
    31  	Name        string
    32  	SqlSettings *model.SqlSettings
    33  	SqlStore    SqlStore
    34  	Store       store.Store
    35  }
    36  
    37  var StoreTypes []*StoreType = []*StoreType{}
    38  
    39  func newStoreType(name, driver string) *StoreType {
    40  	return &StoreType{
    41  		Name:        name,
    42  		SqlSettings: MakeSqlSettings(driver, false),
    43  	}
    44  }
    45  
    46  func StoreTest(t *testing.T, f func(*testing.T, store.Store)) {
    47  	defer func() {
    48  		if err := recover(); err != nil {
    49  			tearDownStores()
    50  			panic(err)
    51  		}
    52  	}()
    53  	for _, st := range StoreTypes {
    54  		st := st
    55  		t.Run(st.Name, func(t *testing.T) {
    56  			if testing.Short() {
    57  				t.SkipNow()
    58  			}
    59  			f(t, st.Store)
    60  		})
    61  	}
    62  }
    63  
    64  func StoreTestWithSearchTestEngine(t *testing.T, f func(*testing.T, store.Store, *searchtest.SearchTestEngine)) {
    65  	defer func() {
    66  		if err := recover(); err != nil {
    67  			tearDownStores()
    68  			panic(err)
    69  		}
    70  	}()
    71  
    72  	for _, st := range StoreTypes {
    73  		st := st
    74  		searchTestEngine := &searchtest.SearchTestEngine{
    75  			Driver: *st.SqlSettings.DriverName,
    76  		}
    77  
    78  		t.Run(st.Name, func(t *testing.T) { f(t, st.Store, searchTestEngine) })
    79  	}
    80  }
    81  
    82  func StoreTestWithSqlStore(t *testing.T, f func(*testing.T, store.Store, SqlStore)) {
    83  	defer func() {
    84  		if err := recover(); err != nil {
    85  			tearDownStores()
    86  			panic(err)
    87  		}
    88  	}()
    89  	for _, st := range StoreTypes {
    90  		st := st
    91  		t.Run(st.Name, func(t *testing.T) {
    92  			if testing.Short() {
    93  				t.SkipNow()
    94  			}
    95  			f(t, st.Store, st.SqlStore)
    96  		})
    97  	}
    98  }
    99  
   100  func initStores() {
   101  	if testing.Short() {
   102  		return
   103  	}
   104  	// In CI, we already run the entire test suite for both mysql and postgres in parallel.
   105  	// So we just run the tests for the current database set.
   106  	if os.Getenv("IS_CI") == "true" {
   107  		switch os.Getenv("MM_SQLSETTINGS_DRIVERNAME") {
   108  		case "mysql":
   109  			StoreTypes = append(StoreTypes, newStoreType("MySQL", model.DATABASE_DRIVER_MYSQL))
   110  		case "postgres":
   111  			StoreTypes = append(StoreTypes, newStoreType("PostgreSQL", model.DATABASE_DRIVER_POSTGRES))
   112  		}
   113  	} else {
   114  		StoreTypes = append(StoreTypes, newStoreType("MySQL", model.DATABASE_DRIVER_MYSQL),
   115  			newStoreType("PostgreSQL", model.DATABASE_DRIVER_POSTGRES))
   116  	}
   117  
   118  	defer func() {
   119  		if err := recover(); err != nil {
   120  			tearDownStores()
   121  			panic(err)
   122  		}
   123  	}()
   124  	var wg sync.WaitGroup
   125  	for _, st := range StoreTypes {
   126  		st := st
   127  		wg.Add(1)
   128  		go func() {
   129  			defer wg.Done()
   130  			sqlStore := sqlstore.New(*st.SqlSettings, nil)
   131  			st.Store = sqlStore
   132  			st.SqlStore = sqlStore
   133  			st.Store.DropAllTables()
   134  			st.Store.MarkSystemRanUnitTests()
   135  		}()
   136  	}
   137  	wg.Wait()
   138  }
   139  
   140  var tearDownStoresOnce sync.Once
   141  
   142  func tearDownStores() {
   143  	if testing.Short() {
   144  		return
   145  	}
   146  	tearDownStoresOnce.Do(func() {
   147  		var wg sync.WaitGroup
   148  		wg.Add(len(StoreTypes))
   149  		for _, st := range StoreTypes {
   150  			st := st
   151  			go func() {
   152  				if st.Store != nil {
   153  					st.Store.Close()
   154  				}
   155  				if st.SqlSettings != nil {
   156  					CleanupSqlSettings(st.SqlSettings)
   157  				}
   158  				wg.Done()
   159  			}()
   160  		}
   161  		wg.Wait()
   162  	})
   163  }
   164  
   165  // This test was used to consistently reproduce the race
   166  // before the fix in MM-28397.
   167  // Keeping it here to help avoiding future regressions.
   168  func TestStoreLicenseRace(t *testing.T) {
   169  	settings := makeSqlSettings(model.DATABASE_DRIVER_POSTGRES)
   170  	store := sqlstore.New(*settings, nil)
   171  
   172  	wg := sync.WaitGroup{}
   173  	wg.Add(3)
   174  
   175  	go func() {
   176  		store.UpdateLicense(&model.License{})
   177  		wg.Done()
   178  	}()
   179  
   180  	go func() {
   181  		store.GetReplica()
   182  		wg.Done()
   183  	}()
   184  
   185  	go func() {
   186  		store.GetSearchReplica()
   187  		wg.Done()
   188  	}()
   189  
   190  	wg.Wait()
   191  }
   192  
   193  func TestGetReplica(t *testing.T) {
   194  	t.Parallel()
   195  	testCases := []struct {
   196  		Description                string
   197  		DataSourceReplicaNum       int
   198  		DataSourceSearchReplicaNum int
   199  	}{
   200  		{
   201  			"no replicas",
   202  			0,
   203  			0,
   204  		},
   205  		{
   206  			"one source replica",
   207  			1,
   208  			0,
   209  		},
   210  		{
   211  			"multiple source replicas",
   212  			3,
   213  			0,
   214  		},
   215  		{
   216  			"one source search replica",
   217  			0,
   218  			1,
   219  		},
   220  		{
   221  			"multiple source search replicas",
   222  			0,
   223  			3,
   224  		},
   225  		{
   226  			"one source replica, one source search replica",
   227  			1,
   228  			1,
   229  		},
   230  		{
   231  			"one source replica, multiple source search replicas",
   232  			1,
   233  			3,
   234  		},
   235  		{
   236  			"multiple source replica, one source search replica",
   237  			3,
   238  			1,
   239  		},
   240  		{
   241  			"multiple source replica, multiple source search replicas",
   242  			3,
   243  			3,
   244  		},
   245  	}
   246  
   247  	for _, testCase := range testCases {
   248  		testCase := testCase
   249  		t.Run(testCase.Description+" with license", func(t *testing.T) {
   250  			t.Parallel()
   251  
   252  			settings := makeSqlSettings(model.DATABASE_DRIVER_POSTGRES)
   253  			dataSourceReplicas := []string{}
   254  			dataSourceSearchReplicas := []string{}
   255  			for i := 0; i < testCase.DataSourceReplicaNum; i++ {
   256  				dataSourceReplicas = append(dataSourceReplicas, *settings.DataSource)
   257  			}
   258  			for i := 0; i < testCase.DataSourceSearchReplicaNum; i++ {
   259  				dataSourceSearchReplicas = append(dataSourceSearchReplicas, *settings.DataSource)
   260  			}
   261  
   262  			settings.DataSourceReplicas = dataSourceReplicas
   263  			settings.DataSourceSearchReplicas = dataSourceSearchReplicas
   264  			store := sqlstore.New(*settings, nil)
   265  			defer func() {
   266  				store.Close()
   267  				CleanupSqlSettings(settings)
   268  			}()
   269  
   270  			store.UpdateLicense(&model.License{})
   271  
   272  			replicas := make(map[*gorp.DbMap]bool)
   273  			for i := 0; i < 5; i++ {
   274  				replicas[store.GetReplica()] = true
   275  			}
   276  
   277  			searchReplicas := make(map[*gorp.DbMap]bool)
   278  			for i := 0; i < 5; i++ {
   279  				searchReplicas[store.GetSearchReplica()] = true
   280  			}
   281  
   282  			if testCase.DataSourceReplicaNum > 0 {
   283  				// If replicas were defined, ensure none are the master.
   284  				assert.Len(t, replicas, testCase.DataSourceReplicaNum)
   285  
   286  				for replica := range replicas {
   287  					assert.NotSame(t, store.GetMaster(), replica)
   288  				}
   289  
   290  			} else if assert.Len(t, replicas, 1) {
   291  				// Otherwise ensure the replicas contains only the master.
   292  				for replica := range replicas {
   293  					assert.Same(t, store.GetMaster(), replica)
   294  				}
   295  			}
   296  
   297  			if testCase.DataSourceSearchReplicaNum > 0 {
   298  				// If search replicas were defined, ensure none are the master nor the replicas.
   299  				assert.Len(t, searchReplicas, testCase.DataSourceSearchReplicaNum)
   300  
   301  				for searchReplica := range searchReplicas {
   302  					assert.NotSame(t, store.GetMaster(), searchReplica)
   303  					for replica := range replicas {
   304  						assert.NotSame(t, searchReplica, replica)
   305  					}
   306  				}
   307  			} else if testCase.DataSourceReplicaNum > 0 {
   308  				assert.Equal(t, len(replicas), len(searchReplicas))
   309  				for k := range replicas {
   310  					assert.True(t, searchReplicas[k])
   311  				}
   312  			} else if testCase.DataSourceReplicaNum == 0 && assert.Len(t, searchReplicas, 1) {
   313  				// Otherwise ensure the search replicas contains the master.
   314  				for searchReplica := range searchReplicas {
   315  					assert.Same(t, store.GetMaster(), searchReplica)
   316  				}
   317  			}
   318  		})
   319  
   320  		t.Run(testCase.Description+" without license", func(t *testing.T) {
   321  			t.Parallel()
   322  
   323  			settings := makeSqlSettings(model.DATABASE_DRIVER_POSTGRES)
   324  			dataSourceReplicas := []string{}
   325  			dataSourceSearchReplicas := []string{}
   326  			for i := 0; i < testCase.DataSourceReplicaNum; i++ {
   327  				dataSourceReplicas = append(dataSourceReplicas, *settings.DataSource)
   328  			}
   329  			for i := 0; i < testCase.DataSourceSearchReplicaNum; i++ {
   330  				dataSourceSearchReplicas = append(dataSourceSearchReplicas, *settings.DataSource)
   331  			}
   332  
   333  			settings.DataSourceReplicas = dataSourceReplicas
   334  			settings.DataSourceSearchReplicas = dataSourceSearchReplicas
   335  			store := sqlstore.New(*settings, nil)
   336  			defer func() {
   337  				store.Close()
   338  				CleanupSqlSettings(settings)
   339  			}()
   340  
   341  			replicas := make(map[*gorp.DbMap]bool)
   342  			for i := 0; i < 5; i++ {
   343  				replicas[store.GetReplica()] = true
   344  			}
   345  
   346  			searchReplicas := make(map[*gorp.DbMap]bool)
   347  			for i := 0; i < 5; i++ {
   348  				searchReplicas[store.GetSearchReplica()] = true
   349  			}
   350  
   351  			if testCase.DataSourceReplicaNum > 0 {
   352  				// If replicas were defined, ensure none are the master.
   353  				assert.Len(t, replicas, 1)
   354  
   355  				for replica := range replicas {
   356  					assert.Same(t, store.GetMaster(), replica)
   357  				}
   358  
   359  			} else if assert.Len(t, replicas, 1) {
   360  				// Otherwise ensure the replicas contains only the master.
   361  				for replica := range replicas {
   362  					assert.Same(t, store.GetMaster(), replica)
   363  				}
   364  			}
   365  
   366  			if testCase.DataSourceSearchReplicaNum > 0 {
   367  				// If search replicas were defined, ensure none are the master nor the replicas.
   368  				assert.Len(t, searchReplicas, 1)
   369  
   370  				for searchReplica := range searchReplicas {
   371  					assert.Same(t, store.GetMaster(), searchReplica)
   372  				}
   373  
   374  			} else if testCase.DataSourceReplicaNum > 0 {
   375  				assert.Equal(t, len(replicas), len(searchReplicas))
   376  				for k := range replicas {
   377  					assert.True(t, searchReplicas[k])
   378  				}
   379  			} else if assert.Len(t, searchReplicas, 1) {
   380  				// Otherwise ensure the search replicas contains the master.
   381  				for searchReplica := range searchReplicas {
   382  					assert.Same(t, store.GetMaster(), searchReplica)
   383  				}
   384  			}
   385  		})
   386  	}
   387  }
   388  
   389  func TestGetDbVersion(t *testing.T) {
   390  	testDrivers := []string{
   391  		model.DATABASE_DRIVER_POSTGRES,
   392  		model.DATABASE_DRIVER_MYSQL,
   393  	}
   394  
   395  	for _, driver := range testDrivers {
   396  		t.Run("Should return db version for "+driver, func(t *testing.T) {
   397  			t.Parallel()
   398  			settings := makeSqlSettings(driver)
   399  			store := sqlstore.New(*settings, nil)
   400  
   401  			version, err := store.GetDbVersion(false)
   402  			require.Nil(t, err)
   403  			require.Regexp(t, regexp.MustCompile(`\d+\.\d+(\.\d+)?`), version)
   404  		})
   405  	}
   406  }
   407  
   408  func TestGetAllConns(t *testing.T) {
   409  	t.Parallel()
   410  	testCases := []struct {
   411  		Description                string
   412  		DataSourceReplicaNum       int
   413  		DataSourceSearchReplicaNum int
   414  		ExpectedNumConnections     int
   415  	}{
   416  		{
   417  			"no replicas",
   418  			0,
   419  			0,
   420  			1,
   421  		},
   422  		{
   423  			"one source replica",
   424  			1,
   425  			0,
   426  			2,
   427  		},
   428  		{
   429  			"multiple source replicas",
   430  			3,
   431  			0,
   432  			4,
   433  		},
   434  		{
   435  			"one source search replica",
   436  			0,
   437  			1,
   438  			1,
   439  		},
   440  		{
   441  			"multiple source search replicas",
   442  			0,
   443  			3,
   444  			1,
   445  		},
   446  		{
   447  			"one source replica, one source search replica",
   448  			1,
   449  			1,
   450  			2,
   451  		},
   452  		{
   453  			"one source replica, multiple source search replicas",
   454  			1,
   455  			3,
   456  			2,
   457  		},
   458  		{
   459  			"multiple source replica, one source search replica",
   460  			3,
   461  			1,
   462  			4,
   463  		},
   464  		{
   465  			"multiple source replica, multiple source search replicas",
   466  			3,
   467  			3,
   468  			4,
   469  		},
   470  	}
   471  
   472  	for _, testCase := range testCases {
   473  		testCase := testCase
   474  		t.Run(testCase.Description, func(t *testing.T) {
   475  			t.Parallel()
   476  			settings := makeSqlSettings(model.DATABASE_DRIVER_POSTGRES)
   477  			dataSourceReplicas := []string{}
   478  			dataSourceSearchReplicas := []string{}
   479  			for i := 0; i < testCase.DataSourceReplicaNum; i++ {
   480  				dataSourceReplicas = append(dataSourceReplicas, *settings.DataSource)
   481  			}
   482  			for i := 0; i < testCase.DataSourceSearchReplicaNum; i++ {
   483  				dataSourceSearchReplicas = append(dataSourceSearchReplicas, *settings.DataSource)
   484  			}
   485  
   486  			settings.DataSourceReplicas = dataSourceReplicas
   487  			settings.DataSourceSearchReplicas = dataSourceSearchReplicas
   488  			store := sqlstore.New(*settings, nil)
   489  			defer func() {
   490  				store.Close()
   491  				CleanupSqlSettings(settings)
   492  			}()
   493  
   494  			assert.Len(t, store.GetAllConns(), testCase.ExpectedNumConnections)
   495  		})
   496  	}
   497  }
   498  
   499  func TestIsDuplicate(t *testing.T) {
   500  	testErrors := map[error]bool{
   501  		&pq.Error{Code: "42P06"}:                                   false,
   502  		&pq.Error{Code: sqlstore.PGDupTableErrorCode}:              true,
   503  		&mysql.MySQLError{Number: uint16(1000)}:                    false,
   504  		&mysql.MySQLError{Number: sqlstore.MySQLDupTableErrorCode}: true,
   505  		errors.New("Random error"):                                 false,
   506  	}
   507  
   508  	for err, expected := range testErrors {
   509  		t.Run(fmt.Sprintf("Should return %t for %s", expected, err.Error()), func(t *testing.T) {
   510  			t.Parallel()
   511  			assert.Equal(t, expected, sqlstore.IsDuplicate(err))
   512  		})
   513  	}
   514  }
   515  
   516  func TestVersionString(t *testing.T) {
   517  	versions := []struct {
   518  		input  int
   519  		output string
   520  	}{
   521  		{
   522  			input:  100000,
   523  			output: "10.0",
   524  		},
   525  		{
   526  			input:  90603,
   527  			output: "9.603",
   528  		},
   529  		{
   530  			input:  120005,
   531  			output: "12.5",
   532  		},
   533  	}
   534  
   535  	for _, v := range versions {
   536  		out := sqlstore.VersionString(v.input)
   537  		assert.Equal(t, v.output, out)
   538  	}
   539  }
   540  
   541  func makeSqlSettings(driver string) *model.SqlSettings {
   542  	switch driver {
   543  	case model.DATABASE_DRIVER_POSTGRES:
   544  		return MakeSqlSettings(driver, false)
   545  	case model.DATABASE_DRIVER_MYSQL:
   546  		return MakeSqlSettings(driver, false)
   547  	}
   548  
   549  	return nil
   550  }
   551  
   552  func StoreTestSuiteWithSqlSupplier(t *testing.T, testSuite StoreTestBaseSuite, executeFunc func(t *testing.T, testSuite StoreTestBaseSuite)) {
   553  	for _, st := range StoreTypes {
   554  		st := st
   555  		t.Run(st.Name, func(t *testing.T) {
   556  			if testing.Short() {
   557  				t.SkipNow()
   558  			}
   559  			testSuite.SetStore(st.Store)
   560  			testSuite.SetSqlStore(st.SqlStore)
   561  			// suite.Run(t, testSuite)
   562  			executeFunc(t, testSuite)
   563  		})
   564  	}
   565  }
   566  
   567  func StoreTestSuiteExecute(t *testing.T, testSuite StoreTestBaseSuite, store store.Store, sqlStore SqlStore) {
   568  	testSuite.SetStore(store)
   569  	testSuite.SetSqlStore(sqlStore)
   570  	suite.Run(t, testSuite)
   571  }