Skip to content

Commit fa431b3

Browse files
authored
Fix incorrect CLI exit code and duplicate error message (#26346) (#26347)
Backport #26346 Follow the CLI refactoring, and add tests.
1 parent 8a97cdd commit fa431b3

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed

cmd/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"strings"
9+
10+
"github.com/urfave/cli"
11+
)
12+
13+
func RunMainApp(app *cli.App, args ...string) error {
14+
err := app.Run(args)
15+
if err == nil {
16+
return nil
17+
}
18+
if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
19+
// the cli package should already have output the error message, so just exit
20+
cli.OsExiter(1)
21+
return err
22+
}
23+
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
24+
cli.OsExiter(1)
25+
return err
26+
}

cmd/main_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,81 @@
44
package cmd
55

66
import (
7+
"fmt"
8+
"io"
9+
"strings"
710
"testing"
811

912
"code.gitea.io/gitea/models/unittest"
13+
"code.gitea.io/gitea/modules/test"
14+
15+
"github.com/stretchr/testify/assert"
16+
"github.com/urfave/cli"
1017
)
1118

1219
func TestMain(m *testing.M) {
1320
unittest.MainTest(m, &unittest.TestOptions{
1421
GiteaRootPath: "..",
1522
})
1623
}
24+
25+
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
26+
app := cli.NewApp()
27+
app.HelpName = "gitea"
28+
testCmd := cli.Command{Name: "test-cmd", Action: testCmdAction}
29+
app.Commands = append(app.Commands, testCmd)
30+
return app
31+
}
32+
33+
type runResult struct {
34+
Stdout string
35+
Stderr string
36+
ExitCode int
37+
}
38+
39+
func runTestApp(app *cli.App, args ...string) (runResult, error) {
40+
outBuf := new(strings.Builder)
41+
errBuf := new(strings.Builder)
42+
app.Writer = outBuf
43+
app.ErrWriter = errBuf
44+
exitCode := -1
45+
defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)()
46+
defer test.MockVariableValue(&cli.OsExiter, func(code int) {
47+
if exitCode == -1 {
48+
exitCode = code // save the exit code once and then reset the writer (to simulate the exit)
49+
app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard
50+
}
51+
})()
52+
err := RunMainApp(app, args...)
53+
return runResult{outBuf.String(), errBuf.String(), exitCode}, err
54+
}
55+
56+
func TestCliCmdError(t *testing.T) {
57+
app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
58+
r, err := runTestApp(app, "./gitea", "test-cmd")
59+
assert.Error(t, err)
60+
assert.Equal(t, 1, r.ExitCode)
61+
assert.Equal(t, "", r.Stdout)
62+
assert.Equal(t, "Command error: normal error\n", r.Stderr)
63+
64+
app = newTestApp(func(ctx *cli.Context) error { return cli.NewExitError("exit error", 2) })
65+
r, err = runTestApp(app, "./gitea", "test-cmd")
66+
assert.Error(t, err)
67+
assert.Equal(t, 2, r.ExitCode)
68+
assert.Equal(t, "", r.Stdout)
69+
assert.Equal(t, "exit error\n", r.Stderr)
70+
71+
app = newTestApp(func(ctx *cli.Context) error { return nil })
72+
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
73+
assert.Error(t, err)
74+
assert.Equal(t, 1, r.ExitCode)
75+
assert.EqualValues(t, "Incorrect Usage: flag provided but not defined: -no-such\n\nNAME:\n gitea test-cmd - \n\nUSAGE:\n gitea test-cmd [arguments...]\n", r.Stdout)
76+
assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
77+
78+
app = newTestApp(func(ctx *cli.Context) error { return nil })
79+
r, err = runTestApp(app, "./gitea", "test-cmd")
80+
assert.NoError(t, err)
81+
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
82+
assert.Equal(t, "", r.Stdout)
83+
assert.Equal(t, "", r.Stderr)
84+
}

main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ func main() {
146146
app.Commands = append(app.Commands, subCmdWithIni...)
147147
app.Commands = append(app.Commands, subCmdStandalone...)
148148

149-
err := app.Run(os.Args)
150-
if err != nil {
151-
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err)
149+
cli.OsExiter = func(code int) {
150+
log.GetManager().Close()
151+
os.Exit(code)
152152
}
153-
153+
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp
154154
log.GetManager().Close()
155155
}
156156

modules/test/utils.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ func RedirectURL(resp http.ResponseWriter) string {
1616
func IsNormalPageCompleted(s string) bool {
1717
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
1818
}
19+
20+
func MockVariableValue[T any](p *T, v T) (reset func()) {
21+
old := *p
22+
*p = v
23+
return func() { *p = old }
24+
}

0 commit comments

Comments
 (0)