Skip to content

Commit 7dd54d6

Browse files
authored
Exit Codes (#37)
1 parent b666551 commit 7dd54d6

4 files changed

Lines changed: 93 additions & 86 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ Simply calling `codeowners` will kick off the cli on the current directory.
3030
| d | . | Directory: specifies the directory you want to use to lint the CODEOWNERS file |
3131
| f | | Format: specifies the format you want to return lint results |
3232

33+
##### Exit Codes
34+
35+
| Exit Code | Description |
36+
| ------------- | ---------------------------------------------------------------- |
37+
| 0 | Success: no errors returned |
38+
| 1 | Warnings: linter returned a few warnings but no errors |
39+
| 2 | Errors: linter returned a few errors |
40+
| 3 | Unexpected errors: errors that prevented the linter from running |
41+
3342
## Compatibility
3443

3544
:warning: This module is on a v0 mode and it is not ready to be used, once it reaches the v1 we will lock the API.

cmd/codeowners/linter.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"fmt"
45
"io"
56
"path/filepath"
67
"text/template"
@@ -14,33 +15,55 @@ type options struct {
1415
format string
1516
}
1617

17-
func run(wr io.Writer, opt options) error {
18+
type exitCode int
19+
20+
const (
21+
successCode exitCode = iota
22+
warningCode
23+
errorCode
24+
unexpectedErrorCode
25+
)
26+
27+
func run(wr io.Writer, opt options) exitCode {
1828
dir, err := filepath.Abs(opt.directory)
1929
if err != nil {
20-
return err
30+
fmt.Fprintf(wr, "Unexpected error when parsing directory: %v", err)
31+
return unexpectedErrorCode
2132
}
2233

23-
format := "{{range .}}{{ .Position }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]\n{{end}}"
34+
format := "{{ .Position }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]"
2435
if len(opt.format) > 0 {
2536
format = opt.format
2637
}
38+
format = fmt.Sprintf("%s\n", format)
2739
tpl, err := template.New("main").Parse(format)
2840
if err != nil {
29-
return err
41+
fmt.Fprintf(wr, "Unexpected error when parsing format: %v", err)
42+
return unexpectedErrorCode
3043
}
3144

3245
checkers := codeowners.AvailableCheckers()
3346

3447
checks, _ := codeowners.Check(dir, checkers...)
3548

36-
if len(checks) > 0 {
37-
err = tpl.Execute(wr, checks)
49+
code := successCode
50+
for _, check := range checks {
51+
err = tpl.Execute(wr, check)
3852
if err != nil {
39-
return err
53+
fmt.Fprintf(wr, "Unexpected error when writing results: %v", err)
54+
return unexpectedErrorCode
55+
}
56+
switch check.Severity {
57+
case codeowners.Error:
58+
if code < errorCode {
59+
code = errorCode
60+
}
61+
case codeowners.Warning:
62+
if code < warningCode {
63+
code = warningCode
64+
}
4065
}
41-
} else {
42-
wr.Write([]byte("Everything ok ;)\n"))
4366
}
4467

45-
return nil
68+
return code
4669
}

cmd/codeowners/linter_test.go

Lines changed: 45 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,104 +5,77 @@ import (
55
"testing"
66
)
77

8-
func testRun(opt options) (string, error) {
8+
func testRun(opt options) (string, exitCode) {
99
var output bytes.Buffer
10-
err := run(&output, opt)
11-
if err != nil {
12-
return "", err
13-
}
14-
return output.String(), nil
10+
exitCode := run(&output, opt)
11+
return output.String(), exitCode
1512
}
1613

17-
func TestPass(t *testing.T) {
18-
opt := options{
19-
directory: "../../test/data/pass",
20-
format: "",
21-
}
14+
func assertCode(t *testing.T, opt options, want exitCode) {
15+
_, got := testRun(opt)
2216

23-
got, err := testRun(opt)
24-
if err != nil {
25-
t.Error(err)
17+
if got != want {
18+
t.Errorf("Input: %v Want: %d Got: %d", opt, want, got)
2619
}
20+
}
2721

28-
want := `Everything ok ;)
29-
`
30-
if got != want {
31-
t.Errorf("Input: %v Want: '%s' Got: '%s'", opt, want, got)
22+
func assert(t *testing.T, opt options, wantCode exitCode, want string) {
23+
got, gotCode := testRun(opt)
24+
25+
if gotCode != wantCode || got != want {
26+
t.Errorf("Input: %v Want: %d '%s' Got: %d '%s'", opt, wantCode, want, gotCode, got)
3227
}
3328
}
3429

30+
func TestPass(t *testing.T) {
31+
assertCode(t, options{
32+
directory: "../../test/data/pass",
33+
format: "",
34+
}, successCode)
35+
}
36+
3537
func TestNoOwners(t *testing.T) {
36-
opt := options{
38+
assert(t, options{
3739
directory: "../../test/data/no_owners",
3840
format: "",
39-
}
40-
41-
got, err := testRun(opt)
42-
if err != nil {
43-
t.Error(err)
44-
}
45-
46-
want := `1 ::Error:: No owners specified [NoOwner]
47-
`
48-
if got != want {
49-
t.Errorf("Input: %v Want: '%s' Got: '%s'", opt, want, got)
50-
}
41+
}, errorCode, `1 ::Error:: No owners specified [NoOwner]
42+
`)
5143
}
5244

5345
func TestCustomFormat(t *testing.T) {
54-
opt := options{
46+
assert(t, options{
5547
directory: "../../test/data/noowners",
5648
format: "test",
57-
}
58-
59-
got, err := testRun(opt)
60-
if err != nil {
61-
t.Error(err)
62-
}
63-
64-
want := `test`
65-
if got != want {
66-
t.Errorf("Input: %v Want: '%s' Got: '%s'", opt, want, got)
67-
}
49+
}, errorCode, `test
50+
`)
6851
}
6952

70-
func TestInvalidFormat(t *testing.T) {
71-
opt := options{
53+
func TestInvalidFormatParse(t *testing.T) {
54+
assertCode(t, options{
7255
directory: "../../test/data/noowners",
73-
format: " {{template \"one\"}} ",
74-
}
75-
76-
_, err := testRun(opt)
77-
if err == nil {
78-
t.Errorf("Should have errored")
79-
}
56+
format: " {{template \"one ",
57+
}, unexpectedErrorCode)
58+
}
8059

81-
opt = options{
60+
func TestInvalidFormatExec(t *testing.T) {
61+
assertCode(t, options{
8262
directory: "../../test/data/noowners",
83-
format: " {{ . ",
84-
}
85-
86-
_, err = testRun(opt)
87-
if err == nil {
88-
t.Errorf("Should have errored")
89-
}
63+
format: " {{template \"one\"}} ",
64+
}, unexpectedErrorCode)
9065
}
9166

9267
func TestInvalidDirectory(t *testing.T) {
93-
opt := options{
68+
assert(t, options{
9469
directory: "'",
9570
format: "",
96-
}
97-
98-
got, err := testRun(opt)
99-
if err != nil {
100-
t.Error(err)
101-
}
71+
}, errorCode, `0 ::Error:: No CODEOWNERS file found [NoCodeowners]
72+
`)
73+
}
10274

103-
want := `0 ::Error:: No CODEOWNERS file found [NoCodeowners]
104-
`
105-
if got != want {
106-
t.Errorf("Input: %v Want: '%s' Got: '%s'", opt, want, got)
107-
}
75+
func TestMultipleCodeOwners(t *testing.T) {
76+
assert(t, options{
77+
directory: "../../test/data/multiple_codeowners",
78+
format: "",
79+
}, warningCode, `0 ::Warning:: Multiple CODEOWNERS files found (CODEOWNERS, docs/CODEOWNERS) [MultipleCodeowners]
80+
`)
10881
}

cmd/codeowners/main.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package main
44

55
import (
66
"flag"
7-
"log"
7+
"fmt"
88
"os"
99
)
1010

@@ -17,8 +17,10 @@ func main() {
1717
directory: *dir,
1818
format: *format,
1919
}
20-
21-
if err := run(os.Stdout, opt); err != nil {
22-
log.Fatal(err)
20+
exitCode := run(os.Stderr, opt)
21+
if exitCode == successCode {
22+
fmt.Println("Everything ok ;)")
23+
return
2324
}
25+
os.Exit(int(exitCode))
2426
}

0 commit comments

Comments
 (0)