0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-07 08:13:50 +02:00

Fix cmd tests by mocking builtin paths (#37369)

After 07ada3666b, PrepareConsoleLoggerLevel can fail in tests when
InstallLock is true, due to the incorrect config file is loaded. This PR
fixes cmd test setup by mocking builtin paths

Fixes #37368

---------

Co-authored-by: Morgan PEYRE <morgan.peyre@brickcode.tech>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Morgan Peyre 2026-04-22 22:58:59 +02:00 committed by GitHub
parent 9894ebb79c
commit 8cfcef32c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 101 additions and 76 deletions

View File

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"io"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -82,7 +83,9 @@ func TestChangePasswordCommand(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
err := microcmdUserChangePassword().Run(ctx, tc.args) cmd := microcmdUserChangePassword()
cmd.Writer, cmd.ErrWriter = io.Discard, io.Discard
err := cmd.Run(ctx, tc.args)
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr) require.Contains(t, err.Error(), tc.expectedErr)
}) })

View File

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"io"
"path/filepath" "path/filepath"
"testing" "testing"
@ -107,6 +108,7 @@ func TestCertCommandFailures(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
app := cmdCert() app := cmdCert()
app.Writer, app.ErrWriter = io.Discard, io.Discard
tempDir := t.TempDir() tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem") certFile := filepath.Join(tempDir, "cert.pem")

View File

@ -124,7 +124,7 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cl
if setting.InstallLock { if setting.InstallLock {
// During config loading, there might also be logs (for example: deprecation warnings). // During config loading, there might also be logs (for example: deprecation warnings).
// It must make sure that console logger is set up before config is loaded. // It must make sure that console logger is set up before config is loaded.
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.") log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it. CustomConf=%s", setting.CustomConf)
return nil, errors.New("console logger must be setup before config is loaded") return nil, errors.New("console logger must be setup before config is loaded")
} }
level := defaultLevel level := defaultLevel

View File

