blob: f95621cf5af4f2600c7d95b0453902336168b68e [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rules
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestFormatRuleResults(t *testing.T) {
tests := []struct {
title string
repo string // if empty string, treated as "go" repo
body string
want string
}{
{
title: `fmt: improve some things`, // We consider this a good commit message title.
body: goodCommitBody,
want: ``,
},
{
title: `a bad commit message title.`,
body: goodCommitBody,
want: `Possible problems detected:
1. The commit title should start with the primary affected package name followed by a colon, like "net/http: improve [...]".
2. The first word in the commit title after the package should be a lowercase English word (usually a verb).
3. The commit title should not end with a period.
The commit title and commit message body come from the GitHub PR title and description, and must be edited in the GitHub web interface (not via git). For instructions, see [here](https://go.dev/wiki/GerritBot/#how-does-gerritbot-determine-the-final-commit-message). For guidelines on commit messages for the Go project, see [here](https://go.dev/doc/contribute#commit_messages).
`,
},
{
title: `A bad vscode-go commit title`, // This verifies we complain about a "component" rather than "package".
repo: "vscode-go",
body: "This includes a bad bug format for vscode-go repo.\nFixes #1234",
want: `Possible problems detected:
1. The commit title should start with the primary affected component name followed by a colon, like "src/goInstallTools: improve [...]".
2. The first word in the commit title after the component should be a lowercase English word (usually a verb).
3. Do you have the right bug reference format? For the vscode-go repo, the format is usually 'Fixes golang/vscode-go#1234' or 'Updates golang/vscode-go#1234' at the end of the commit message.
The commit title and commit message body come from the GitHub PR title and description, and must be edited in the GitHub web interface (not via git). For instructions, see [here](https://go.dev/wiki/GerritBot/#how-does-gerritbot-determine-the-final-commit-message). For guidelines on commit messages for the Go project, see [here](https://go.dev/doc/contribute#commit_messages).
`,
},
{
title: goodCommitTitle,
body: "This commit body is missing a bug reference.",
want: `Possible problems detected:
1. You usually need to reference a bug number for all but trivial or cosmetic fixes. For this repo, the format is usually 'Fixes #12345' or 'Updates #12345' at the end of the commit message. Should you have a bug reference?
The commit title and commit message body come from the GitHub PR title and description, and must be edited in the GitHub web interface (not via git). For instructions, see [here](https://go.dev/wiki/GerritBot/#how-does-gerritbot-determine-the-final-commit-message). For guidelines on commit messages for the Go project, see [here](https://go.dev/doc/contribute#commit_messages).
`,
},
}
for _, tt := range tests {
t.Run("title "+tt.title, func(t *testing.T) {
commit := commitMessage(tt.title, tt.body, goodCommitFooters)
repo := "go"
if tt.repo != "" {
repo = tt.repo
}
change, err := ParseCommitMessage(repo, commit)
if err != nil {
t.Fatalf("ParseCommitMessage failed: %v", err)
}
results := Check(change)
got := FormatResults(results)
t.Log("FormatResults:\n" + got)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("checkRules() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestTitleRules(t *testing.T) {
tests := []struct {
title string
repo string // if empty string, treated as "go" repo
body string
want []string
}{
// We consider these good titles.
{
title: "fmt: good",
want: nil,
},
{
title: "go/types, types2: good",
want: nil,
},
{
title: "go/types, types2, types3: good",
want: nil,
},
{
title: "fmt: improve & make: Good",
want: nil,
},
{
title: "README: fix something",
want: nil,
},
{
title: "_content/doc/go1.21: fix something",
repo: "website",
want: nil,
},
{
title: "Fix a proposal", // We are lenient with proposal repo titles.
repo: "proposal",
want: nil,
},
// We consider these bad titles.
{
title: "bad.",
want: []string{
"title: no package found",
"title: no lowercase word after a first colon",
"title: ends with period",
},
},
{
title: "bad",
want: []string{
"title: no package found",
"title: no lowercase word after a first colon",
},
},
{
title: "fmt: bad.",
want: []string{
"title: ends with period",
},
},
{
title: "Fmt: bad",
want: []string{
"title: no package found",
},
},
{
title: "fmt: Bad",
want: []string{
"title: no lowercase word after a first colon",
},
},
{
title: "fmt: bad",
want: []string{
"title: no colon then single space after package",
},
},
{
title: "fmt: Bad",
want: []string{
"title: no colon then single space after package",
"title: no lowercase word after a first colon",
},
},
{
title: "fmt:bad",
want: []string{
"title: no colon then single space after package",
},
},
{
title: "fmt : bad",
want: []string{
"title: no package found",
},
},
{
title: ": bad",
want: []string{
"title: no package found",
},
},
{
title: " : bad",
want: []string{
"title: no package found",
},
},
{
title: "go/types types2: bad",
want: []string{
"title: no package found",
},
},
{
title: "a sentence, with a comma and colon: bad",
want: []string{
"title: no package found",
},
},
{
title: "a sentence with a colon: and a wrongly placed package fmt: bad",
want: []string{
"title: no package found",
},
},
{
title: "",
want: []string{
"title: no package found",
"title: no lowercase word after a first colon",
},
},
// We allow these titles (in interests of simplicity or leniency).
// TODO: are some of these considered an alternative good style?
{
title: "go/types,types2: we allow",
want: nil,
},
{
title: "cmd/{compile,link}: we allow",
want: nil,
},
{
title: "cmd/{compile, link}: we allow",
want: nil,
},
}
for _, tt := range tests {
t.Run("title "+tt.title, func(t *testing.T) {
commit := commitMessage(tt.title, goodCommitBody, goodCommitFooters)
repo := "go"
if tt.repo != "" {
repo = tt.repo
}
change, err := ParseCommitMessage(repo, commit)
if err != nil {
t.Fatalf("ParseCommitMessage failed: %v", err)
}
results := Check(change)
var got []string
for _, r := range results {
got = append(got, r.Name)
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("checkRules() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestBodyRules(t *testing.T) {
tests := []struct {
name string
title string // if empty string, we use goodCommitTitle
repo string // if empty string, treated as "go" repo
body string
want []string
}{
// We consider these good bodies.
{
name: "good",
body: goodCommitBody,
want: nil,
},
{
name: "good bug format for go repo",
repo: "go",
body: "This is This is body text.\n\nFixes #1234",
want: nil,
},
{
name: "good bug format for tools repo",
repo: "tools",
body: "This is This is body text.\n\nFixes golang/go#1234",
want: nil,
},
{
name: "good bug format for vscode-go",
repo: "vscode-go",
body: "This is This is body text.\n\nFixes golang/vscode-go#1234",
want: nil,
},
{
name: "good bug format for unknown repo",
repo: "some-future-repo",
body: "This is This is body text.\n\nFixes golang/go#1234",
want: nil,
},
{
name: "allowed long lines for benchstat output",
body: "Encode/format=json-48 1.718µ ± 1% 1.423µ ± 1% -17.20% (p=0.000 n=10)" +
strings.Repeat("Hello. ", 100) + "\n" + goodCommitBody,
want: nil,
},
{
name: "a trival fix",
title: "fmt: fix spelling mistakes",
body: "",
want: nil, // we don't flag short body or missing bug reference because "spelling" is in title
},
// Now we consider some bad bodies.
// First, some basic mistakes.
{
name: "too short",
body: "Short body",
want: []string{
"body: short",
"body: no bug reference candidate found",
},
},
{
name: "missing body",
body: "",
want: []string{
"body: short",
"body: no bug reference candidate found",
},
},
{
name: "not word wrapped",
body: strings.Repeat("Hello. ", 100) + "\n" + goodCommitBody,
want: []string{
"body: long lines",
},
},
{
name: "not a sentence",
body: "This is missing a period",
want: []string{
"body: no sentence candidates found",
"body: no bug reference candidate found",
},
},
{
name: "Signed-off-by",
body: "Signed-off-by: bad\n\n" + goodCommitBody,
want: []string{
"body: contains Signed-off-by",
},
},
{
name: "PR instructions",
body: "Delete these instructions once you have read and applied them.\n",
want: []string{
"body: still contains PR instructions",
"body: no bug reference candidate found",
},
},
// Next, mistakes in the repo-specific format or location of bug references.
{
name: "bad bug format for go repo",
repo: "go",
body: "This is body text.\n\nFixes golang/go#1234",
want: []string{
"body: bug format looks incorrect",
},
},
{
name: "bad bug format for tools repo",
repo: "tools",
body: "This is body text.\n\nFixes #1234",
want: []string{
"body: bug format looks incorrect",
},
},
{
name: "bad bug format for vscode-go",
repo: "vscode-go",
body: "This is body text.\n\nFixes #1234",
want: []string{
"body: bug format looks incorrect",
},
},
{
name: "bad bug format for unknown repo",
repo: "some-future-repo",
body: "This is body text.\n\nFixes #1234",
want: []string{
"body: bug format looks incorrect",
},
},
{
name: "bad bug location",
body: "This is body text.\nFixes #1234\nAnd a final line we should not have.",
want: []string{
"body: no bug reference candidate at end",
},
},
// We next have some good bodies that are markdown-ish,
// but we allow them.
{
name: "allowed markdown: mention regex in title",
title: "regexp: fix something",
body: "Example `.*`.\n" + goodCommitBody,
want: nil,
},
{
name: "allowed markdown: mention regex in body",
body: "A regex `.*`.\n" + goodCommitBody,
want: nil,
},
{
name: "allowed markdown: mention markdown",
body: "A markdown bug `and this is ok`.\n" + goodCommitBody,
want: nil,
},
{
name: "allowed markdown: might be go code",
body: " s := `raw`\n" + goodCommitBody,
want: nil,
},
// Examples of using markdown that we flag.
{
name: "markdown backticks",
body: "A variable `foo`.\n" + goodCommitBody,
want: []string{
"body: might use markdown",
},
},
{
name: "markdown block quote",
body: "Some code:\n```\nx := y\n```\n" + goodCommitBody,
want: []string{
"body: might use markdown",
},
},
{
name: "markdown link",
body: "[click here](https://example.com)\n" + goodCommitBody,
want: []string{
"body: might use markdown",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
title := goodCommitTitle
if tt.title != "" {
title = tt.title
}
commit := commitMessage(title, tt.body, goodCommitFooters)
repo := "go"
if tt.repo != "" {
repo = tt.repo
}
change, err := ParseCommitMessage(repo, commit)
if err != nil {
t.Fatalf("ParseCommitMessage failed: %v", err)
}
results := Check(change)
var got []string
for _, r := range results {
got = append(got, r.Name)
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("checkRules() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestParseCommitMessage(t *testing.T) {
tests := []struct {
name string
repo string
text string
want Change
wantErr bool
}{
// Bad examples.
{
name: "not enough lines",
text: "title",
want: Change{},
wantErr: true,
},
{
name: "second line not blank",
text: "title\nbad line\nBody 1",
want: Change{},
wantErr: true,
},
{
name: "no footer",
text: "title\n\nBody 1\n",
want: Change{},
wantErr: true,
},
{
name: "no footer and no body",
text: "title\n\n\n",
want: Change{},
wantErr: true,
},
// Good examples.
{
name: "good",
text: "title\n\nBody 1\n\nFooter: 1\n",
want: Change{
Title: "title",
Body: "Body 1",
},
wantErr: false,
},
{
name: "good with two body lines",
text: "title\n\nBody 1\nBody 2\n\nFooter: 1\n",
want: Change{
Title: "title",
Body: "Body 1\nBody 2",
},
wantErr: false,
},
{
name: "good with empty body",
text: "title\n\nFooter: 1\n",
want: Change{
Title: "title",
Body: "",
},
wantErr: false,
},
{
name: "good with extra blank lines after footer",
text: "title\n\nBody 1\n\nFooter: 1\n\n\n",
want: Change{
Title: "title",
Body: "Body 1",
},
wantErr: false,
},
{
name: "good with body line that looks like footer",
text: "title\n\nBody 1\nLink: example.com\n\nFooter: 1\n\n\n",
want: Change{
Title: "title",
Body: "Body 1\nLink: example.com",
},
wantErr: false,
},
{
name: "allowed cherry pick in footer", // Example from CL 346093.
text: "title\n\nBody 1\n\nFooter: 1\n(cherry picked from commit ebd07b13caf35114b32e7d6783b27902af4829ce)\n",
want: Change{
Title: "title",
Body: "Body 1",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseCommitMessage(tt.repo, tt.text)
if (err != nil) != tt.wantErr {
t.Errorf("ParseCommitMessage() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("checkRules() mismatch (-want +got):\n%s", diff)
}
})
}
}
// commitMessage helps us create valid commit messages while testing.
func commitMessage(title, body, footers string) string {
return title + "\n\n" + body + "\n\n" + footers
}
// Some auxiliary testing variables available for use when creating commit messages.
var (
goodCommitTitle = "pkg: a title that does not trigger any rules"
goodCommitBody = "A commit message body that does not trigger any rules.\n\nFixes #1234"
goodCommitFooters = `Change-Id: I1d8d10b142358983194ef2c389de4d9862d4ce97
GitHub-Last-Rev: 6d27e1471ee5dac0323a10b46e6e64e647068ecf
GitHub-Pull-Request: golang/build#69`
)