From 9abb18c8fac0f4927f5d8db91a84cb6b08b900cf Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 25 Apr 2026 15:44:54 +0800 Subject: [PATCH] fix test --- cmd/cmd_test.go | 38 ------ cmd/cmdtest/cmd_test.go | 237 +++++++++++++++++++++++++++++++++++++ cmd/{cmd.go => helper.go} | 0 cmd/main.go | 4 +- cmd/main_test.go | 241 ++++---------------------------------- 5 files changed, 262 insertions(+), 258 deletions(-) delete mode 100644 cmd/cmd_test.go create mode 100644 cmd/cmdtest/cmd_test.go rename cmd/{cmd.go => helper.go} (100%) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go deleted file mode 100644 index a36d05c76e0..00000000000 --- a/cmd/cmd_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v3" -) - -func TestDefaultCommand(t *testing.T) { - test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) { - called := false - cmd := &cli.Command{ - DefaultCommand: "test", - Commands: []*cli.Command{ - { - Name: "test", - Action: func(ctx context.Context, command *cli.Command) error { - retName, retValid := isValidDefaultSubCommand(command) - assert.Equal(t, expectedRetName, retName) - assert.Equal(t, expectedRetValid, retValid) - called = true - return nil - }, - }, - }, - } - assert.NoError(t, cmd.Run(t.Context(), args)) - assert.True(t, called) - } - test(t, []string{"./gitea"}, "", true) - test(t, []string{"./gitea", "test"}, "", true) - test(t, []string{"./gitea", "other"}, "other", false) -} diff --git a/cmd/cmdtest/cmd_test.go b/cmd/cmdtest/cmd_test.go new file mode 100644 index 00000000000..1de9390ec84 --- /dev/null +++ b/cmd/cmdtest/cmd_test.go @@ -0,0 +1,237 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// Tests here reload the config system multiple times with uncontrollable details. +// So it must be in a separate package, to avoid affect other tests + +package cmdtest + +import ( + "context" + "errors" + "fmt" + "io" + "path/filepath" + "strings" + "testing" + + "code.gitea.io/gitea/cmd" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} + +func makePathOutput(workPath, customPath, customConf string) string { + return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) +} + +func newTestApp(testCmd cli.Command) *cli.Command { + app := cmd.NewMainApp(cmd.AppVersion{}) + testCmd.Name = util.IfZero(testCmd.Name, "test-cmd") + cmd.PrepareSubcommandWithGlobalFlags(&testCmd) + app.Commands = append(app.Commands, &testCmd) + app.DefaultCommand = testCmd.Name + return app +} + +type runResult struct { + Stdout string + Stderr string + ExitCode int +} + +func runTestApp(app *cli.Command, args ...string) (runResult, error) { + outBuf := new(strings.Builder) + errBuf := new(strings.Builder) + app.Writer = outBuf + app.ErrWriter = errBuf + exitCode := -1 + defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)() + defer test.MockVariableValue(&cli.OsExiter, func(code int) { + if exitCode == -1 { + exitCode = code // save the exit code once and then reset the writer (to simulate the exit) + app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard + } + })() + err := cmd.RunMainApp(app, args...) + return runResult{outBuf.String(), errBuf.String(), exitCode}, err +} + +func TestCliCmd(t *testing.T) { + defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path") + defaultCustomPath := filepath.Join(defaultWorkPath, "custom") + defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") + defer setting.MockBuiltinPaths(defaultWorkPath, "", "")() + + cli.CommandHelpTemplate = "(command help template)" + cli.RootCommandHelpTemplate = "(app help template)" + cli.SubcommandHelpTemplate = "(subcommand help template)" + + cases := []struct { + env map[string]string + cmd string + exp string + }{ + // help commands + { + cmd: "./gitea -h", + exp: "DEFAULT CONFIGURATION:", + }, + { + cmd: "./gitea help", + exp: "DEFAULT CONFIGURATION:", + }, + + { + cmd: "./gitea -c /dev/null -h", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea help -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null test-cmd -h", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -c /dev/null -h", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -h -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null test-cmd help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -c /dev/null help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd help -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + // parse paths + { + cmd: "./gitea test-cmd", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), + }, + { + cmd: "./gitea -c /tmp/app.ini test-cmd", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + }, + { + cmd: "./gitea test-cmd -c /tmp/app.ini", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd", + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd --work-path /tmp/other", + exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd --config /tmp/app-other.ini", + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), + }, + } + + for _, c := range cases { + t.Run(c.cmd, func(t *testing.T) { + app := newTestApp(cli.Command{ + Action: func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }, + }) + for k, v := range c.env { + t.Setenv(k, v) + } + args := strings.Split(c.cmd, " ") // for test only, "split" is good enough + r, err := runTestApp(app, args...) + assert.NoError(t, err, c.cmd) + assert.NotEmpty(t, c.exp, c.cmd) + if !assert.Contains(t, r.Stdout, c.exp, c.cmd) { + t.Log("Full output:\n" + r.Stdout) + t.Log("Expected:\n" + c.exp) + } + }) + } +} + +func TestCliCmdError(t *testing.T) { + app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }}) + r, err := runTestApp(app, "./gitea", "test-cmd") + assert.Error(t, err) + assert.Equal(t, 1, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "Command error: normal error\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }}) + r, err = runTestApp(app, "./gitea", "test-cmd") + assert.Error(t, err) + assert.Equal(t, 2, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "exit error\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) + r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") + assert.Error(t, err) + assert.Equal(t, 1, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) + r, err = runTestApp(app, "./gitea", "test-cmd") + assert.NoError(t, err) + assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called + assert.Empty(t, r.Stdout) + assert.Empty(t, r.Stderr) +} + +func TestCliCmdBefore(t *testing.T) { + ctxNew := context.WithValue(context.Background(), any("key"), "value") + configValues := map[string]string{} + setting.CustomConf = "/tmp/any.ini" + var actionCtx context.Context + app := newTestApp(cli.Command{ + Before: func(context.Context, *cli.Command) (context.Context, error) { + configValues["before"] = setting.CustomConf + return ctxNew, nil + }, + Action: func(ctx context.Context, cmd *cli.Command) error { + configValues["action"] = setting.CustomConf + actionCtx = ctx + return nil + }, + }) + _, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd") + assert.NoError(t, err) + assert.Equal(t, ctxNew, actionCtx) + assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config") + assert.Equal(t, "/dev/null", configValues["action"]) +} diff --git a/cmd/cmd.go b/cmd/helper.go similarity index 100% rename from cmd/cmd.go rename to cmd/helper.go diff --git a/cmd/main.go b/cmd/main.go index a6b89a6fada..27d8cba2e90 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,7 +48,7 @@ DEFAULT CONFIGURATION: } } -func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) { +func PrepareSubcommandWithGlobalFlags(originCmd *cli.Command) { originBefore := originCmd.Before originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) { ctx = ctxOrig @@ -145,7 +145,7 @@ func NewMainApp(appVer AppVersion) *cli.Command { app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { - prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) + PrepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) } app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdStandalone...) diff --git a/cmd/main_test.go b/cmd/main_test.go index 3cd2c984e64..a36d05c76e0 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -1,233 +1,38 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. +// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package cmd import ( "context" - "errors" - "fmt" - "io" - "path/filepath" - "strings" "testing" - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - "code.gitea.io/gitea/modules/util" - "github.com/stretchr/testify/assert" "github.com/urfave/cli/v3" ) -func TestMain(m *testing.M) { - unittest.MainTest(m) -} - -func makePathOutput(workPath, customPath, customConf string) string { - return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) -} - -func newTestApp(testCmd cli.Command) *cli.Command { - app := NewMainApp(AppVersion{}) - testCmd.Name = util.IfZero(testCmd.Name, "test-cmd") - prepareSubcommandWithGlobalFlags(&testCmd) - app.Commands = append(app.Commands, &testCmd) - app.DefaultCommand = testCmd.Name - return app -} - -type runResult struct { - Stdout string - Stderr string - ExitCode int -} - -func runTestApp(app *cli.Command, args ...string) (runResult, error) { - outBuf := new(strings.Builder) - errBuf := new(strings.Builder) - app.Writer = outBuf - app.ErrWriter = errBuf - exitCode := -1 - defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)() - defer test.MockVariableValue(&cli.OsExiter, func(code int) { - if exitCode == -1 { - exitCode = code // save the exit code once and then reset the writer (to simulate the exit) - app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard - } - })() - err := RunMainApp(app, args...) - return runResult{outBuf.String(), errBuf.String(), exitCode}, err -} - -func TestCliCmd(t *testing.T) { - defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path") - defaultCustomPath := filepath.Join(defaultWorkPath, "custom") - defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") - defer setting.MockBuiltinPaths(defaultWorkPath, "", "")() - - cli.CommandHelpTemplate = "(command help template)" - cli.RootCommandHelpTemplate = "(app help template)" - cli.SubcommandHelpTemplate = "(subcommand help template)" - - cases := []struct { - env map[string]string - cmd string - exp string - }{ - // help commands - { - cmd: "./gitea -h", - exp: "DEFAULT CONFIGURATION:", - }, - { - cmd: "./gitea help", - exp: "DEFAULT CONFIGURATION:", - }, - - { - cmd: "./gitea -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea help -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -h -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd help -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - // parse paths - { - cmd: "./gitea test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), - }, - { - cmd: "./gitea -c /tmp/app.ini test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), - }, - { - cmd: "./gitea test-cmd -c /tmp/app.ini", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --work-path /tmp/other", - exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --config /tmp/app-other.ini", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), - }, - } - - for _, c := range cases { - t.Run(c.cmd, func(t *testing.T) { - app := newTestApp(cli.Command{ - Action: func(ctx context.Context, cmd *cli.Command) error { - _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil +func TestDefaultCommand(t *testing.T) { + test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) { + called := false + cmd := &cli.Command{ + DefaultCommand: "test", + Commands: []*cli.Command{ + { + Name: "test", + Action: func(ctx context.Context, command *cli.Command) error { + retName, retValid := isValidDefaultSubCommand(command) + assert.Equal(t, expectedRetName, retName) + assert.Equal(t, expectedRetValid, retValid) + called = true + return nil + }, }, - }) - for k, v := range c.env { - t.Setenv(k, v) - } - args := strings.Split(c.cmd, " ") // for test only, "split" is good enough - r, err := runTestApp(app, args...) - assert.NoError(t, err, c.cmd) - assert.NotEmpty(t, c.exp, c.cmd) - if !assert.Contains(t, r.Stdout, c.exp, c.cmd) { - t.Log("Full output:\n" + r.Stdout) - t.Log("Expected:\n" + c.exp) - } - }) + }, + } + assert.NoError(t, cmd.Run(t.Context(), args)) + assert.True(t, called) } -} - -func TestCliCmdError(t *testing.T) { - app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }}) - r, err := runTestApp(app, "./gitea", "test-cmd") - assert.Error(t, err) - assert.Equal(t, 1, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "Command error: normal error\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }}) - r, err = runTestApp(app, "./gitea", "test-cmd") - assert.Error(t, err) - assert.Equal(t, 2, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "exit error\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) - r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") - assert.Error(t, err) - assert.Equal(t, 1, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) - r, err = runTestApp(app, "./gitea", "test-cmd") - assert.NoError(t, err) - assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called - assert.Empty(t, r.Stdout) - assert.Empty(t, r.Stderr) -} - -func TestCliCmdBefore(t *testing.T) { - ctxNew := context.WithValue(context.Background(), any("key"), "value") - configValues := map[string]string{} - setting.CustomConf = "/tmp/any.ini" - var actionCtx context.Context - app := newTestApp(cli.Command{ - Before: func(context.Context, *cli.Command) (context.Context, error) { - configValues["before"] = setting.CustomConf - return ctxNew, nil - }, - Action: func(ctx context.Context, cmd *cli.Command) error { - configValues["action"] = setting.CustomConf - actionCtx = ctx - return nil - }, - }) - _, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd") - assert.NoError(t, err) - assert.Equal(t, ctxNew, actionCtx) - assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config") - assert.Equal(t, "/dev/null", configValues["action"]) + test(t, []string{"./gitea"}, "", true) + test(t, []string{"./gitea", "test"}, "", true) + test(t, []string{"./gitea", "other"}, "other", false) }