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 }