github.com/twelho/conform@v0.0.0-20231016230407-c25e9238598a/internal/policy/commit/commit_test.go (about) 1 // This Source Code Form is subject to the terms of the Mozilla Public 2 // License, v. 2.0. If a copy of the MPL was not distributed with this 3 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 //nolint:testpackage 6 package commit 7 8 import ( 9 "fmt" 10 "os" 11 "os/exec" 12 "strings" 13 "testing" 14 15 "github.com/twelho/conform/internal/policy" 16 ) 17 18 //nolint:gocognit 19 func TestConventionalCommitPolicy(t *testing.T) { 20 //nolint:govet 21 type testDesc struct { 22 Name string 23 CreateCommit func() error 24 ExpectValid bool 25 } 26 27 for _, test := range []testDesc{ 28 { 29 Name: "Valid", 30 CreateCommit: createValidScopedCommit, 31 ExpectValid: true, 32 }, 33 { 34 Name: "ValidBreaking", 35 CreateCommit: createValidBreakingCommit, 36 ExpectValid: true, 37 }, 38 { 39 Name: "InvalidBreakingSymbol", 40 CreateCommit: createInvalidBreakingSymbolCommit, 41 ExpectValid: false, 42 }, 43 { 44 Name: "ValidScopedBreaking", 45 CreateCommit: createValidScopedBreakingCommit, 46 ExpectValid: true, 47 }, 48 { 49 Name: "InvalidScopedBreaking", 50 CreateCommit: createInvalidScopedBreakingCommit, 51 ExpectValid: false, 52 }, 53 { 54 Name: "Invalid", 55 CreateCommit: createInvalidCommit, 56 ExpectValid: false, 57 }, 58 { 59 Name: "InvalidEmpty", 60 CreateCommit: createInvalidEmptyCommit, 61 ExpectValid: false, 62 }, 63 } { 64 func(test testDesc) { 65 t.Run(test.Name, func(tt *testing.T) { 66 dir := t.TempDir() 67 68 err := os.Chdir(dir) 69 if err != nil { 70 tt.Error(err) 71 } 72 err = initRepo() 73 if err != nil { 74 tt.Error(err) 75 } 76 77 err = test.CreateCommit() 78 if err != nil { 79 tt.Error(err) 80 } 81 report, err := runCompliance() 82 if err != nil { 83 t.Error(err) 84 } 85 86 if test.ExpectValid { 87 if !report.Valid() { 88 tt.Error("Report is invalid with valid conventional commit") 89 } 90 } else { 91 if report.Valid() { 92 tt.Error("Report is valid with invalid conventional commit") 93 } 94 } 95 }) 96 }(test) 97 } 98 } 99 100 func TestValidateDCO(t *testing.T) { 101 type testDesc struct { 102 Name string 103 CommitMessage string 104 ExpectValid bool 105 } 106 107 for _, test := range []testDesc{ 108 { 109 Name: "Valid DCO", 110 CommitMessage: "something nice\n\nSigned-off-by: Foo Bar <foobar@example.org>\n\n", 111 ExpectValid: true, 112 }, 113 { 114 Name: "Valid DCO with CRLF", 115 CommitMessage: "something nice\r\n\r\nSigned-off-by: Foo Bar <foobar@example.org>\r\n\r\n", 116 ExpectValid: true, 117 }, 118 { 119 Name: "No DCO", 120 CommitMessage: "something nice\n\nnot signed\n", 121 ExpectValid: false, 122 }, 123 } { 124 // Fixes scopelint error. 125 test := test 126 t.Run(test.Name, func(tt *testing.T) { 127 var report policy.Report 128 c := Commit{msg: test.CommitMessage} 129 report.AddCheck(c.ValidateDCO()) 130 131 if test.ExpectValid { 132 if !report.Valid() { 133 tt.Error("Report is invalid with valid DCP") 134 } 135 } else { 136 if report.Valid() { 137 tt.Error("Report is valid with invalid DCO") 138 } 139 } 140 }) 141 } 142 } 143 144 func TestValidConventionalCommitPolicy(t *testing.T) { 145 dir := t.TempDir() 146 147 err := os.Chdir(dir) 148 if err != nil { 149 t.Error(err) 150 } 151 152 err = initRepo() 153 if err != nil { 154 t.Error(err) 155 } 156 157 err = createValidScopedCommit() 158 if err != nil { 159 t.Error(err) 160 } 161 162 report, err := runCompliance() 163 if err != nil { 164 t.Error(err) 165 } 166 167 if !report.Valid() { 168 t.Errorf("Report is invalid with valid conventional commit") 169 } 170 } 171 172 func TestInvalidConventionalCommitPolicy(t *testing.T) { 173 dir := t.TempDir() 174 175 err := os.Chdir(dir) 176 if err != nil { 177 t.Error(err) 178 } 179 180 err = initRepo() 181 if err != nil { 182 t.Error(err) 183 } 184 185 err = createInvalidCommit() 186 if err != nil { 187 t.Error(err) 188 } 189 190 report, err := runCompliance() 191 if err != nil { 192 t.Error(err) 193 } 194 195 if report.Valid() { 196 t.Errorf("Report is valid with invalid conventional commit") 197 } 198 } 199 200 func TestEmptyConventionalCommitPolicy(t *testing.T) { 201 dir := t.TempDir() 202 203 err := os.Chdir(dir) 204 if err != nil { 205 t.Error(err) 206 } 207 208 err = initRepo() 209 if err != nil { 210 t.Error(err) 211 } 212 213 err = createInvalidEmptyCommit() 214 if err != nil { 215 t.Error(err) 216 } 217 218 report, err := runCompliance() 219 if err != nil { 220 t.Error(err) 221 } 222 223 if report.Valid() { 224 t.Error("Report is valid with invalid conventional commit") 225 } 226 } 227 228 func TestValidConventionalCommitPolicyRegex(t *testing.T) { 229 dir := t.TempDir() 230 231 err := os.Chdir(dir) 232 if err != nil { 233 t.Error(err) 234 } 235 236 err = initRepo() 237 if err != nil { 238 t.Error(err) 239 } 240 241 err = createValidCommitRegex() 242 if err != nil { 243 t.Error(err) 244 } 245 246 report, err := runCompliance() 247 if err != nil { 248 t.Error(err) 249 } 250 251 if !report.Valid() { 252 t.Error("Report is invalid with valid conventional commit") 253 } 254 } 255 256 func TestInvalidConventionalCommitPolicyRegex(t *testing.T) { 257 dir := t.TempDir() 258 259 err := os.Chdir(dir) 260 if err != nil { 261 t.Error(err) 262 } 263 264 err = initRepo() 265 if err != nil { 266 t.Error(err) 267 } 268 269 err = createInvalidCommitRegex() 270 if err != nil { 271 t.Error(err) 272 } 273 274 report, err := runCompliance() 275 if err != nil { 276 t.Error(err) 277 } 278 279 if report.Valid() { 280 t.Error("Report is valid with invalid conventional commit") 281 } 282 } 283 284 func TestValidRevisionRange(t *testing.T) { 285 dir := t.TempDir() 286 287 err := os.Chdir(dir) 288 if err != nil { 289 t.Error(err) 290 } 291 292 err = initRepo() 293 if err != nil { 294 t.Error(err) 295 } 296 297 revs, err := createValidCommitRange() 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 // Test with a valid revision range 303 report, err := runComplianceRange(revs[0], revs[len(revs)-1]) 304 if err != nil { 305 t.Error(err) 306 } 307 308 if !report.Valid() { 309 t.Error("Report is invalid with valid conventional commits") 310 } 311 312 // Test with HEAD as end of revision range 313 report, err = runComplianceRange(revs[0], "HEAD") 314 if err != nil { 315 t.Error(err) 316 } 317 318 if !report.Valid() { 319 t.Error("Report is invalid with valid conventional commits") 320 } 321 322 // Test with empty end of revision range (should fail) 323 _, err = runComplianceRange(revs[0], "") 324 if err == nil { 325 t.Error("Invalid end of revision, got success, expecting failure") 326 } 327 328 // Test with empty start of revision (should fail) 329 _, err = runComplianceRange("", "HEAD") 330 if err == nil { 331 t.Error("Invalid end of revision, got success, expecting failure") 332 } 333 334 // Test with start of revision not an ancestor of end of range (should fail) 335 _, err = runComplianceRange(revs[1], revs[0]) 336 if err == nil { 337 t.Error("Invalid end of revision, got success, expecting failure") 338 } 339 } 340 341 func createValidCommitRange() ([]string, error) { 342 var revs []string 343 344 for i := 0; i < 4; i++ { 345 err := os.WriteFile("test", []byte(fmt.Sprint(i)), 0o644) 346 if err != nil { 347 return nil, fmt.Errorf("writing test file failed: %w", err) 348 } 349 350 _, err = exec.Command("git", "add", "test").Output() 351 if err != nil { 352 return nil, fmt.Errorf("git add failed: %w", err) 353 } 354 355 _, err = exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", fmt.Sprintf("type(scope): description %d", i)).Output() 356 if err != nil { 357 return nil, fmt.Errorf("git commit failed: %w", err) 358 } 359 360 id, err := exec.Command("git", "rev-parse", "HEAD").Output() 361 if err != nil { 362 return nil, fmt.Errorf("rev-parse failed: %w", err) 363 } 364 365 revs = append(revs, strings.TrimSpace(string(id))) 366 } 367 368 return revs, nil 369 } 370 371 func runComplianceRange(id1, id2 string) (*policy.Report, error) { 372 c := &Commit{ 373 Conventional: &Conventional{ 374 Types: []string{"type"}, 375 Scopes: []string{"scope", "^valid"}, 376 }, 377 } 378 379 return c.Compliance(&policy.Options{ 380 RevisionRange: fmt.Sprintf("%s..%s", id1, id2), 381 }) 382 } 383 384 func runCompliance() (*policy.Report, error) { 385 c := &Commit{ 386 Conventional: &Conventional{ 387 Types: []string{"type"}, 388 Scopes: []string{"scope", "^valid"}, 389 }, 390 } 391 392 return c.Compliance(&policy.Options{}) 393 } 394 395 func initRepo() error { 396 _, err := exec.Command("git", "init").Output() 397 if err != nil { 398 return err 399 } 400 401 _, err = exec.Command("touch", "test").Output() 402 if err != nil { 403 return err 404 } 405 406 _, err = exec.Command("git", "add", "test").Output() 407 408 return err 409 } 410 411 func createValidScopedCommit() error { 412 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(scope): description").Output() 413 414 return err 415 } 416 417 func createValidBreakingCommit() error { 418 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat!: description").Output() 419 420 return err 421 } 422 423 func createInvalidBreakingSymbolCommit() error { 424 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat$: description").Output() 425 426 return err 427 } 428 429 func createValidScopedBreakingCommit() error { 430 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat(scope)!: description").Output() 431 432 return err 433 } 434 435 func createInvalidScopedBreakingCommit() error { 436 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat!(scope): description").Output() 437 438 return err 439 } 440 441 func createInvalidCommit() error { 442 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "invalid commit").Output() 443 444 return err 445 } 446 447 func createInvalidEmptyCommit() error { 448 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "--allow-empty-message", "-m", "").Output() 449 450 return err 451 } 452 453 func createValidCommitRegex() error { 454 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(valid-1): description").Output() 455 456 return err 457 } 458 459 func createInvalidCommitRegex() error { 460 _, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(invalid-1): description").Output() 461 462 return err 463 }