golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gerritbot/internal/rules/rules_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rules 6 7 import ( 8 "strings" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 ) 13 14 func TestFormatRuleResults(t *testing.T) { 15 tests := []struct { 16 title string 17 repo string // if empty string, treated as "go" repo 18 body string 19 want string 20 }{ 21 { 22 title: `fmt: improve some things`, // We consider this a good commit message title. 23 body: goodCommitBody, 24 want: ``, 25 }, 26 { 27 title: `a bad commit message title.`, 28 body: goodCommitBody, 29 want: `Possible problems detected: 30 1. The commit title should start with the primary affected package name followed by a colon, like "net/http: improve [...]". 31 2. The first word in the commit title after the package should be a lowercase English word (usually a verb). 32 3. The commit title should not end with a period. 33 34 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). 35 `, 36 }, 37 { 38 title: `A bad vscode-go commit title`, // This verifies we complain about a "component" rather than "package". 39 repo: "vscode-go", 40 body: "This includes a bad bug format for vscode-go repo.\nFixes #1234", 41 want: `Possible problems detected: 42 1. The commit title should start with the primary affected component name followed by a colon, like "src/goInstallTools: improve [...]". 43 2. The first word in the commit title after the component should be a lowercase English word (usually a verb). 44 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. 45 46 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). 47 `, 48 }, 49 { 50 title: goodCommitTitle, 51 body: "This commit body is missing a bug reference.", 52 want: `Possible problems detected: 53 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? 54 55 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). 56 `, 57 }, 58 } 59 for _, tt := range tests { 60 t.Run("title "+tt.title, func(t *testing.T) { 61 commit := commitMessage(tt.title, tt.body, goodCommitFooters) 62 repo := "go" 63 if tt.repo != "" { 64 repo = tt.repo 65 } 66 change, err := ParseCommitMessage(repo, commit) 67 if err != nil { 68 t.Fatalf("ParseCommitMessage failed: %v", err) 69 } 70 results := Check(change) 71 got := FormatResults(results) 72 t.Log("FormatResults:\n" + got) 73 if diff := cmp.Diff(tt.want, got); diff != "" { 74 t.Errorf("checkRules() mismatch (-want +got):\n%s", diff) 75 } 76 }) 77 } 78 } 79 80 func TestTitleRules(t *testing.T) { 81 tests := []struct { 82 title string 83 repo string // if empty string, treated as "go" repo 84 body string 85 want []string 86 }{ 87 // We consider these good titles. 88 { 89 title: "fmt: good", 90 want: nil, 91 }, 92 { 93 title: "go/types, types2: good", 94 want: nil, 95 }, 96 { 97 title: "go/types, types2, types3: good", 98 want: nil, 99 }, 100 { 101 title: "fmt: improve & make: Good", 102 want: nil, 103 }, 104 { 105 title: "README: fix something", 106 want: nil, 107 }, 108 { 109 title: "_content/doc/go1.21: fix something", 110 repo: "website", 111 want: nil, 112 }, 113 { 114 title: "Fix a proposal", // We are lenient with proposal repo titles. 115 repo: "proposal", 116 want: nil, 117 }, 118 119 // We consider these bad titles. 120 { 121 title: "bad.", 122 want: []string{ 123 "title: no package found", 124 "title: no lowercase word after a first colon", 125 "title: ends with period", 126 }, 127 }, 128 { 129 title: "bad", 130 want: []string{ 131 "title: no package found", 132 "title: no lowercase word after a first colon", 133 }, 134 }, 135 { 136 title: "fmt: bad.", 137 want: []string{ 138 "title: ends with period", 139 }, 140 }, 141 { 142 title: "Fmt: bad", 143 want: []string{ 144 "title: no package found", 145 }, 146 }, 147 { 148 title: "fmt: Bad", 149 want: []string{ 150 "title: no lowercase word after a first colon", 151 }, 152 }, 153 { 154 title: "fmt: bad", 155 want: []string{ 156 "title: no colon then single space after package", 157 }, 158 }, 159 { 160 title: "fmt: Bad", 161 want: []string{ 162 "title: no colon then single space after package", 163 "title: no lowercase word after a first colon", 164 }, 165 }, 166 { 167 title: "fmt:bad", 168 want: []string{ 169 "title: no colon then single space after package", 170 }, 171 }, 172 { 173 title: "fmt : bad", 174 want: []string{ 175 "title: no package found", 176 }, 177 }, 178 { 179 title: ": bad", 180 want: []string{ 181 "title: no package found", 182 }, 183 }, 184 { 185 title: " : bad", 186 want: []string{ 187 "title: no package found", 188 }, 189 }, 190 { 191 title: "go/types types2: bad", 192 want: []string{ 193 "title: no package found", 194 }, 195 }, 196 { 197 title: "a sentence, with a comma and colon: bad", 198 want: []string{ 199 "title: no package found", 200 }, 201 }, 202 { 203 title: "a sentence with a colon: and a wrongly placed package fmt: bad", 204 want: []string{ 205 "title: no package found", 206 }, 207 }, 208 { 209 title: "", 210 want: []string{ 211 "title: no package found", 212 "title: no lowercase word after a first colon", 213 }, 214 }, 215 216 // We allow these titles (in interests of simplicity or leniency). 217 // TODO: are some of these considered an alternative good style? 218 { 219 title: "go/types,types2: we allow", 220 want: nil, 221 }, 222 { 223 title: "cmd/{compile,link}: we allow", 224 want: nil, 225 }, 226 { 227 title: "cmd/{compile, link}: we allow", 228 want: nil, 229 }, 230 } 231 for _, tt := range tests { 232 t.Run("title "+tt.title, func(t *testing.T) { 233 commit := commitMessage(tt.title, goodCommitBody, goodCommitFooters) 234 repo := "go" 235 if tt.repo != "" { 236 repo = tt.repo 237 } 238 change, err := ParseCommitMessage(repo, commit) 239 if err != nil { 240 t.Fatalf("ParseCommitMessage failed: %v", err) 241 } 242 results := Check(change) 243 244 var got []string 245 for _, r := range results { 246 got = append(got, r.Name) 247 } 248 249 if diff := cmp.Diff(tt.want, got); diff != "" { 250 t.Errorf("checkRules() mismatch (-want +got):\n%s", diff) 251 } 252 }) 253 } 254 } 255 256 func TestBodyRules(t *testing.T) { 257 tests := []struct { 258 name string 259 title string // if empty string, we use goodCommitTitle 260 repo string // if empty string, treated as "go" repo 261 body string 262 want []string 263 }{ 264 // We consider these good bodies. 265 { 266 name: "good", 267 body: goodCommitBody, 268 want: nil, 269 }, 270 { 271 name: "good bug format for go repo", 272 repo: "go", 273 body: "This is This is body text.\n\nFixes #1234", 274 want: nil, 275 }, 276 { 277 name: "good bug format for tools repo", 278 repo: "tools", 279 body: "This is This is body text.\n\nFixes golang/go#1234", 280 want: nil, 281 }, 282 { 283 name: "good bug format for vscode-go", 284 repo: "vscode-go", 285 body: "This is This is body text.\n\nFixes golang/vscode-go#1234", 286 want: nil, 287 }, 288 { 289 name: "good bug format for unknown repo", 290 repo: "some-future-repo", 291 body: "This is This is body text.\n\nFixes golang/go#1234", 292 want: nil, 293 }, 294 { 295 name: "allowed long lines for benchstat output", 296 body: "Encode/format=json-48 1.718µ ± 1% 1.423µ ± 1% -17.20% (p=0.000 n=10)" + 297 strings.Repeat("Hello. ", 100) + "\n" + goodCommitBody, 298 want: nil, 299 }, 300 { 301 name: "a trival fix", 302 title: "fmt: fix spelling mistakes", 303 body: "", 304 want: nil, // we don't flag short body or missing bug reference because "spelling" is in title 305 }, 306 307 // Now we consider some bad bodies. 308 // First, some basic mistakes. 309 { 310 name: "too short", 311 body: "Short body", 312 want: []string{ 313 "body: short", 314 "body: no bug reference candidate found", 315 }, 316 }, 317 { 318 name: "missing body", 319 body: "", 320 want: []string{ 321 "body: short", 322 "body: no bug reference candidate found", 323 }, 324 }, 325 { 326 name: "not word wrapped", 327 body: strings.Repeat("Hello. ", 100) + "\n" + goodCommitBody, 328 want: []string{ 329 "body: long lines", 330 }, 331 }, 332 { 333 name: "not a sentence", 334 body: "This is missing a period", 335 want: []string{ 336 "body: no sentence candidates found", 337 "body: no bug reference candidate found", 338 }, 339 }, 340 { 341 name: "Signed-off-by", 342 body: "Signed-off-by: bad\n\n" + goodCommitBody, 343 want: []string{ 344 "body: contains Signed-off-by", 345 }, 346 }, 347 { 348 name: "PR instructions", 349 body: "Delete these instructions once you have read and applied them.\n", 350 want: []string{ 351 "body: still contains PR instructions", 352 "body: no bug reference candidate found", 353 }, 354 }, 355 356 // Next, mistakes in the repo-specific format or location of bug references. 357 { 358 name: "bad bug format for go repo", 359 repo: "go", 360 body: "This is body text.\n\nFixes golang/go#1234", 361 want: []string{ 362 "body: bug format looks incorrect", 363 }, 364 }, 365 { 366 name: "bad bug format for tools repo", 367 repo: "tools", 368 body: "This is body text.\n\nFixes #1234", 369 want: []string{ 370 "body: bug format looks incorrect", 371 }, 372 }, 373 { 374 name: "bad bug format for vscode-go", 375 repo: "vscode-go", 376 body: "This is body text.\n\nFixes #1234", 377 want: []string{ 378 "body: bug format looks incorrect", 379 }, 380 }, 381 { 382 name: "bad bug format for unknown repo", 383 repo: "some-future-repo", 384 body: "This is body text.\n\nFixes #1234", 385 want: []string{ 386 "body: bug format looks incorrect", 387 }, 388 }, 389 { 390 name: "bad bug location", 391 body: "This is body text.\nFixes #1234\nAnd a final line we should not have.", 392 want: []string{ 393 "body: no bug reference candidate at end", 394 }, 395 }, 396 397 // We next have some good bodies that are markdown-ish, 398 // but we allow them. 399 { 400 name: "allowed markdown: mention regex in title", 401 title: "regexp: fix something", 402 body: "Example `.*`.\n" + goodCommitBody, 403 want: nil, 404 }, 405 { 406 name: "allowed markdown: mention regex in body", 407 body: "A regex `.*`.\n" + goodCommitBody, 408 want: nil, 409 }, 410 { 411 name: "allowed markdown: mention markdown", 412 body: "A markdown bug `and this is ok`.\n" + goodCommitBody, 413 want: nil, 414 }, 415 { 416 name: "allowed markdown: might be go code", 417 body: " s := `raw`\n" + goodCommitBody, 418 want: nil, 419 }, 420 421 // Examples of using markdown that we flag. 422 { 423 name: "markdown backticks", 424 body: "A variable `foo`.\n" + goodCommitBody, 425 want: []string{ 426 "body: might use markdown", 427 }, 428 }, 429 { 430 name: "markdown block quote", 431 body: "Some code:\n```\nx := y\n```\n" + goodCommitBody, 432 want: []string{ 433 "body: might use markdown", 434 }, 435 }, 436 { 437 name: "markdown link", 438 body: "[click here](https://example.com)\n" + goodCommitBody, 439 want: []string{ 440 "body: might use markdown", 441 }, 442 }, 443 } 444 for _, tt := range tests { 445 t.Run(tt.name, func(t *testing.T) { 446 title := goodCommitTitle 447 if tt.title != "" { 448 title = tt.title 449 } 450 commit := commitMessage(title, tt.body, goodCommitFooters) 451 repo := "go" 452 if tt.repo != "" { 453 repo = tt.repo 454 } 455 change, err := ParseCommitMessage(repo, commit) 456 if err != nil { 457 t.Fatalf("ParseCommitMessage failed: %v", err) 458 } 459 results := Check(change) 460 461 var got []string 462 for _, r := range results { 463 got = append(got, r.Name) 464 } 465 466 if diff := cmp.Diff(tt.want, got); diff != "" { 467 t.Errorf("checkRules() mismatch (-want +got):\n%s", diff) 468 } 469 }) 470 } 471 } 472 473 func TestParseCommitMessage(t *testing.T) { 474 tests := []struct { 475 name string 476 repo string 477 text string 478 want Change 479 wantErr bool 480 }{ 481 // Bad examples. 482 { 483 name: "not enough lines", 484 text: "title", 485 want: Change{}, 486 wantErr: true, 487 }, 488 { 489 name: "second line not blank", 490 text: "title\nbad line\nBody 1", 491 want: Change{}, 492 wantErr: true, 493 }, 494 { 495 name: "no footer", 496 text: "title\n\nBody 1\n", 497 want: Change{}, 498 wantErr: true, 499 }, 500 { 501 name: "no footer and no body", 502 text: "title\n\n\n", 503 want: Change{}, 504 wantErr: true, 505 }, 506 507 // Good examples. 508 { 509 name: "good", 510 text: "title\n\nBody 1\n\nFooter: 1\n", 511 want: Change{ 512 Title: "title", 513 Body: "Body 1", 514 }, 515 wantErr: false, 516 }, 517 { 518 name: "good with two body lines", 519 text: "title\n\nBody 1\nBody 2\n\nFooter: 1\n", 520 want: Change{ 521 Title: "title", 522 Body: "Body 1\nBody 2", 523 }, 524 wantErr: false, 525 }, 526 { 527 name: "good with empty body", 528 text: "title\n\nFooter: 1\n", 529 want: Change{ 530 Title: "title", 531 Body: "", 532 }, 533 wantErr: false, 534 }, 535 { 536 name: "good with extra blank lines after footer", 537 text: "title\n\nBody 1\n\nFooter: 1\n\n\n", 538 want: Change{ 539 Title: "title", 540 Body: "Body 1", 541 }, 542 wantErr: false, 543 }, 544 { 545 name: "good with body line that looks like footer", 546 text: "title\n\nBody 1\nLink: example.com\n\nFooter: 1\n\n\n", 547 want: Change{ 548 Title: "title", 549 Body: "Body 1\nLink: example.com", 550 }, 551 wantErr: false, 552 }, 553 { 554 name: "allowed cherry pick in footer", // Example from CL 346093. 555 text: "title\n\nBody 1\n\nFooter: 1\n(cherry picked from commit ebd07b13caf35114b32e7d6783b27902af4829ce)\n", 556 want: Change{ 557 Title: "title", 558 Body: "Body 1", 559 }, 560 wantErr: false, 561 }, 562 } 563 for _, tt := range tests { 564 t.Run(tt.name, func(t *testing.T) { 565 got, err := ParseCommitMessage(tt.repo, tt.text) 566 if (err != nil) != tt.wantErr { 567 t.Errorf("ParseCommitMessage() error = %v, wantErr %v", err, tt.wantErr) 568 return 569 } 570 if diff := cmp.Diff(tt.want, got); diff != "" { 571 t.Errorf("checkRules() mismatch (-want +got):\n%s", diff) 572 } 573 }) 574 } 575 } 576 577 // commitMessage helps us create valid commit messages while testing. 578 func commitMessage(title, body, footers string) string { 579 return title + "\n\n" + body + "\n\n" + footers 580 } 581 582 // Some auxiliary testing variables available for use when creating commit messages. 583 var ( 584 goodCommitTitle = "pkg: a title that does not trigger any rules" 585 goodCommitBody = "A commit message body that does not trigger any rules.\n\nFixes #1234" 586 goodCommitFooters = `Change-Id: I1d8d10b142358983194ef2c389de4d9862d4ce97 587 GitHub-Last-Rev: 6d27e1471ee5dac0323a10b46e6e64e647068ecf 588 GitHub-Pull-Request: golang/build#69` 589 )