Skip to content

Commit 365920b

Browse files
committed
BUG/MINOR: fix unittest and some linting errors
1 parent 268f088 commit 365920b

2 files changed

Lines changed: 173 additions & 92 deletions

File tree

check-commit/check.go

Lines changed: 137 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,38 @@ package main
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"io/ioutil"
78
"log"
89
"os"
910
"os/exec"
1011
"regexp"
1112
"strings"
13+
"unicode/utf8"
1214

1315
yaml "gopkg.in/yaml.v2"
1416
)
1517

16-
const guidelinesLink = "Please refer to https://github.com/haproxy/haproxy/blob/master/CONTRIBUTING#L632"
17-
18-
type patchScope_t struct {
19-
Scope string `yaml:"Scope"`
20-
Values []string `yaml:"Values"`
21-
}
22-
type patchType_t struct {
18+
type patchTypeT struct {
2319
Values []string `yaml:"Values"`
2420
Scope string `yaml:"Scope"`
2521
}
26-
type tagAlternatives_t struct {
22+
23+
type tagAlternativesT struct {
2724
PatchTypes []string `yaml:"PatchTypes"`
2825
Optional bool `yaml:"Optional"`
2926
}
3027

31-
type prgConfig struct {
32-
PatchScopes map[string][]string `yaml:"PatchScopes"`
33-
PatchTypes map[string]patchType_t `yaml:"PatchTypes"`
34-
TagOrder []tagAlternatives_t `yaml:"TagOrder"`
35-
HelpText string `yaml:"HelpText"`
28+
type CommitPolicyConfig struct {
29+
PatchScopes map[string][]string `yaml:"PatchScopes"`
30+
PatchTypes map[string]patchTypeT `yaml:"PatchTypes"`
31+
TagOrder []tagAlternativesT `yaml:"TagOrder"`
32+
HelpText string `yaml:"HelpText"`
3633
}
3734

38-
var defaultConf string = `
35+
const (
36+
defaultConf = `
3937
---
4038
HelpText: "Please refer to https://github.com/haproxy/haproxy/blob/master/CONTRIBUTING#L632"
4139
PatchScopes:
@@ -70,84 +68,114 @@ TagOrder:
7068
- HAProxy Standard Feature Commit
7169
`
7270

73-
var myConfig prgConfig
71+
minSubjectParts = 3
72+
maxSubjectParts = 15
73+
minSubjectLen = 15
74+
maxSubjectLen = 100
75+
)
76+
77+
var ErrSubjectMessageFormat = errors.New("invalid subject message format")
78+
79+
func checkSubjectText(subject string) error {
80+
subjectLen := utf8.RuneCountInString(subject)
81+
subjectParts := strings.Fields(subject)
82+
subjectPartsLen := len(subjectParts)
83+
84+
if subject != strings.Join(subjectParts, " ") {
85+
log.Printf("malformatted subject string (trailing or double spaces?): '%s'\n", subject)
86+
}
87+
88+
if subjectPartsLen < minSubjectParts || subjectPartsLen > maxSubjectParts {
89+
return fmt.Errorf(
90+
"subject word count out of bounds [words %d < %d < %d] '%s': %w",
91+
minSubjectParts, subjectPartsLen, maxSubjectParts, subjectParts, ErrSubjectMessageFormat)
92+
}
93+
94+
if subjectLen < minSubjectLen || subjectLen > maxSubjectLen {
95+
return fmt.Errorf(
96+
"subject length out of bounds [len %d < %d < %d] '%s': %w",
97+
minSubjectLen, subjectLen, maxSubjectLen, subject, ErrSubjectMessageFormat)
98+
}
99+
100+
return nil
101+
}
74102

75-
func checkSubject(rawSubject []byte) error {
76-
r, _ := regexp.Compile("^(?P<match>(?P<tag>[A-Z]+)(\\/(?P<scope>[A-Z]+))?: )") // 5 subgroups, 4. is "/scope", 5. is "scope"
103+
func (c CommitPolicyConfig) CheckPatchTypes(tag, severity string, patchTypeName string) bool {
104+
tagScopeOK := false
77105

78-
t_tag := []byte("$tag")
79-
t_scope := []byte("$scope")
106+
for _, allowedTag := range c.PatchTypes[patchTypeName].Values {
107+
if tag == allowedTag {
108+
if severity == "" {
109+
tagScopeOK = true
110+
111+
break
112+
}
113+
114+
if c.PatchTypes[patchTypeName].Scope == "" {
115+
log.Printf("unable to verify severity %s without definitions", severity)
116+
117+
break // subject has severity but there is no definition to verify it
118+
}
119+
120+
for _, allowedScope := range c.PatchScopes[c.PatchTypes[patchTypeName].Scope] {
121+
if severity == allowedScope {
122+
tagScopeOK = true
123+
124+
break
125+
}
126+
}
127+
}
128+
}
129+
130+
return tagScopeOK
131+
}
132+
133+
var ErrTagScope = errors.New("invalid tag and or severity")
134+
135+
func (c CommitPolicyConfig) CheckSubject(rawSubject []byte) error {
136+
// 5 subgroups, 4. is "/severity", 5. is "severity"
137+
r := regexp.MustCompile(`^(?P<match>(?P<tag>[A-Z]+)(\/(?P<severity>[A-Z]+))?: )`)
138+
139+
tTag := []byte("$tag")
140+
tScope := []byte("$severity")
80141
result := []byte{}
81142

82-
var tag string
83-
var scope string
143+
var tag, severity string
144+
145+
for _, tagAlternative := range c.TagOrder {
84146

85-
for _, tagAlternative := range myConfig.TagOrder {
86-
// log.Printf("processing tagalternative %s\n", tagAlternative)
87147
tagOK := tagAlternative.Optional
88-
for _, pType := range tagAlternative.PatchTypes {
89-
// log.Printf("processing patchtype %s", pType)
148+
for _, pType := range tagAlternative.PatchTypes { // we allow more than one set of tags in a position
90149

91150
submatch := r.FindSubmatchIndex(rawSubject)
92151
if len(submatch) == 0 { // no match
93152
continue
94153
}
154+
95155
tagPart := rawSubject[submatch[0]:submatch[1]]
96156

97-
tag = string(r.Expand(result, t_tag, tagPart, submatch))
98-
scope = string(r.Expand(result, t_scope, tagPart, submatch))
99-
100-
tagScopeOK := false
101-
102-
for _, allowedTag := range myConfig.PatchTypes[pType].Values {
103-
if tag == allowedTag {
104-
// log.Printf("found allowed tag %s\n", tag)
105-
if scope == "" {
106-
tagScopeOK = true
107-
} else {
108-
if myConfig.PatchTypes[pType].Scope == "" {
109-
log.Printf("subject scope problem")
110-
break // subject has scope but there is no definition to verify it
111-
}
112-
for _, allowedScope := range myConfig.PatchScopes[myConfig.PatchTypes[pType].Scope] {
113-
if scope == allowedScope {
114-
tagScopeOK = true
115-
}
116-
}
117-
}
118-
}
119-
}
120-
if tagScopeOK { //we found what we were looking for, so consume input
157+
tag = string(r.Expand(result, tTag, tagPart, submatch))
158+
severity = string(r.Expand(result, tScope, tagPart, submatch))
159+
160+
if c.CheckPatchTypes(tag, severity, pType) { // we found what we were looking for, so consume input
121161
rawSubject = rawSubject[submatch[1]:]
162+
tagOK = tagOK || true
122163
}
123-
tagOK = tagOK || tagScopeOK
124-
// log.Printf("tag is %s, scope is %s, rest is %s\n", tag, scope, rawSubject)
125164
}
165+
126166
if !tagOK {
127-
return fmt.Errorf("invalid tag or no tag found: %s/%s", tag, scope)
167+
return fmt.Errorf("invalid tag or no tag found: %w", ErrTagScope)
128168
}
129169
}
130170

131-
subject := string(rawSubject)
132-
subjectParts := strings.Fields(subject)
171+
return checkSubjectText(string(rawSubject))
172+
}
133173

134-
if subject != strings.Join(subjectParts, " ") {
135-
log.Printf("malformatted subject string (trailing or double spaces?): '%s'\n", subject)
136-
}
174+
func (c CommitPolicyConfig) IsEmpty() bool {
175+
c1, _ := yaml.Marshal(c)
176+
c2, _ := yaml.Marshal(new(CommitPolicyConfig)) // empty config
137177

138-
if len(subjectParts) < 3 {
139-
return fmt.Errorf("Too short or meaningless commit subject [words %d < 3] '%s'", len(subjectParts), subjectParts)
140-
}
141-
if len(subject) < 15 {
142-
return fmt.Errorf("Too short or meaningless commit subject [len %d < 15]'%s'", len(subject), subject)
143-
}
144-
if len(subjectParts) > 15 {
145-
return fmt.Errorf("Too long commit subject [words %d > 15 - use msg body] '%s'", len(subjectParts), subjectParts)
146-
}
147-
if len(subject) > 100 {
148-
return fmt.Errorf("Too long commit subject [len %d > 100] '%s'", len(subject), subject)
149-
}
150-
return nil
178+
return string(c1) == string(c2)
151179
}
152180

153181
type gitEnv struct {
@@ -161,70 +189,90 @@ type gitEnvVars struct {
161189
BaseVar string
162190
}
163191

164-
var knownVars []gitEnvVars = []gitEnvVars{
165-
{"Github", "GITHUB_REF", "GITHUB_BASE_REF"},
166-
{"Gitlab", "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", "CI_MERGE_REQUEST_TARGET_BRANCH_NAME"},
167-
}
192+
var ErrGitEnvironment = errors.New("git environment error")
168193

169194
func readGitEnvironment() (*gitEnv, error) {
195+
knownVars := []gitEnvVars{
196+
{"Github", "GITHUB_REF", "GITHUB_BASE_REF"},
197+
{"Gitlab", "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME", "CI_MERGE_REQUEST_TARGET_BRANCH_NAME"},
198+
}
199+
170200
var ref, base string
171201
for _, vars := range knownVars {
172202
ref = os.Getenv(vars.RefVar)
173203
base = os.Getenv(vars.BaseVar)
204+
174205
if ref != "" && base != "" {
175206
log.Printf("detected %s environment\n", vars.EnvName)
207+
176208
return &gitEnv{
177209
Ref: ref,
178210
Base: base,
179211
}, nil
180212
}
181213
}
182-
return nil, fmt.Errorf("no suitable git environment variables found")
214+
215+
return nil, fmt.Errorf("no suitable git environment variables found %w", ErrGitEnvironment)
183216
}
184217

185-
func main() {
218+
func LoadCommitPolicy(filename string) (CommitPolicyConfig, error) {
219+
var commitPolicy CommitPolicyConfig
220+
186221
var config string
187-
if data, err := ioutil.ReadFile(".check-commit.yml"); err == nil {
188-
config = string(data)
189-
} else {
190-
log.Printf("no config found, using built-in fallback configuration (HAProxy defaults)")
222+
223+
if data, err := ioutil.ReadFile(filename); err != nil {
224+
log.Printf("error reading config (%s), using built-in fallback configuration (HAProxy defaults)", err)
225+
191226
config = defaultConf
227+
} else {
228+
config = string(data)
192229
}
193230

194-
if err := yaml.Unmarshal([]byte(config), &myConfig); err != nil {
231+
if err := yaml.Unmarshal([]byte(config), &commitPolicy); err != nil {
232+
return CommitPolicyConfig{}, fmt.Errorf("error loading commit policy: %w", err)
233+
}
234+
235+
return commitPolicy, nil
236+
}
237+
238+
func main() {
239+
commitPolicy, err := LoadCommitPolicy(".check-commit.yml")
240+
if err != nil {
195241
log.Fatalf("error reading configuration: %s", err)
196242
}
197-
c1, _ := yaml.Marshal(myConfig)
198-
c2, _ := yaml.Marshal(prgConfig{}) // empty config
199-
if string(c1) == string(c2) {
243+
244+
if commitPolicy.IsEmpty() {
200245
log.Printf("WARNING: using empty configuration (i.e. no verification)")
201246
}
202247

203-
var out []byte
204-
205248
gitEnv, err := readGitEnvironment()
206249
if err != nil {
207250
log.Fatalf("couldn't auto-detect running environment, please set GITHUB_REF and GITHUB_BASE_REF manually")
208251
}
209252

210-
out, err = exec.Command("git", "log", fmt.Sprintf("%s...%s", gitEnv.Base, gitEnv.Ref), "--pretty=format:'%s'").Output()
253+
commitRange := fmt.Sprintf("%s...%s", gitEnv.Base, gitEnv.Ref)
254+
255+
out, err := exec.Command("git", "log", commitRange, "--pretty=format:'%s'").Output()
211256
if err != nil {
212257
log.Fatalf("Unable to get log subject '%s'", err)
213258
}
214259

215260
// Check subject
216261
errors := false
262+
217263
for _, subject := range bytes.Split(out, []byte("\n")) {
264+
218265
subject = bytes.Trim(subject, "'")
219-
if err := checkSubject(subject); err != nil {
266+
if err := commitPolicy.CheckSubject(subject); err != nil {
220267
log.Printf("%s, original subject message '%s'", err, string(subject))
268+
221269
errors = true
222270
}
223271
}
224272

225273
if errors {
226274
log.Printf("encountered one or more commit message errors\n")
227-
log.Fatalf("%s\n", myConfig.HelpText)
275+
log.Fatalf("%s\n", commitPolicy.HelpText)
228276
} else {
229277
log.Printf("check completed without errors\n")
230278
}

check-commit/check_test.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ package main
22

33
import "testing"
44

5-
func Test_checkSubject(t *testing.T) {
5+
func TestCheckSubject(t *testing.T) {
6+
t.Parallel()
7+
8+
c, _ := LoadCommitPolicy("")
9+
610
type args struct {
711
subject string
812
}
13+
914
tests := []struct {
1015
name string
1116
args args
1217
wantErr bool
1318
}{
1419
{
15-
name: "bug",
20+
name: "valid type/severity",
1621
args: args{subject: "BUG/MEDIUM: config: add default location of path to the configuration file"},
1722
wantErr: false,
1823
},
@@ -21,10 +26,38 @@ func Test_checkSubject(t *testing.T) {
2126
args: args{subject: "BUG/MEDIUM: config: default"},
2227
wantErr: true,
2328
},
29+
{
30+
name: "missing severity",
31+
args: args{subject: "BUG/: config: default"},
32+
wantErr: true,
33+
},
34+
{
35+
name: "wrong tag",
36+
args: args{subject: "WRONG: config: default"},
37+
wantErr: true,
38+
},
39+
{
40+
name: "wrong severity",
41+
args: args{subject: "BUG/WRONG: config: default"},
42+
wantErr: true,
43+
},
44+
{
45+
name: "double spaces",
46+
args: args{subject: "BUG/MEDIUM: config: default"},
47+
wantErr: true,
48+
},
49+
{
50+
name: "trailing spaces",
51+
args: args{subject: "BUG/MEDIUM: config: default "},
52+
wantErr: true,
53+
},
2454
}
55+
2556
for _, tt := range tests {
57+
tt := tt
2658
t.Run(tt.name, func(t *testing.T) {
27-
if err := checkSubject(tt.args.subject); (err != nil) != tt.wantErr {
59+
t.Parallel()
60+
if err := c.CheckSubject([]byte(tt.args.subject)); (err != nil) != tt.wantErr {
2861
t.Errorf("checkSubject() error = %v, wantErr %v", err, tt.wantErr)
2962
}
3063
})

0 commit comments

Comments
 (0)