@ -62,9 +62,10 @@ func runTestApp(app *cli.Command, args ...string) (runResult, error) {
} }
func TestCliCmd(t *testing.T) { func TestCliCmd(t *testing.T) {
defaultWorkPath := filepath.Dir(setting.AppPath) defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path")
defaultCustomPath := filepath.Join(defaultWorkPath, "custom") defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
defer setting.MockBuiltinPaths(defaultWorkPath, "", "")()
cli.CommandHelpTemplate = "(command help template)" cli.CommandHelpTemplate = "(command help template)"
cli.RootCommandHelpTemplate = "(app help template)" cli.RootCommandHelpTemplate = "(app help template)"
@ -157,7 +158,6 @@ func TestCliCmd(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) { t.Run(c.cmd, func(t *testing.T) {
defer test.MockVariableValue(&setting.InstallLock, false)()
app := newTestApp(cli.Command{ app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))

View File

@ -202,16 +202,15 @@ func LoadTableSchemasMap(t *testing.T, x *xorm.Engine) map[string]*schemas.Table
func mainTest(m *testing.M) int { func mainTest(m *testing.M) int {
testlogger.Init() testlogger.Init()
setting.SetupGiteaTestEnv()
tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data") tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data")
if err != nil { if err != nil {
testlogger.Panicf("Unable to create temporary data path %v\n", err) testlogger.Panicf("Unable to create temporary data path %v\n", err)
} }
defer cleanup() defer cleanup()
setting.AppDataPath = tmpDataPath setting.AppDataPath = tmpDataPath
unittest.InitSettingsForTesting()
if err = git.InitFull(); err != nil { if err = git.InitFull(); err != nil {
testlogger.Panicf("Unable to InitFull: %v\n", err) testlogger.Panicf("Unable to InitFull: %v\n", err)
} }

View File

@ -13,10 +13,8 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/setting/config"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
@ -29,37 +27,6 @@ import (
"xorm.io/xorm/names" "xorm.io/xorm/names"
) )
// InitSettingsForTesting initializes config provider and load common settings for tests
func InitSettingsForTesting() {
setting.SetupGiteaTestEnv()
log.OsExiter = func(code int) {
if code != 0 {
// non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details
panic(fmt.Errorf("non-zero exit code during testing: %d", code))
}
os.Exit(0)
}
if setting.CustomConf == "" {
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
_ = os.Remove(setting.CustomConf)
}
// init paths and config system for testing
getTestEnv := func(key string) string {
return ""
}
setting.InitWorkPathAndCommonConfig(getTestEnv, setting.ArgWorkPathAndCustomConf{CustomConf: setting.CustomConf})
if err := setting.PrepareAppDataPath(); err != nil {
log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
}
// register the dummy hash algorithm function used in the test fixtures
_ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
}
// TestOptions represents test options // TestOptions represents test options
type TestOptions struct { type TestOptions struct {
FixtureFiles []string FixtureFiles []string
@ -75,11 +42,12 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
func mainTest(m *testing.M, testOptsArg ...*TestOptions) int { func mainTest(m *testing.M, testOptsArg ...*TestOptions) int {
testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) testOpts := util.OptionalArg(testOptsArg, &TestOptions{})
InitSettingsForTesting() setting.SetupGiteaTestEnv()
giteaRoot := setting.GetGiteaTestSourceRoot() giteaRoot := setting.GetGiteaTestSourceRoot()
fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles}
if err := CreateTestEngine(fixturesOpts); err != nil { if err := CreateTestEngine(fixturesOpts); err != nil {
testlogger.Panicf("Error creating test engine: %v\n", err) _, _ = fmt.Fprintf(os.Stderr, "Error creating test database engine: %v\n", err)
os.Exit(1)
} }
setting.AppURL = "https://try.gitea.io/" setting.AppURL = "https://try.gitea.io/"

View File

@ -198,6 +198,12 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP
CustomConf = tmpCustomConf.Value CustomConf = tmpCustomConf.Value
} }
func MockBuiltinPaths(workPath, customPath, customConf string) func() {
oldApp, oldCustom, oldConf := appWorkPathBuiltin, customPathBuiltin, customConfBuiltin
appWorkPathBuiltin, customPathBuiltin, customConfBuiltin = workPath, customPath, customConf
return func() { appWorkPathBuiltin, customPathBuiltin, customConfBuiltin = oldApp, oldCustom, oldConf }
}
// AppDataTempDir returns a managed temporary directory for the application data. // AppDataTempDir returns a managed temporary directory for the application data.
// Using empty sub will get the managed base temp directory, and it's safe to delete it. // Using empty sub will get the managed base temp directory, and it's safe to delete it.
// Gitea only creates subdirectories under it, but not the APP_TEMP_PATH directory itself. // Gitea only creates subdirectories under it, but not the APP_TEMP_PATH directory itself.

View File

@ -10,6 +10,8 @@ import (
"runtime" "runtime"
"strings" "strings"
"code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -25,48 +27,84 @@ func SetupGiteaTestEnv() {
} }
IsInTesting = true IsInTesting = true
giteaRoot := os.Getenv("GITEA_TEST_ROOT")
if giteaRoot == "" { log.OsExiter = func(code int) {
_, filename, _, _ := runtime.Caller(0) if code != 0 {
giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename))) // non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details
fixturesDir := filepath.Join(giteaRoot, "models", "fixtures") panic(fmt.Errorf("non-zero exit code during testing: %d", code))
if _, err := os.Stat(fixturesDir); err != nil { }
panic("in gitea source code directory, fixtures directory not found: " + fixturesDir) os.Exit(0)
}
initGiteaRoot := func() string {
giteaRoot := os.Getenv("GITEA_TEST_ROOT")
if giteaRoot == "" {
_, filename, _, _ := runtime.Caller(0)
giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename)))
fixturesDir := filepath.Join(giteaRoot, "models", "fixtures")
if _, err := os.Stat(fixturesDir); err != nil {
panic("in gitea source code directory, fixtures directory not found: " + fixturesDir)
}
}
giteaTestSourceRoot = &giteaRoot
return giteaRoot
}
initGiteaPaths := func() {
appWorkPathBuiltin = *giteaTestSourceRoot
AppWorkPath = appWorkPathBuiltin
AppPath = filepath.Join(AppWorkPath, "gitea") + util.Iif(IsWindows, ".exe", "")
StaticRootPath = AppWorkPath // need to load assets (options, public) from the source code directory for testing
}
initGiteaConf := func() string {
// giteaConf (GITEA_CONF) must be relative because it is used in the git hooks as "$GITEA_ROOT/$GITEA_CONF"
giteaConf := os.Getenv("GITEA_TEST_CONF")
if giteaConf == "" {
// if no GITEA_TEST_CONF, then it is in unit test, use a temp (non-existing / empty) config file
giteaConf = "custom/conf/app-test-tmp.ini"
customConfBuiltin = filepath.Join(AppWorkPath, giteaConf)
CustomConf = customConfBuiltin
_ = os.Remove(CustomConf)
} else {
// CustomConf must be absolute path to make tests pass,
CustomConf = filepath.Join(AppWorkPath, giteaConf)
}
return giteaConf
}
cleanUpEnv := func() {
// also unset unnecessary env vars for testing (only keep "GITEA_TEST_*" ones)
UnsetUnnecessaryEnvVars()
for _, env := range os.Environ() {
if strings.HasPrefix(env, "GIT_") || (strings.HasPrefix(env, "GITEA_") && !strings.HasPrefix(env, "GITEA_TEST_")) {
k, _, _ := strings.Cut(env, "=")
_ = os.Unsetenv(k)
}
} }
} }
appWorkPathBuiltin = giteaRoot initWorkPathAndConfig := func() {
AppWorkPath = giteaRoot // init paths and config system for testing
AppPath = filepath.Join(giteaRoot, "gitea") + util.Iif(IsWindows, ".exe", "") getTestEnv := func(key string) string { return "" }
StaticRootPath = giteaRoot // need to load assets (options, public) from the source code directory for testing InitWorkPathAndCommonConfig(getTestEnv, ArgWorkPathAndCustomConf{CustomConf: CustomConf})
// giteaConf (GITEA_CONF) must be relative because it is used in the git hooks as "$GITEA_ROOT/$GITEA_CONF" if err := PrepareAppDataPath(); err != nil {
giteaConf := os.Getenv("GITEA_TEST_CONF") log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
if giteaConf == "" {
// By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger.
// It's easier for developers to debug bugs step by step with a debugger.
// Notice: when doing "ssh push", Gitea executes sub processes, debugger won't work for the sub processes.
giteaConf = "tests/sqlite.ini"
_, _ = fmt.Fprintf(os.Stderr, "Environment variable GITEA_TEST_CONF not set - defaulting to %s\n", giteaConf)
if !EnableSQLite3 {
_, _ = fmt.Fprintf(os.Stderr, "sqlite3 requires: -tags sqlite,sqlite_unlock_notify\n")
os.Exit(1)
} }
}
// CustomConf must be absolute path to make tests pass,
CustomConf = filepath.Join(AppWorkPath, giteaConf)
// also unset unnecessary env vars for testing (only keep "GITEA_TEST_*" ones) // register the dummy hash algorithm function used in the test fixtures
UnsetUnnecessaryEnvVars() _ = hash.Register("dummy", hash.NewDummyHasher)
for _, env := range os.Environ() { PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
if strings.HasPrefix(env, "GIT_") || (strings.HasPrefix(env, "GITEA_") && !strings.HasPrefix(env, "GITEA_TEST_")) {
k, _, _ := strings.Cut(env, "=")
_ = os.Unsetenv(k)
}
} }
giteaRoot := initGiteaRoot()
initGiteaPaths()
giteaConf := initGiteaConf()
cleanUpEnv()
initWorkPathAndConfig()
// TODO: some git repo hooks (test fixtures) still use these env variables, need to be refactored in the future // TODO: some git repo hooks (test fixtures) still use these env variables, need to be refactored in the future
_ = os.Setenv("GITEA_ROOT", giteaRoot) _ = os.Setenv("GITEA_ROOT", giteaRoot)
_ = os.Setenv("GITEA_CONF", giteaConf) // test fixture git hooks use "$GITEA_ROOT/$GITEA_CONF" in their scripts _ = os.Setenv("GITEA_CONF", giteaConf) // test fixture git hooks use "$GITEA_ROOT/$GITEA_CONF" in their scripts
giteaTestSourceRoot = &giteaRoot
} }

