parallel tests (#7629)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
adb2b1d6ed
Коммит
34285d8cca
@@ -47,7 +47,8 @@ type App struct {
|
||||
Mfa einterfaces.MfaInterface
|
||||
Saml einterfaces.SamlInterface
|
||||
|
||||
newStore func() store.Store
|
||||
newStore func() store.Store
|
||||
configOverride func(*model.Config) *model.Config
|
||||
}
|
||||
|
||||
var appCount = 0
|
||||
@@ -77,7 +78,7 @@ func New(options ...Option) *App {
|
||||
|
||||
if app.newStore == nil {
|
||||
app.newStore = func() store.Store {
|
||||
return store.NewLayeredStore(sqlstore.NewSqlSupplier(utils.Cfg.SqlSettings, app.Metrics), app.Metrics, app.Cluster)
|
||||
return store.NewLayeredStore(sqlstore.NewSqlSupplier(app.Config().SqlSettings, app.Metrics), app.Metrics, app.Cluster)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +234,9 @@ func (a *App) initEnterprise() {
|
||||
}
|
||||
|
||||
func (a *App) Config() *model.Config {
|
||||
if a.configOverride != nil {
|
||||
return a.configOverride(utils.Cfg)
|
||||
}
|
||||
return utils.Cfg
|
||||
}
|
||||
|
||||
|
||||
48
app/app_test.go
Обычный файл
48
app/app_test.go
Обычный файл
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/store/storetest"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
// In the case where a dev just wants to run a single test, it's faster to just use the default
|
||||
// store.
|
||||
if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." {
|
||||
utils.TranslationsPreInit()
|
||||
utils.LoadConfig("config.json")
|
||||
l4g.Info("-test.run used, not creating temporary containers")
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
utils.TranslationsPreInit()
|
||||
utils.LoadConfig("config.json")
|
||||
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
||||
|
||||
status := 0
|
||||
|
||||
container, settings, err := storetest.NewMySQLContainer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
UseTestStore(container, settings)
|
||||
|
||||
defer func() {
|
||||
StopTestStore()
|
||||
os.Exit(status)
|
||||
}()
|
||||
|
||||
status = m.Run()
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/store/sqlstore"
|
||||
"github.com/mattermost/mattermost-server/store/storetest"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
@@ -21,13 +24,47 @@ type TestHelper struct {
|
||||
BasicPost *model.Post
|
||||
}
|
||||
|
||||
type persistentTestStore struct {
|
||||
store.Store
|
||||
}
|
||||
|
||||
func (*persistentTestStore) Close() {}
|
||||
|
||||
var testStoreContainer *storetest.RunningContainer
|
||||
var testStore *persistentTestStore
|
||||
|
||||
// UseTestStore sets the container and corresponding settings to use for tests. Once the tests are
|
||||
// complete (e.g. at the end of your TestMain implementation), you should call StopTestStore.
|
||||
func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) {
|
||||
testStoreContainer = container
|
||||
testStore = &persistentTestStore{store.NewLayeredStore(sqlstore.NewSqlSupplier(*settings, nil), nil, nil)}
|
||||
}
|
||||
|
||||
func StopTestStore() {
|
||||
if testStoreContainer != nil {
|
||||
testStoreContainer.Stop()
|
||||
testStoreContainer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestHelper(enterprise bool) *TestHelper {
|
||||
utils.TranslationsPreInit()
|
||||
if utils.T == nil {
|
||||
utils.TranslationsPreInit()
|
||||
}
|
||||
utils.LoadConfig("config.json")
|
||||
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
||||
|
||||
var options []Option
|
||||
if testStore != nil {
|
||||
options = append(options, StoreOverride(testStore))
|
||||
options = append(options, ConfigOverride(func(cfg *model.Config) {
|
||||
cfg.ServiceSettings.ListenAddress = new(string)
|
||||
*cfg.ServiceSettings.ListenAddress = ":0"
|
||||
}))
|
||||
}
|
||||
|
||||
th := &TestHelper{
|
||||
App: New(),
|
||||
App: New(options...),
|
||||
}
|
||||
|
||||
*utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||
@@ -188,4 +225,8 @@ func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
||||
|
||||
func (me *TestHelper) TearDown() {
|
||||
me.App.Shutdown()
|
||||
if err := recover(); err != nil {
|
||||
StopTestStore()
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,53 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
type Option func(a *App)
|
||||
|
||||
// By default, the app will use a global configuration file. This allows you to override all or part
|
||||
// of that configuration.
|
||||
//
|
||||
// The override parameter must be a *model.Config, func(*model.Config), or func(*model.Config) *model.Config.
|
||||
//
|
||||
// XXX: Most code will not respect this at the moment. (We need to eliminate utils.Cfg first.)
|
||||
func ConfigOverride(override interface{}) Option {
|
||||
return func(a *App) {
|
||||
switch o := override.(type) {
|
||||
case *model.Config:
|
||||
a.configOverride = func(*model.Config) *model.Config {
|
||||
return o
|
||||
}
|
||||
case func(*model.Config):
|
||||
a.configOverride = func(cfg *model.Config) *model.Config {
|
||||
ret := *cfg
|
||||
o(&ret)
|
||||
return &ret
|
||||
}
|
||||
case func(*model.Config) *model.Config:
|
||||
a.configOverride = o
|
||||
default:
|
||||
panic("invalid ConfigOverride")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// By default, the app will use the store specified by the configuration. This allows you to
|
||||
// construct an app with a different store.
|
||||
//
|
||||
// The storeOrFactory parameter must be either a store.Store or func(App) store.Store.
|
||||
func StoreOverride(storeOrFactory interface{}) Option {
|
||||
// The override parameter must be either a store.Store or func(App) store.Store.
|
||||
func StoreOverride(override interface{}) Option {
|
||||
return func(a *App) {
|
||||
switch s := storeOrFactory.(type) {
|
||||
switch o := override.(type) {
|
||||
case store.Store:
|
||||
a.newStore = func() store.Store {
|
||||
return s
|
||||
return o
|
||||
}
|
||||
case func(*App) store.Store:
|
||||
a.newStore = func() store.Store {
|
||||
return s(a)
|
||||
return o(a)
|
||||
}
|
||||
default:
|
||||
panic("invalid StoreOverride")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -16,7 +17,6 @@ import (
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rsc/letsencrypt"
|
||||
"github.com/tylerb/graceful"
|
||||
"gopkg.in/throttled/throttled.v2"
|
||||
"gopkg.in/throttled/throttled.v2/store/memstore"
|
||||
|
||||
@@ -29,7 +29,8 @@ type Server struct {
|
||||
Store store.Store
|
||||
WebSocketRouter *WebSocketRouter
|
||||
Router *mux.Router
|
||||
GracefulServer *graceful.Server
|
||||
Server *http.Server
|
||||
ListenAddr *net.TCPAddr
|
||||
}
|
||||
|
||||
var allowedMethods []string = []string{
|
||||
@@ -152,16 +153,29 @@ func (a *App) StartServer() {
|
||||
handler = httpRateLimiter.RateLimit(handler)
|
||||
}
|
||||
|
||||
a.Srv.GracefulServer = &graceful.Server{
|
||||
Timeout: TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN,
|
||||
Server: &http.Server{
|
||||
Addr: *utils.Cfg.ServiceSettings.ListenAddress,
|
||||
Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler),
|
||||
ReadTimeout: time.Duration(*utils.Cfg.ServiceSettings.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(*utils.Cfg.ServiceSettings.WriteTimeout) * time.Second,
|
||||
},
|
||||
a.Srv.Server = &http.Server{
|
||||
Handler: handlers.RecoveryHandler(handlers.RecoveryLogger(&RecoveryLogger{}), handlers.PrintRecoveryStack(true))(handler),
|
||||
ReadTimeout: time.Duration(*utils.Cfg.ServiceSettings.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(*utils.Cfg.ServiceSettings.WriteTimeout) * time.Second,
|
||||
}
|
||||
l4g.Info(utils.T("api.server.start_server.listening.info"), *utils.Cfg.ServiceSettings.ListenAddress)
|
||||
|
||||
addr := *a.Config().ServiceSettings.ListenAddress
|
||||
if addr == "" {
|
||||
if *utils.Cfg.ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
|
||||
addr = ":https"
|
||||
} else {
|
||||
addr = ":http"
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err)
|
||||
return
|
||||
}
|
||||
a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
|
||||
|
||||
l4g.Info(utils.T("api.server.start_server.listening.info"), listener.Addr().String())
|
||||
|
||||
if *utils.Cfg.ServiceSettings.Forward80To443 {
|
||||
go func() {
|
||||
@@ -189,25 +203,55 @@ func (a *App) StartServer() {
|
||||
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
|
||||
|
||||
err = a.Srv.GracefulServer.ListenAndServeTLSConfig(tlsConfig)
|
||||
a.Srv.Server.TLSConfig = tlsConfig
|
||||
err = a.Srv.Server.ServeTLS(listener, "", "")
|
||||
} else {
|
||||
err = a.Srv.GracefulServer.ListenAndServeTLS(*utils.Cfg.ServiceSettings.TLSCertFile, *utils.Cfg.ServiceSettings.TLSKeyFile)
|
||||
err = a.Srv.Server.ServeTLS(listener, *utils.Cfg.ServiceSettings.TLSCertFile, *utils.Cfg.ServiceSettings.TLSKeyFile)
|
||||
}
|
||||
} else {
|
||||
err = a.Srv.GracefulServer.ListenAndServe()
|
||||
err = a.Srv.Server.Serve(listener)
|
||||
}
|
||||
if err != nil {
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (a *App) Listen(addr string) (net.Listener, error) {
|
||||
if addr == "" {
|
||||
addr = ":http"
|
||||
}
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
|
||||
}
|
||||
|
||||
func (a *App) StopServer() {
|
||||
if a.Srv.GracefulServer != nil {
|
||||
a.Srv.GracefulServer.Stop(TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
||||
<-a.Srv.GracefulServer.StopChan()
|
||||
a.Srv.GracefulServer = nil
|
||||
if a.Srv.Server != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
||||
defer cancel()
|
||||
if err := a.Srv.Server.Shutdown(ctx); err != nil {
|
||||
l4g.Warn(err.Error())
|
||||
}
|
||||
a.Srv.Server.Close()
|
||||
a.Srv.Server = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user