github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/testlib/helper.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package testlib 5 6 import ( 7 "flag" 8 "fmt" 9 "io/ioutil" 10 "log" 11 "os" 12 "path/filepath" 13 "testing" 14 15 "github.com/pkg/errors" 16 17 "github.com/masterhung0112/hk_server/v5/model" 18 "github.com/masterhung0112/hk_server/v5/services/searchengine" 19 "github.com/masterhung0112/hk_server/v5/shared/mlog" 20 "github.com/masterhung0112/hk_server/v5/store" 21 "github.com/masterhung0112/hk_server/v5/store/searchlayer" 22 "github.com/masterhung0112/hk_server/v5/store/sqlstore" 23 "github.com/masterhung0112/hk_server/v5/store/storetest" 24 "github.com/masterhung0112/hk_server/v5/utils" 25 ) 26 27 type MainHelper struct { 28 Settings *model.SqlSettings 29 Store store.Store 30 SearchEngine *searchengine.Broker 31 SQLStore *sqlstore.SqlStore 32 ClusterInterface *FakeClusterInterface 33 34 status int 35 testResourcePath string 36 replicas []string 37 } 38 39 type HelperOptions struct { 40 EnableStore bool 41 EnableResources bool 42 WithReadReplica bool 43 } 44 45 func NewMainHelper() *MainHelper { 46 return NewMainHelperWithOptions(&HelperOptions{ 47 EnableStore: true, 48 EnableResources: true, 49 }) 50 } 51 52 func NewMainHelperWithOptions(options *HelperOptions) *MainHelper { 53 var mainHelper MainHelper 54 flag.Parse() 55 56 // Setup a global logger to catch tests logging outside of app context 57 // The global logger will be stomped by apps initializing but that's fine for testing. 58 // Ideally this won't happen. 59 mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ 60 EnableConsole: true, 61 ConsoleJson: true, 62 ConsoleLevel: "error", 63 EnableFile: false, 64 })) 65 66 utils.TranslationsPreInit() 67 68 if options != nil { 69 if options.EnableStore && !testing.Short() { 70 mainHelper.setupStore(options.WithReadReplica) 71 } 72 73 if options.EnableResources { 74 mainHelper.setupResources() 75 } 76 } 77 78 return &mainHelper 79 } 80 81 func (h *MainHelper) Main(m *testing.M) { 82 if h.testResourcePath != "" { 83 prevDir, err := os.Getwd() 84 if err != nil { 85 panic("Failed to get current working directory: " + err.Error()) 86 } 87 88 err = os.Chdir(h.testResourcePath) 89 if err != nil { 90 panic(fmt.Sprintf("Failed to set current working directory to %s: %s", h.testResourcePath, err.Error())) 91 } 92 93 defer func() { 94 err := os.Chdir(prevDir) 95 if err != nil { 96 panic(fmt.Sprintf("Failed to restore current working directory to %s: %s", prevDir, err.Error())) 97 } 98 }() 99 } 100 101 h.status = m.Run() 102 } 103 104 func (h *MainHelper) setupStore(withReadReplica bool) { 105 driverName := os.Getenv("MM_SQLSETTINGS_DRIVERNAME") 106 if driverName == "" { 107 driverName = model.DATABASE_DRIVER_POSTGRES 108 } 109 110 h.Settings = storetest.MakeSqlSettings(driverName, withReadReplica) 111 h.replicas = h.Settings.DataSourceReplicas 112 113 config := &model.Config{} 114 config.SetDefaults() 115 116 h.SearchEngine = searchengine.NewBroker(config, nil) 117 h.ClusterInterface = &FakeClusterInterface{} 118 h.SQLStore = sqlstore.New(*h.Settings, nil) 119 h.Store = searchlayer.NewSearchLayer(&TestStore{ 120 h.SQLStore, 121 }, h.SearchEngine, config) 122 } 123 124 func (h *MainHelper) ToggleReplicasOff() { 125 if h.SQLStore.GetLicense() == nil { 126 panic("expecting a license to use this") 127 } 128 h.Settings.DataSourceReplicas = []string{} 129 lic := h.SQLStore.GetLicense() 130 h.SQLStore = sqlstore.New(*h.Settings, nil) 131 h.SQLStore.UpdateLicense(lic) 132 } 133 134 func (h *MainHelper) ToggleReplicasOn() { 135 if h.SQLStore.GetLicense() == nil { 136 panic("expecting a license to use this") 137 } 138 h.Settings.DataSourceReplicas = h.replicas 139 lic := h.SQLStore.GetLicense() 140 h.SQLStore = sqlstore.New(*h.Settings, nil) 141 h.SQLStore.UpdateLicense(lic) 142 } 143 144 func (h *MainHelper) setupResources() { 145 var err error 146 h.testResourcePath, err = SetupTestResources() 147 if err != nil { 148 panic("failed to setup test resources: " + err.Error()) 149 } 150 } 151 152 // PreloadMigrations preloads the migrations and roles into the database 153 // so that they are not run again when the migrations happen every time 154 // the server is started. 155 // This change is forward-compatible with new migrations and only new migrations 156 // will get executed. 157 // Only if the schema of either roles or systems table changes, this will break. 158 // In that case, just update the migrations or comment this out for the time being. 159 // In the worst case, only an optimization is lost. 160 // 161 // Re-generate the files with: 162 // pg_dump -a -h localhost -U mmuser -d <> --no-comments --inserts -t roles -t systems 163 // mysqldump -u root -p <> --no-create-info --extended-insert=FALSE Systems Roles 164 // And keep only the permission related rows in the systems table output. 165 func (h *MainHelper) PreloadMigrations() { 166 var buf []byte 167 var err error 168 basePath := os.Getenv("HK_SERVER_PATH") 169 relPath := "testlib/testdata" 170 switch *h.Settings.DriverName { 171 case model.DATABASE_DRIVER_POSTGRES: 172 var finalPath string 173 if basePath != "" { 174 finalPath = filepath.Join(basePath, relPath, "postgres_migration_warmup.sql") 175 } else { 176 // finalPath = filepath.Join("hk_server", relPath, "postgres_migration_warmup.sql") 177 finalPath = filepath.Join(relPath, "postgres_migration_warmup.sql") 178 } 179 buf, err = ioutil.ReadFile(finalPath) 180 if err != nil { 181 panic(fmt.Errorf("cannot read file: %v", err)) 182 } 183 case model.DATABASE_DRIVER_MYSQL: 184 var finalPath string 185 if basePath != "" { 186 finalPath = filepath.Join(basePath, relPath, "mysql_migration_warmup.sql") 187 } else { 188 finalPath = filepath.Join(relPath, "mysql_migration_warmup.sql") 189 } 190 buf, err = ioutil.ReadFile(finalPath) 191 if err != nil { 192 panic(fmt.Errorf("cannot read file: %v", err)) 193 } 194 } 195 handle := h.SQLStore.GetMaster() 196 _, err = handle.Exec(string(buf)) 197 if err != nil { 198 panic(errors.Wrap(err, "Error preloading migrations. Check if you have &multiStatements=true in your DSN if you are using MySQL. Or perhaps the schema changed? If yes, then update the warmup files accordingly")) 199 } 200 } 201 202 func (h *MainHelper) Close() error { 203 if h.SQLStore != nil { 204 h.SQLStore.Close() 205 } 206 if h.Settings != nil { 207 storetest.CleanupSqlSettings(h.Settings) 208 } 209 if h.testResourcePath != "" { 210 os.RemoveAll(h.testResourcePath) 211 } 212 213 if r := recover(); r != nil { 214 log.Fatalln(r) 215 } 216 217 os.Exit(h.status) 218 219 return nil 220 } 221 222 func (h *MainHelper) GetSQLSettings() *model.SqlSettings { 223 if h.Settings == nil { 224 panic("MainHelper not initialized with database access.") 225 } 226 227 return h.Settings 228 } 229 230 func (h *MainHelper) GetStore() store.Store { 231 if h.Store == nil { 232 panic("MainHelper not initialized with store.") 233 } 234 235 return h.Store 236 } 237 238 func (h *MainHelper) GetSQLStore() *sqlstore.SqlStore { 239 if h.SQLStore == nil { 240 panic("MainHelper not initialized with sql store.") 241 } 242 243 return h.SQLStore 244 } 245 246 func (h *MainHelper) GetClusterInterface() *FakeClusterInterface { 247 if h.ClusterInterface == nil { 248 panic("MainHelper not initialized with cluster interface.") 249 } 250 251 return h.ClusterInterface 252 } 253 254 func (h *MainHelper) GetSearchEngine() *searchengine.Broker { 255 if h.SearchEngine == nil { 256 panic("MainHelper not initialized with search engine") 257 } 258 259 return h.SearchEngine 260 } 261 262 func (h *MainHelper) SetReplicationLagForTesting(seconds int) error { 263 if dn := h.SQLStore.DriverName(); dn != model.DATABASE_DRIVER_MYSQL { 264 return fmt.Errorf("method not implemented for %q database driver, only %q is supported", dn, model.DATABASE_DRIVER_MYSQL) 265 } 266 267 err := h.execOnEachReplica("STOP SLAVE SQL_THREAD FOR CHANNEL ''") 268 if err != nil { 269 return err 270 } 271 272 err = h.execOnEachReplica(fmt.Sprintf("CHANGE MASTER TO MASTER_DELAY = %d", seconds)) 273 if err != nil { 274 return err 275 } 276 277 err = h.execOnEachReplica("START SLAVE SQL_THREAD FOR CHANNEL ''") 278 if err != nil { 279 return err 280 } 281 282 return nil 283 } 284 285 func (h *MainHelper) execOnEachReplica(query string, args ...interface{}) error { 286 for _, replica := range h.SQLStore.Replicas { 287 _, err := replica.Exec(query, args...) 288 if err != nil { 289 return err 290 } 291 } 292 return nil 293 }