View File

@ -37,7 +37,7 @@ var currentEngine *xorm.Engine
func initMigrationTest(t *testing.T) func() { func initMigrationTest(t *testing.T) func() {
testlogger.Init() testlogger.Init()
unittest.InitSettingsForTesting() setting.SetupGiteaTestEnv()
assert.NotEmpty(t, setting.RepoRootPath) assert.NotEmpty(t, setting.RepoRootPath)
assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))

View File

@ -6,6 +6,7 @@ package tests
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -26,7 +27,15 @@ import (
func InitTest() { func InitTest() {
testlogger.Init() testlogger.Init()
unittest.InitSettingsForTesting() if os.Getenv("GITEA_TEST_CONF") == "" {
// By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger.
// It's easier for developers to debug bugs step by step with a debugger.
// Notice: when doing "ssh push", Gitea executes sub processes, debugger won't work for the sub processes.
giteaConf := "tests/sqlite.ini"
_ = os.Setenv("GITEA_TEST_CONF", giteaConf)
_, _ = fmt.Fprintf(os.Stderr, "Environment variable GITEA_TEST_CONF not set - defaulting to %s\n", giteaConf)
}
setting.SetupGiteaTestEnv()
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
if err := git.InitFull(); err != nil { if err := git.InitFull(); err != nil {