From 8796df8218aa306f1431c17233fbcc3e0811cc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=9C=C9=B4=E1=B4=8B=C9=B4=E1=B4=A1=E1=B4=8F=C9=B4?= Date: Sat, 29 Feb 2020 22:24:20 +0800 Subject: [PATCH] conf: add unit tests (#5954) * conf: add tests for utils.go * conf: add tests for static.go * mock os/exec * Run tests on Windows * appveyor: fix gcc not found * computed: add unit tests * log: add unit tests * log: fix tests on Windows * conf: add some tests * Finish adding tests * Cover more cases * Add tests for testutil * Add more tests --- appveyor.yml | 4 + internal/conf/computed.go | 4 +- internal/conf/computed_test.go | 126 +++++++++++++++++ internal/conf/conf.go | 2 +- internal/conf/conf_test.go | 88 ++++++++++++ internal/conf/log.go | 124 +++++++++++------ internal/conf/log_test.go | 134 ++++++++++++++++++ internal/conf/static.go | 24 ++-- internal/conf/static_test.go | 35 +++++ internal/conf/testdata/TestInit.golden.ini | 153 +++++++++++++++++++++ internal/conf/testdata/custom.ini | 46 +++++++ internal/conf/utils.go | 12 +- internal/conf/utils_test.go | 57 ++++++++ internal/osutil/osutil.go | 6 +- internal/osutil/osutil_test.go | 5 + internal/testutil/exec.go | 46 +++++++ internal/testutil/exec_test.go | 55 ++++++++ internal/testutil/golden.go | 63 +++++++++ internal/testutil/golden_test.go | 52 +++++++ internal/testutil/testdata/golden | 3 + 20 files changed, 974 insertions(+), 65 deletions(-) create mode 100644 internal/conf/computed_test.go create mode 100644 internal/conf/conf_test.go create mode 100644 internal/conf/log_test.go create mode 100644 internal/conf/static_test.go create mode 100644 internal/conf/testdata/TestInit.golden.ini create mode 100644 internal/conf/testdata/custom.ini create mode 100644 internal/conf/utils_test.go create mode 100644 internal/testutil/exec.go create mode 100644 internal/testutil/exec_test.go create mode 100644 internal/testutil/golden.go create mode 100644 internal/testutil/golden_test.go create mode 100644 internal/testutil/testdata/golden diff --git a/appveyor.yml b/appveyor.yml index e1169985c..288b9549f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,10 @@ build: false deploy: false install: + - set PATH=C:\msys64\mingw64\bin;%PATH% # Fix "gcc" not found: https://github.com/appveyor/ci/issues/2613 - go version - go env - go build -tags "minwinsvc" -v + +test_script: + - go test -v -race -cover ./... diff --git a/internal/conf/computed.go b/internal/conf/computed.go index 07a5a94c6..64eb322da 100644 --- a/internal/conf/computed.go +++ b/internal/conf/computed.go @@ -99,8 +99,8 @@ var ( // string when environment variables are not set. func HomeDir() string { homeDirOnce.Do(func() { - if !IsWindowsRuntime() { - homeDir = os.Getenv("HOME") + homeDir = os.Getenv("HOME") + if homeDir != "" { return } diff --git a/internal/conf/computed_test.go b/internal/conf/computed_test.go new file mode 100644 index 000000000..908b7679b --- /dev/null +++ b/internal/conf/computed_test.go @@ -0,0 +1,126 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "gogs.io/gogs/internal/testutil" +) + +func TestIsProdMode(t *testing.T) { + before := App.RunMode + defer func() { + App.RunMode = before + }() + + tests := []struct { + mode string + want bool + }{ + {mode: "dev", want: false}, + {mode: "test", want: false}, + + {mode: "prod", want: true}, + {mode: "Prod", want: true}, + {mode: "PROD", want: true}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + App.RunMode = test.mode + assert.Equal(t, test.want, IsProdMode()) + }) + } +} + +func TestWorkDirHelper(t *testing.T) { + if !testutil.WantHelperProcess() { + return + } + + fmt.Fprintln(os.Stdout, WorkDir()) +} + +func TestWorkDir(t *testing.T) { + tests := []struct { + env string + want string + }{ + {env: "GOGS_WORK_DIR=/tmp", want: "/tmp"}, + {env: "", want: WorkDir()}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + out, err := testutil.Exec("TestWorkDirHelper", test.env) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, test.want, out) + }) + } +} + +func TestCustomDirHelper(t *testing.T) { + if !testutil.WantHelperProcess() { + return + } + + fmt.Fprintln(os.Stdout, CustomDir()) +} + +func TestCustomDir(t *testing.T) { + tests := []struct { + env string + want string + }{ + {env: "GOGS_CUSTOM=/tmp", want: "/tmp"}, + {env: "", want: CustomDir()}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + out, err := testutil.Exec("TestCustomDirHelper", test.env) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, test.want, out) + }) + } +} + +func TestHomeDirHelper(t *testing.T) { + if !testutil.WantHelperProcess() { + return + } + + fmt.Fprintln(os.Stdout, HomeDir()) +} + +func TestHomeDir(t *testing.T) { + tests := []struct { + envs []string + want string + }{ + {envs: []string{"HOME=/tmp"}, want: "/tmp"}, + {envs: []string{`USERPROFILE=C:\Users\Joe`}, want: `C:\Users\Joe`}, + {envs: []string{`HOMEDRIVE=C:`, `HOMEPATH=\Users\Joe`}, want: `C:\Users\Joe`}, + {envs: nil, want: ""}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + out, err := testutil.Exec("TestHomeDirHelper", test.envs...) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, test.want, out) + }) + } +} diff --git a/internal/conf/conf.go b/internal/conf/conf.go index b708eb835..9ba14f385 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -353,7 +353,7 @@ func Init(customConf string) error { // ----- I18n settings ----- // ************************* - I18n = new(i18n) + I18n = new(i18nConf) if err = File.Section("i18n").MapTo(I18n); err != nil { return errors.Wrap(err, "mapping [i18n] section") } diff --git a/internal/conf/conf_test.go b/internal/conf/conf_test.go new file mode 100644 index 000000000..0030ea52e --- /dev/null +++ b/internal/conf/conf_test.go @@ -0,0 +1,88 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "bytes" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/ini.v1" + + "gogs.io/gogs/internal/testutil" +) + +func TestAsset(t *testing.T) { + // Make sure it does not blow up + _, err := Asset("conf/app.ini") + if err != nil { + t.Fatal(err) + } +} + +func TestAssetDir(t *testing.T) { + // Make sure it does not blow up + _, err := AssetDir("conf") + if err != nil { + t.Fatal(err) + } +} + +func TestMustAsset(t *testing.T) { + // Make sure it does not blow up + MustAsset("conf/app.ini") +} + +func TestInit(t *testing.T) { + if IsWindowsRuntime() { + return + } + + ini.PrettyFormat = false + defer func() { + MustInit("") + ini.PrettyFormat = true + }() + + assert.Nil(t, Init(filepath.Join("testdata", "custom.ini"))) + + cfg := ini.Empty() + cfg.NameMapper = ini.SnackCase + + for _, v := range []struct { + section string + config interface{} + }{ + {"", &App}, + {"server", &Server}, + {"server", &SSH}, + {"repository", &Repository}, + {"database", &Database}, + {"security", &Security}, + {"email", &Email}, + {"auth", &Auth}, + {"user", &User}, + {"session", &Session}, + {"attachment", &Attachment}, + {"time", &Time}, + {"picture", &Picture}, + {"mirror", &Mirror}, + {"i18n", &I18n}, + } { + err := cfg.Section(v.section).ReflectFrom(v.config) + if err != nil { + t.Fatalf("%s: %v", v.section, err) + } + } + + buf := new(bytes.Buffer) + _, err := cfg.WriteTo(buf) + if err != nil { + t.Fatal(err) + } + + testutil.AssertGolden(t, filepath.Join("testdata", "TestInit.golden.ini"), testutil.Update("TestInit"), buf.String()) +} diff --git a/internal/conf/log.go b/internal/conf/log.go index fdb1d64ef..3087c7c86 100644 --- a/internal/conf/log.go +++ b/internal/conf/log.go @@ -9,28 +9,39 @@ import ( "path/filepath" "strings" + "github.com/pkg/errors" + "gopkg.in/ini.v1" log "unknwon.dev/clog/v2" ) -// Log settings -var Log struct { - RootPath string - Modes []string - Configs []interface{} +type loggerConf struct { + Buffer int64 + Config interface{} } -// InitLogging initializes the logging service of the application. -func InitLogging() { - Log.RootPath = File.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log")) +type logConf struct { + RootPath string + Modes []string + Configs []*loggerConf +} - // Because we always create a console logger as the primary logger at init time, - // we need to remove it in case the user doesn't configure to use it after the - // logging service is initalized. - hasConsole := false +// Log settings +var Log *logConf + +// initLogConf returns parsed logging configuration from given INI file. +// NOTE: Because we always create a console logger as the primary logger at init time, +// we need to remove it in case the user doesn't configure to use it after the logging +// service is initalized. +func initLogConf(cfg *ini.File) (_ *logConf, hasConsole bool, _ error) { + rootPath := cfg.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log")) + modes := strings.Split(cfg.Section("log").Key("MODE").MustString("console"), ",") + lc := &logConf{ + RootPath: ensureAbs(rootPath), + Modes: make([]string, 0, len(modes)), + Configs: make([]*loggerConf, 0, len(modes)), + } // Iterate over [log.*] sections to initialize individual logger. - Log.Modes = strings.Split(File.Section("log").Key("MODE").MustString("console"), ",") - Log.Configs = make([]interface{}, 0, len(Log.Modes)) levelMappings := map[string]log.Level{ "trace": log.LevelTrace, "info": log.LevelInfo, @@ -39,43 +50,30 @@ func InitLogging() { "fatal": log.LevelFatal, } - type config struct { - Buffer int64 - Config interface{} - } - for _, mode := range Log.Modes { - mode = strings.ToLower(strings.TrimSpace(mode)) - secName := "log." + mode - sec, err := File.GetSection(secName) + for i := range modes { + modes[i] = strings.ToLower(strings.TrimSpace(modes[i])) + secName := "log." + modes[i] + sec, err := cfg.GetSection(secName) if err != nil { - log.Fatal("Missing configuration section [%s] for %q logger", secName, mode) - return + return nil, hasConsole, errors.Errorf("missing configuration section [%s] for %q logger", secName, modes[i]) } level := levelMappings[strings.ToLower(sec.Key("LEVEL").MustString("trace"))] buffer := sec.Key("BUFFER_LEN").MustInt64(100) - var c *config - switch mode { + var c *loggerConf + switch modes[i] { case log.DefaultConsoleName: hasConsole = true - c = &config{ + c = &loggerConf{ Buffer: buffer, Config: log.ConsoleConfig{ Level: level, }, } - err = log.NewConsole(c.Buffer, c.Config) case log.DefaultFileName: - logPath := filepath.Join(Log.RootPath, "gogs.log") - logDir := filepath.Dir(logPath) - err = os.MkdirAll(logDir, os.ModePerm) - if err != nil { - log.Fatal("Failed to create log directory %q: %v", logDir, err) - return - } - - c = &config{ + logPath := filepath.Join(lc.RootPath, "gogs.log") + c = &loggerConf{ Buffer: buffer, Config: log.FileConfig{ Level: level, @@ -89,20 +87,18 @@ func InitLogging() { }, }, } - err = log.NewFile(c.Buffer, c.Config) case log.DefaultSlackName: - c = &config{ + c = &loggerConf{ Buffer: buffer, Config: log.SlackConfig{ Level: level, URL: sec.Key("URL").String(), }, } - err = log.NewSlack(c.Buffer, c.Config) case log.DefaultDiscordName: - c = &config{ + c = &loggerConf{ Buffer: buffer, Config: log.DiscordConfig{ Level: level, @@ -110,22 +106,62 @@ func InitLogging() { Username: sec.Key("USERNAME").String(), }, } - err = log.NewDiscord(c.Buffer, c.Config) default: continue } + lc.Modes = append(lc.Modes, modes[i]) + lc.Configs = append(lc.Configs, c) + } + + return lc, hasConsole, nil +} + +// InitLogging initializes the logging service of the application. +func InitLogging() { + logConf, hasConsole, err := initLogConf(File) + if err != nil { + log.Fatal("Failed to init logging configuration: %v", err) + } + + err = os.MkdirAll(logConf.RootPath, os.ModePerm) + if err != nil { + log.Fatal("Failed to create log directory: %v", err) + } + + for i, mode := range logConf.Modes { + c := logConf.Configs[i] + + var err error + var level log.Level + switch mode { + case log.DefaultConsoleName: + level = c.Config.(log.ConsoleConfig).Level + err = log.NewConsole(c.Buffer, c.Config) + case log.DefaultFileName: + level = c.Config.(log.FileConfig).Level + err = log.NewFile(c.Buffer, c.Config) + case log.DefaultSlackName: + level = c.Config.(log.SlackConfig).Level + err = log.NewSlack(c.Buffer, c.Config) + case log.DefaultDiscordName: + level = c.Config.(log.DiscordConfig).Level + err = log.NewDiscord(c.Buffer, c.Config) + default: + panic("unreachable") + } + if err != nil { log.Fatal("Failed to init %s logger: %v", mode, err) return } - - Log.Configs = append(Log.Configs, c) log.Trace("Log mode: %s (%s)", strings.Title(mode), strings.Title(strings.ToLower(level.String()))) } if !hasConsole { log.Remove(log.DefaultConsoleName) } + + Log = logConf } diff --git a/internal/conf/log_test.go b/internal/conf/log_test.go new file mode 100644 index 000000000..8d5d2e356 --- /dev/null +++ b/internal/conf/log_test.go @@ -0,0 +1,134 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/ini.v1" + log "unknwon.dev/clog/v2" +) + +func Test_initLogConf(t *testing.T) { + t.Run("missing configuration section", func(t *testing.T) { + f, err := ini.Load([]byte(` +[log] +MODE = console +`)) + if err != nil { + t.Fatal(err) + } + + got, hasConsole, err := initLogConf(f) + assert.NotNil(t, err) + assert.Equal(t, `missing configuration section [log.console] for "console" logger`, err.Error()) + assert.False(t, hasConsole) + assert.Nil(t, got) + }) + + t.Run("no console logger", func(t *testing.T) { + f, err := ini.Load([]byte(` +[log] +MODE = file + +[log.file] +`)) + if err != nil { + t.Fatal(err) + } + + got, hasConsole, err := initLogConf(f) + if err != nil { + t.Fatal(err) + } + + assert.False(t, hasConsole) + assert.NotNil(t, got) + }) + + f, err := ini.Load([]byte(` +[log] +ROOT_PATH = log +MODE = console, file, slack, discord +BUFFER_LEN = 50 +LEVEL = trace + +[log.console] +BUFFER_LEN = 10 + +[log.file] +LEVEL = INFO +LOG_ROTATE = true +DAILY_ROTATE = true +MAX_SIZE_SHIFT = 20 +MAX_LINES = 1000 +MAX_DAYS = 3 + +[log.slack] +LEVEL = Warn +URL = https://slack.com + +[log.discord] +LEVEL = error +URL = https://discordapp.com +USERNAME = yoyo +`)) + if err != nil { + t.Fatal(err) + } + + got, hasConsole, err := initLogConf(f) + if err != nil { + t.Fatal(err) + } + + logConf := &logConf{ + RootPath: filepath.Join(WorkDir(), "log"), + Modes: []string{ + log.DefaultConsoleName, + log.DefaultFileName, + log.DefaultSlackName, + log.DefaultDiscordName, + }, + Configs: []*loggerConf{ + { + Buffer: 10, + Config: log.ConsoleConfig{ + Level: log.LevelTrace, + }, + }, { + Buffer: 50, + Config: log.FileConfig{ + Level: log.LevelInfo, + Filename: filepath.Join(WorkDir(), "log", "gogs.log"), + FileRotationConfig: log.FileRotationConfig{ + Rotate: true, + Daily: true, + MaxSize: 1 << 20, + MaxLines: 1000, + MaxDays: 3, + }, + }, + }, { + Buffer: 50, + Config: log.SlackConfig{ + Level: log.LevelWarn, + URL: "https://slack.com", + }, + }, { + Buffer: 50, + Config: log.DiscordConfig{ + Level: log.LevelError, + URL: "https://discordapp.com", + Username: "yoyo", + }, + }, + }, + } + assert.True(t, hasConsole) + assert.Equal(t, logConf, got) +} diff --git a/internal/conf/static.go b/internal/conf/static.go index cb58ac176..b4d95bc68 100644 --- a/internal/conf/static.go +++ b/internal/conf/static.go @@ -16,12 +16,12 @@ import ( // HasMinWinSvc is whether the application is built with Windows Service support. // -// ⚠️ WARNING: should only be set by "static_minwinsvc.go". +// ⚠️ WARNING: should only be set by "internal/conf/static_minwinsvc.go". var HasMinWinSvc bool // Build time and commit information. // -// ⚠️ WARNING: should only be set by -ldflags. +// ⚠️ WARNING: should only be set by "-ldflags". var ( BuildTime string BuildCommit string @@ -35,7 +35,7 @@ var CustomConf string var ( // Application settings App struct { - // ⚠️ WARNING: Should only be set by main package (i.e. "gogs.go"). + // ⚠️ WARNING: Should only be set by the main package (i.e. "gogs.go"). Version string `ini:"-"` BrandName string @@ -288,7 +288,7 @@ var ( } // I18n settings - I18n *i18n + I18n *i18nConf // Webhook settings Webhook struct { @@ -349,7 +349,9 @@ var ( // Git settings Git struct { - Version string `ini:"-"` + // ⚠️ WARNING: Should only be set by "internal/db/repo.go". + Version string `ini:"-"` + DisableDiffHighlight bool MaxGitDiffLines int MaxGitDiffLineCharacters int @@ -408,15 +410,15 @@ var ( HasRobotsTxt bool ) -type i18n struct { - Langs []string `delim:","` - Names []string `delim:","` - dateLangs map[string]string +type i18nConf struct { + Langs []string `delim:","` + Names []string `delim:","` + dateLangs map[string]string `ini:"-"` } // DateLang transforms standard language locale name to corresponding value in datetime plugin. -func (i *i18n) DateLang(lang string) string { - name, ok := i.dateLangs[lang] +func (c *i18nConf) DateLang(lang string) string { + name, ok := c.dateLangs[lang] if ok { return name } diff --git a/internal/conf/static_test.go b/internal/conf/static_test.go new file mode 100644 index 000000000..e9b9cb022 --- /dev/null +++ b/internal/conf/static_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_i18n_DateLang(t *testing.T) { + c := &i18nConf{ + dateLangs: map[string]string{ + "en-US": "en", + "zh-CN": "zh", + }, + } + + tests := []struct { + lang string + want string + }{ + {lang: "en-US", want: "en"}, + {lang: "zh-CN", want: "zh"}, + + {lang: "jp-JP", want: "en"}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + assert.Equal(t, test.want, c.DateLang(test.lang)) + }) + } +} diff --git a/internal/conf/testdata/TestInit.golden.ini b/internal/conf/testdata/TestInit.golden.ini new file mode 100644 index 000000000..b08b90e1c --- /dev/null +++ b/internal/conf/testdata/TestInit.golden.ini @@ -0,0 +1,153 @@ +BRAND_NAME=Testing +RUN_USER=git +RUN_MODE=test +APP_NAME= + +[server] +EXTERNAL_URL=http://localhost:3080/ +DOMAIN=localhost +PROTOCOL=http +HTTP_ADDR=0.0.0.0 +HTTP_PORT=3000 +CERT_FILE=custom/https/cert.pem +KEY_FILE=custom/https/key.pem +TLS_MIN_VERSION=TLS12 +UNIX_SOCKET_PERMISSION=666 +LOCAL_ROOT_URL=http://0.0.0.0:3000/ +OFFLINE_MODE=false +DISABLE_ROUTER_LOG=true +ENABLE_GZIP=false +APP_DATA_PATH=/tmp/data +LOAD_ASSETS_FROM_DISK=false +LANDING_URL=/explore +ROOT_URL= +LANDING_PAGE= +DISABLE_SSH=false +SSH_DOMAIN=localhost +SSH_PORT=22 +SSH_ROOT_PATH=/tmp +SSH_KEYGEN_PATH=ssh-keygen +SSH_KEY_TEST_PATH=/tmp/ssh-key-test +MINIMUM_KEY_SIZE_CHECK=true +REWRITE_AUTHORIZED_KEYS_AT_START=false +START_SSH_SERVER=false +SSH_LISTEN_HOST=0.0.0.0 +SSH_LISTEN_PORT=22 +SSH_SERVER_CIPHERS=aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,arcfour256,arcfour128 + +[repository] +ROOT=/tmp/gogs-repositories +SCRIPT_TYPE=bash +ANSI_CHARSET= +FORCE_PRIVATE=false +MAX_CREATION_LIMIT=-1 +PREFERRED_LICENSES=Apache License 2.0,MIT License +DISABLE_HTTP_GIT=false +ENABLE_LOCAL_PATH_MIGRATION=false +ENABLE_RAW_FILE_RENDER_MODE=false +COMMITS_FETCH_CONCURRENCY=0 + +[repository.editor] +LINE_WRAP_EXTENSIONS=.txt,.md,.markdown,.mdown,.mkd +PREVIEWABLE_FILE_MODES=markdown + +[repository.upload] +ENABLED=true +TEMP_PATH=/tmp/uploads +ALLOWED_TYPES= +FILE_MAX_SIZE=3 +MAX_FILES=5 + +[database] +TYPE=sqlite +HOST=127.0.0.1:5432 +NAME=gogs +USER=gogs +PASSWORD=12345678 +SSL_MODE=disable +PATH=/tmp/gogs.db +DB_TYPE= +PASSWD= + +[security] +INSTALL_LOCK=false +SECRET_KEY=`!#@FDEWREWR&*(` +LOGIN_REMEMBER_DAYS=7 +COOKIE_REMEMBER_NAME=gogs_incredible +COOKIE_USERNAME=gogs_awesome +COOKIE_SECURE=false +ENABLE_LOGIN_STATUS_COOKIE=false +LOGIN_STATUS_COOKIE_NAME=login_status +REVERSE_PROXY_AUTHENTICATION_USER= + +[email] +ENABLED=true +SUBJECT_PREFIX=[Gogs] +HOST=smtp.mailgun.org:587 +FROM=noreply@gogs.localhost +USER=noreply@gogs.localhost +PASSWORD=87654321 +DISABLE_HELO=false +HELO_HOSTNAME= +SKIP_VERIFY=false +USE_CERTIFICATE=false +CERT_FILE=custom/email/cert.pem +KEY_FILE=custom/email/key.pem +USE_PLAIN_TEXT=false +ADD_PLAIN_TEXT_ALT=false +PASSWD= + +[auth] +ACTIVATE_CODE_LIVES=10 +RESET_PASSWORD_CODE_LIVES=10 +REQUIRE_EMAIL_CONFIRMATION=true +REQUIRE_SIGNIN_VIEW=false +DISABLE_REGISTRATION=false +ENABLE_REGISTRATION_CAPTCHA=true +ENABLE_REVERSE_PROXY_AUTHENTICATION=false +ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=false +REVERSE_PROXY_AUTHENTICATION_HEADER=X-FORWARDED-FOR +ACTIVE_CODE_LIVE_MINUTES=0 +RESET_PASSWD_CODE_LIVE_MINUTES=0 +REGISTER_EMAIL_CONFIRM=false +ENABLE_CAPTCHA=false +ENABLE_NOTIFY_MAIL=false + +[user] +ENABLE_EMAIL_NOTIFICATION=true + +[session] +PROVIDER=memory +PROVIDER_CONFIG=data/sessions +COOKIE_NAME=i_like_gogs +COOKIE_SECURE=false +GC_INTERVAL=10 +MAX_LIFE_TIME=10 +CSRF_COOKIE_NAME=_csrf +GC_INTERVAL_TIME=0 +SESSION_LIFE_TIME=0 + +[attachment] +ENABLED=true +PATH=/tmp/attachments +ALLOWED_TYPES=image/jpeg|image/png +MAX_SIZE=4 +MAX_FILES=5 + +[time] +FORMAT=RFC1123 + +[picture] +AVATAR_UPLOAD_PATH=/tmp/avatars +REPOSITORY_AVATAR_UPLOAD_PATH=/tmp/repo-avatars +GRAVATAR_SOURCE=https://secure.gravatar.com/avatar/ +DISABLE_GRAVATAR=false +ENABLE_FEDERATED_AVATAR=false + +[mirror] +DEFAULT_INTERVAL=8 + +[i18n] +LANGS=en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,gl-ES,uk-UA,en-GB,hu-HU,sk-SK,id-ID,fa-IR,vi-VN,pt-PT +NAMES=English,简体中文,繁體中文(香港),繁體中文(臺灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어,galego,українська,English (United Kingdom),Magyar,Slovenčina,Indonesian,Persian,Vietnamese,Português + diff --git a/internal/conf/testdata/custom.ini b/internal/conf/testdata/custom.ini new file mode 100644 index 000000000..ca7160d03 --- /dev/null +++ b/internal/conf/testdata/custom.ini @@ -0,0 +1,46 @@ +APP_NAME = Testing +RUN_MODE = test + +[server] +ROOT_URL = http://localhost:3080/ +APP_DATA_PATH = /tmp/data +SSH_ROOT_PATH = /tmp +SSH_KEY_TEST_PATH = /tmp/ssh-key-test +MINIMUM_KEY_SIZE_CHECK = true +LANDING_PAGE = explore + +[repository] +ROOT = /tmp/gogs-repositories + +[repository.upload] +TEMP_PATH = /tmp/uploads + +[database] +DB_TYPE = sqlite +PASSWD = 12345678 +PATH = /tmp/gogs.db + +[security] +REVERSE_PROXY_AUTHENTICATION_USER=X-FORWARDED-FOR + +[email] +ENABLED = true +PASSWD = 87654321 + +[auth] +ACTIVE_CODE_LIVE_MINUTES = 10 +RESET_PASSWD_CODE_LIVE_MINUTES = 10 +REGISTER_EMAIL_CONFIRM = true +ENABLE_CAPTCHA = true +ENABLE_NOTIFY_MAIL = true + +[session] +GC_INTERVAL_TIME = 10 +SESSION_LIFE_TIME = 10 + +[attachment] +PATH = /tmp/attachments + +[picture] +AVATAR_UPLOAD_PATH = /tmp/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = /tmp/repo-avatars diff --git a/internal/conf/utils.go b/internal/conf/utils.go index edead54a4..f2662dc34 100644 --- a/internal/conf/utils.go +++ b/internal/conf/utils.go @@ -14,6 +14,13 @@ import ( "gogs.io/gogs/internal/process" ) +// cleanUpOpenSSHVersion cleans up the raw output of "ssh -V" and returns a clean version string. +func cleanUpOpenSSHVersion(raw string) string { + v := strings.TrimRight(strings.Fields(raw)[0], ",1234567890") + v = strings.TrimSuffix(strings.TrimPrefix(v, "OpenSSH_"), "p") + return v +} + // openSSHVersion returns string representation of OpenSSH version via command "ssh -V". func openSSHVersion() (string, error) { // NOTE: Somehow the version is printed to stderr. @@ -22,10 +29,7 @@ func openSSHVersion() (string, error) { return "", errors.Wrap(err, stderr) } - // Trim unused information, see https://github.com/gogs/gogs/issues/4507#issuecomment-305150441. - v := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890") - v = strings.TrimSuffix(strings.TrimPrefix(v, "OpenSSH_"), "p") - return v, nil + return cleanUpOpenSSHVersion(stderr), nil } // ensureAbs prepends the WorkDir to the given path if it is not an absolute path. diff --git a/internal/conf/utils_test.go b/internal/conf/utils_test.go new file mode 100644 index 000000000..3d5fafe3a --- /dev/null +++ b/internal/conf/utils_test.go @@ -0,0 +1,57 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_cleanUpOpenSSHVersion(t *testing.T) { + tests := []struct { + raw string + want string + }{ + { + raw: "OpenSSH_7.4p1 Ubuntu-10, OpenSSL 1.0.2g 1 Mar 2016", + want: "7.4", + }, { + raw: "OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013", + want: "5.3", + }, { + raw: "OpenSSH_4.3p2, OpenSSL 0.9.8e-fips-rhel5 01 Jul 2008", + want: "4.3", + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + assert.Equal(t, test.want, cleanUpOpenSSHVersion(test.raw)) + }) + } +} + +func Test_ensureAbs(t *testing.T) { + wd := WorkDir() + + tests := []struct { + path string + want string + }{ + { + path: "data/avatars", + want: filepath.Join(wd, "data", "avatars"), + }, { + path: wd, + want: wd, + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + assert.Equal(t, test.want, ensureAbs(test.path)) + }) + } +} diff --git a/internal/osutil/osutil.go b/internal/osutil/osutil.go index 827988a0b..3af67570d 100644 --- a/internal/osutil/osutil.go +++ b/internal/osutil/osutil.go @@ -25,9 +25,9 @@ func IsExist(path string) bool { // CurrentUsername returns the current system user via environment variables. func CurrentUsername() string { - curUserName := os.Getenv("USER") - if len(curUserName) > 0 { - return curUserName + username := os.Getenv("USER") + if len(username) > 0 { + return username } return os.Getenv("USERNAME") diff --git a/internal/osutil/osutil_test.go b/internal/osutil/osutil_test.go index 4c9a2ad73..8c45f5c0a 100644 --- a/internal/osutil/osutil_test.go +++ b/internal/osutil/osutil_test.go @@ -55,3 +55,8 @@ func TestIsExist(t *testing.T) { }) } } + +func TestCurrentUsername(t *testing.T) { + // Make sure it does not blow up + CurrentUsername() +} diff --git a/internal/testutil/exec.go b/internal/testutil/exec.go new file mode 100644 index 000000000..eee1677bd --- /dev/null +++ b/internal/testutil/exec.go @@ -0,0 +1,46 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package testutil + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" +) + +// Exec executes "go test" on given helper with supplied environment variables. +// It is useful to mock "os/exec" functions in tests. When succeeded, it returns +// the result produced by the test helper. +// The test helper should: +// 1. Use WantHelperProcess function to determine if it is being called in helper mode. +// 2. Call fmt.Fprintln(os.Stdout, ...) to print results for the main test to collect. +func Exec(helper string, envs ...string) (string, error) { + cmd := exec.Command(os.Args[0], "-test.run="+helper, "--") + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + cmd.Env = append(cmd.Env, envs...) + out, err := cmd.CombinedOutput() + str := string(out) + if err != nil { + return "", fmt.Errorf("%v - %s", err, str) + } + + if strings.Contains(str, "no tests to run") { + return "", errors.New("no tests to run") + } else if !strings.Contains(str, "PASS") { + return "", errors.New(str) + } + + // Collect helper result + result := str[:strings.Index(str, "PASS")] + result = strings.TrimSpace(result) + return result, nil +} + +// WantHelperProcess returns true if current process is in helper mode. +func WantHelperProcess() bool { + return os.Getenv("GO_WANT_HELPER_PROCESS") == "1" +} diff --git a/internal/testutil/exec_test.go b/internal/testutil/exec_test.go new file mode 100644 index 000000000..679dc9d05 --- /dev/null +++ b/internal/testutil/exec_test.go @@ -0,0 +1,55 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package testutil + +import ( + "errors" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExecHelper(t *testing.T) { + if !WantHelperProcess() { + return + } + + if os.Getenv("PASS") != "1" { + fmt.Fprintln(os.Stdout, "tests failed") + os.Exit(1) + } + + fmt.Fprintln(os.Stdout, "tests succeed") +} + +func TestExec(t *testing.T) { + tests := []struct { + helper string + env string + expOut string + expErr error + }{ + { + helper: "NoTestsToRun", + expErr: errors.New("no tests to run"), + }, { + helper: "TestExecHelper", + expErr: errors.New("exit status 1 - tests failed\n"), + }, { + helper: "TestExecHelper", + env: "PASS=1", + expOut: "tests succeed", + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + out, err := Exec(test.helper, test.env) + assert.Equal(t, test.expErr, err) + assert.Equal(t, test.expOut, out) + }) + } +} diff --git a/internal/testutil/golden.go b/internal/testutil/golden.go new file mode 100644 index 000000000..fa584d1ba --- /dev/null +++ b/internal/testutil/golden.go @@ -0,0 +1,63 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package testutil + +import ( + "encoding/json" + "flag" + "io/ioutil" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +var updateRegex = flag.String("update", "", "Update testdata of tests matching the given regex") + +// Update returns true if update regex mathces given test name. +func Update(name string) bool { + if updateRegex == nil || *updateRegex == "" { + return false + } + return regexp.MustCompile(*updateRegex).MatchString(name) +} + +// AssertGolden compares what's got and what's in the golden file. It updates +// the golden file on-demand. +func AssertGolden(t testing.TB, path string, update bool, got interface{}) { + t.Helper() + + data := marshal(t, got) + + if update { + if err := ioutil.WriteFile(path, data, 0640); err != nil { + t.Fatalf("update golden file %q: %s", path, err) + } + } + + golden, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("read golden file %q: %s", path, err) + } + + assert.Equal(t, string(golden), string(data)) +} + +func marshal(t testing.TB, v interface{}) []byte { + t.Helper() + + switch v2 := v.(type) { + case string: + return []byte(v2) + case []byte: + return v2 + default: + data, err := json.MarshalIndent(v, "", " ") + if err != nil { + t.Fatal(err) + } + return data + } +} diff --git a/internal/testutil/golden_test.go b/internal/testutil/golden_test.go new file mode 100644 index 000000000..05f31399c --- /dev/null +++ b/internal/testutil/golden_test.go @@ -0,0 +1,52 @@ +// Copyright 2020 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package testutil + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpdate(t *testing.T) { + before := updateRegex + defer func() { + updateRegex = before + }() + + t.Run("no flag", func(t *testing.T) { + updateRegex = nil + assert.False(t, Update("TestUpdate")) + }) + + tests := []struct { + regex string + name string + want bool + }{ + {regex: "", name: "TestUpdate", want: false}, + {regex: "TestNotFound", name: "TestUpdate", want: false}, + + {regex: ".*", name: "TestUpdate", want: true}, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + updateRegex = &test.regex + assert.Equal(t, test.want, Update(test.name)) + }) + } +} + +func TestAssertGolden(t *testing.T) { + // Make sure it does not blow up + AssertGolden(t, filepath.Join("testdata", "golden"), false, "{\n \"Message\": \"This is a golden file.\"\n}") + AssertGolden(t, filepath.Join("testdata", "golden"), false, []byte("{\n \"Message\": \"This is a golden file.\"\n}")) + + type T struct { + Message string + } + AssertGolden(t, filepath.Join("testdata", "golden"), false, T{"This is a golden file."}) +} diff --git a/internal/testutil/testdata/golden b/internal/testutil/testdata/golden new file mode 100644 index 000000000..b03368184 --- /dev/null +++ b/internal/testutil/testdata/golden @@ -0,0 +1,3 @@ +{ + "Message": "This is a golden file." +} \ No newline at end of file