summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorChris <ccbrown112@gmail.com>2017-10-16 08:09:43 -0700
committerJoram Wilander <jwawilander@gmail.com>2017-10-16 11:09:43 -0400
commit34285d8cca93fc0f473636e78680fade03f26bda (patch)
treeb8274ed8d17e5dc63ac36aadac7e7299635d2b43 /app
parentadb2b1d6eddabea803af8fa6cf53a75c98694427 (diff)
downloadchat-34285d8cca93fc0f473636e78680fade03f26bda.tar.gz
chat-34285d8cca93fc0f473636e78680fade03f26bda.tar.bz2
chat-34285d8cca93fc0f473636e78680fade03f26bda.zip
parallel tests (#7629)
Diffstat (limited to 'app')
-rw-r--r--app/app.go8
-rw-r--r--app/app_test.go48
-rw-r--r--app/apptestlib.go45
-rw-r--r--app/options.go38
-rw-r--r--app/server.go82
5 files changed, 193 insertions, 28 deletions
diff --git a/app/app.go b/app/app.go
index 7b6499b1f..34c0721a0 100644
--- a/app/app.go
+++ b/app/app.go
@@ -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
}
diff --git a/app/app_test.go b/app/app_test.go
new file mode 100644
index 000000000..00d08fb14
--- /dev/null
+++ b/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()
+}
diff --git a/app/apptestlib.go b/app/apptestlib.go
index 09bf02d39..9c26e0bbb 100644
--- a/app/apptestlib.go
+++ b/app/apptestlib.go
@@ -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)
+ }
}
diff --git a/app/options.go b/app/options.go
index e5ac85706..121bbbf80 100644
--- a/app/options.go
+++ b/app/options.go
@@ -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")
diff --git a/app/server.go b/app/server.go
index c509d0440..08772dce4 100644
--- a/app/server.go
+++ b/app/server.go
@@ -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
}
}