aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/conf/computed.go4
-rw-r--r--internal/conf/computed_test.go126
-rw-r--r--internal/conf/conf.go2
-rw-r--r--internal/conf/conf_test.go88
-rw-r--r--internal/conf/log.go122
-rw-r--r--internal/conf/log_test.go134
-rw-r--r--internal/conf/static.go24
-rw-r--r--internal/conf/static_test.go35
-rw-r--r--internal/conf/testdata/TestInit.golden.ini153
-rw-r--r--internal/conf/testdata/custom.ini46
-rw-r--r--internal/conf/utils.go12
-rw-r--r--internal/conf/utils_test.go57
-rw-r--r--internal/osutil/osutil.go6
-rw-r--r--internal/osutil/osutil_test.go5
-rw-r--r--internal/testutil/exec.go46
-rw-r--r--internal/testutil/exec_test.go55
-rw-r--r--internal/testutil/golden.go63
-rw-r--r--internal/testutil/golden_test.go52
-rw-r--r--internal/testutil/testdata/golden3
19 files changed, 969 insertions, 64 deletions
diff --git a/internal/conf/computed.go b/internal/conf/computed.go
index 07a5a94c..64eb322d 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 00000000..908b7679
--- /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 b708eb83..9ba14f38 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 00000000..0030ea52
--- /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 fdb1d64e..3087c7c8 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 {
+type loggerConf struct {
+ Buffer int64
+ Config interface{}
+}
+
+type logConf struct {
RootPath string
Modes []string
- Configs []interface{}
+ Configs []*loggerConf
}
-// InitLogging initializes the logging service of the application.
-func InitLogging() {
- Log.RootPath = File.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log"))
-
- // 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 00000000..8d5d2e35
--- /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 cb58ac17..b4d95bc6 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 00000000..e9b9cb02
--- /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 00000000..b08b90e1
--- /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 00000000..ca7160d0
--- /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 edead54a..f2662dc3 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 00000000..3d5fafe3
--- /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 827988a0..3af67570 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 4c9a2ad7..8c45f5c0 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 00000000..eee1677b
--- /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 00000000..679dc9d0
--- /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 00000000..fa584d1b
--- /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 00000000..05f31399
--- /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 00000000..b0336818
--- /dev/null
+++ b/internal/testutil/testdata/golden
@@ -0,0 +1,3 @@
+{
+ "Message": "This is a golden file."
+} \ No newline at end of file