github.com/blend/go-sdk@v1.20220411.3/copyright/copyright_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package copyright 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "io" 15 "io/fs" 16 "os" 17 "path/filepath" 18 "strings" 19 "testing" 20 "text/template" 21 "time" 22 23 "github.com/blend/go-sdk/assert" 24 "github.com/blend/go-sdk/ref" 25 ) 26 27 func Test_Copyright_GetStdout(t *testing.T) { 28 its := assert.New(t) 29 30 c := New() 31 32 its.Equal(os.Stdout, c.GetStdout()) 33 buf := new(bytes.Buffer) 34 c.Stdout = buf 35 its.Equal(c.Stdout, c.GetStdout()) 36 c.Quiet = ref.Bool(true) 37 its.Equal(io.Discard, c.GetStdout()) 38 } 39 40 func Test_Copyright_GetStderr(t *testing.T) { 41 its := assert.New(t) 42 43 c := New() 44 45 its.Equal(os.Stderr, c.GetStderr()) 46 buf := new(bytes.Buffer) 47 c.Stderr = buf 48 its.Equal(buf, c.GetStderr()) 49 c.Quiet = ref.Bool(true) 50 its.Equal(io.Discard, c.GetStderr()) 51 } 52 53 func Test_Copyright_mergeFileSections(t *testing.T) { 54 its := assert.New(t) 55 56 merged := Copyright{}.mergeFileSections([]byte("foo"), []byte("bar"), []byte("baz")) 57 its.Equal("foobarbaz", string(merged)) 58 } 59 60 func Test_Copyright_fileHasCopyrightHeader(t *testing.T) { 61 its := assert.New(t) 62 63 var goodCorpus = []byte(`foo 64 bar 65 baz 66 `) 67 68 notice, err := generateGoNotice(OptYear(2021)) 69 its.Nil(err) 70 71 goodCorpusWithNotice := Copyright{}.mergeFileSections([]byte(notice), goodCorpus) 72 its.Contains(string(goodCorpusWithNotice), "Copyright (c) 2021") 73 its.True((Copyright{}).fileHasCopyrightHeader(goodCorpusWithNotice, []byte(notice))) 74 } 75 76 func Test_Copyright_fileHasCopyrightHeader_invalid(t *testing.T) { 77 its := assert.New(t) 78 79 c := Copyright{} 80 81 var invalidCorpus = []byte(`foo 82 bar 83 baz 84 `) 85 expectedNotice, err := generateGoNotice(OptYear(2021)) 86 its.Nil(err) 87 88 its.False(c.fileHasCopyrightHeader(invalidCorpus, []byte(expectedNotice)), "we haven't added the notice") 89 } 90 91 func Test_Copyright_fileHasCopyrightHeader_differentYear(t *testing.T) { 92 its := assert.New(t) 93 94 c := Copyright{} 95 96 var goodCorpus = []byte(`foo 97 bar 98 baz 99 `) 100 101 notice, err := generateGoNotice(OptYear(2020)) 102 its.Nil(err) 103 104 goodCorpusWithNotice := c.mergeFileSections(notice, goodCorpus) 105 its.Contains(string(goodCorpusWithNotice), "Copyright (c) 2020") 106 107 newNotice, err := generateGoNotice(OptYear(2021)) 108 its.Nil(err) 109 110 its.True(c.fileHasCopyrightHeader(goodCorpusWithNotice, []byte(newNotice))) 111 } 112 113 func Test_Copyright_fileHasCopyrightHeader_leadingWhitespace(t *testing.T) { 114 its := assert.New(t) 115 116 c := Copyright{} 117 118 var goodCorpus = []byte(`foo 119 bar 120 baz 121 `) 122 123 notice, err := generateGoNotice(OptYear(2021)) 124 its.Nil(err) 125 126 goodCorpusWithNotice := c.mergeFileSections([]byte("\n\n"), notice, goodCorpus) 127 its.HasPrefix(string(goodCorpusWithNotice), "\n\n") 128 its.Contains(string(goodCorpusWithNotice), "Copyright (c) 2021") 129 130 its.True(c.fileHasCopyrightHeader(goodCorpusWithNotice, []byte(notice))) 131 } 132 133 func Test_Copyright_goBuildTagMatch(t *testing.T) { 134 its := assert.New(t) 135 136 c := Copyright{} 137 138 buildTag := []byte(`// +build foo 139 140 `) 141 corpus := []byte(`foo 142 bar 143 baz 144 `) 145 146 file := (Copyright{}).mergeFileSections(buildTag, corpus) 147 148 its.False(goBuildTagMatch.Match(corpus)) 149 its.True(goBuildTagMatch.Match(c.mergeFileSections(buildTag))) 150 151 found := goBuildTagMatch.FindAll(file, -1) 152 its.NotEmpty(found) 153 its.True(goBuildTagMatch.Match(file)) 154 } 155 156 func Test_Copyright_goBuildTagsMatch(t *testing.T) { 157 its := assert.New(t) 158 159 file := []byte(goBuildTags1) // testutil.GetTestFixture(its, "buildtags1.go") 160 its.True(goBuildTagMatch.Match(file)) 161 found := goBuildTagMatch.Find(file) 162 its.Equal("//go:build tag1\n// +build tag1\n\n", string(found)) 163 164 file2 := []byte(goBuildTags2) // testutil.GetTestFixture(its, "buildtags2.go") 165 its.True(goBuildTagMatch.Match(file2)) 166 found2 := goBuildTagMatch.Find(file2) 167 168 expected := `// +build tag5 169 //go:build tag1 && tag2 && tag3 170 // +build tag1,tag2,tag3 171 // +build tag6 172 173 ` 174 its.Equal(expected, string(found2)) 175 176 file3 := []byte(goBuildTags3) // testutil.GetTestFixture(its, "buildtags3.go") 177 its.True(goBuildTagMatch.Match(file3)) 178 found3 := goBuildTagMatch.Find(file3) 179 its.Equal("//go:build tag1 & tag2\n\n", string(found3)) 180 } 181 182 func Test_Copyright_goInjectNotice(t *testing.T) { 183 its := assert.New(t) 184 185 c := Copyright{} 186 187 file := []byte(`foo 188 bar 189 baz 190 `) 191 192 notice, err := generateGoNotice(OptYear(2021)) 193 its.Nil(err) 194 195 output := c.goInjectNotice("foo.go", file, notice) 196 its.Contains(string(output), "Copyright (c) 2021") 197 its.HasSuffix(string(output), string(file)) 198 } 199 200 func Test_Copyright_goInjectNotice_buildTag(t *testing.T) { 201 its := assert.New(t) 202 c := Copyright{} 203 204 buildTag := []byte(`// +build foo`) 205 corpus := []byte(`foo 206 bar 207 baz 208 `) 209 210 file := c.mergeFileSections(buildTag, []byte("\n\n"), corpus) 211 212 notice, err := generateGoNotice(OptYear(2021)) 213 its.Nil(err) 214 215 output := c.goInjectNotice("foo.go", file, notice) 216 its.Contains(string(output), "Copyright (c) 2021") 217 its.HasPrefix(string(output), string(buildTag)+"\n") 218 its.HasSuffix(string(output), string(corpus)) 219 220 outputRepeat := c.goInjectNotice("foo.go", output, notice) 221 its.Empty(outputRepeat, "inject notice functions should return an empty slice if the header already exists") 222 } 223 224 func Test_Copyright_goInjectNotice_goBuildTags(t *testing.T) { 225 t.Parallel() 226 227 type testCase struct { 228 Name string 229 Input string 230 Expect string 231 } 232 233 cases := []testCase{ 234 { 235 Name: "standard build tags", 236 Input: goBuildTags1, // "buildtags1.go", 237 Expect: goldenGoBuildTags1, 238 }, 239 { 240 Name: "multiple build tags", 241 Input: goBuildTags2, // "buildtags2.go", 242 Expect: goldenGoBuildTags2, 243 }, 244 { 245 Name: "build tags split across file", 246 Input: goBuildTags3, // "buildtags3.go", 247 Expect: goldenGoBuildTags3, 248 }, 249 } 250 251 for _, tc := range cases { 252 tc := tc 253 t.Run(tc.Name, func(t *testing.T) { 254 it := assert.New(t) 255 c := Copyright{} 256 257 notice, err := generateGoNotice(OptYear(2001)) 258 it.Nil(err) 259 260 output := c.goInjectNotice("foo.go", []byte(tc.Input), notice) 261 it.Equal(string(output), tc.Expect) // testutil.AssertGoldenFile(it, output, tc.TestFile) 262 263 outputRepeat := c.goInjectNotice("foo.go", output, notice) 264 it.Empty(outputRepeat) 265 }) 266 } 267 } 268 269 func Test_Copyright_tsInjectNotice_tsReferenceTags(t *testing.T) { 270 t.Parallel() 271 272 type testCase struct { 273 Name string 274 Input string 275 Expect string 276 } 277 278 cases := []testCase{ 279 { 280 Name: "single reference tag", 281 Input: tsReferenceTag, 282 Expect: goldenTsReferenceTag, 283 }, 284 { 285 Name: "multiple reference tags", 286 Input: tsReferenceTags, 287 Expect: goldenTsReferenceTags, 288 }, 289 { 290 Name: "no reference tags", 291 Input: tsTest, // "buildtags3.go", 292 Expect: goldenTs, 293 }, 294 } 295 296 for _, tc := range cases { 297 tc := tc 298 t.Run(tc.Name, func(t *testing.T) { 299 it := assert.New(t) 300 c := Copyright{} 301 302 notice, err := generateTypescriptNotice(OptYear(2022)) 303 it.Nil(err) 304 305 output := c.tsInjectNotice("foo.ts", []byte(tc.Input), notice) 306 it.Equal(tc.Expect, string(output)) 307 308 outputRepeat := c.tsInjectNotice("foo.ts", output, notice) 309 it.Empty(outputRepeat) 310 }) 311 } 312 } 313 314 func Test_Copyright_injectNotice_typescript(t *testing.T) { 315 its := assert.New(t) 316 317 c := Copyright{} 318 319 file := []byte(`foo 320 bar 321 baz 322 `) 323 324 notice, err := generateTypescriptNotice(OptYear(2001)) 325 its.Nil(err) 326 327 output := c.injectNotice("foo.ts", file, notice) 328 its.Contains(string(output), "Copyright (c) 2001") 329 its.HasSuffix(string(output), string(file)) 330 331 outputRepeat := c.injectNotice("foo.ts", output, notice) 332 its.Empty(outputRepeat, "inject notice functions should return an empty slice if the header already exists") 333 } 334 335 func Test_Copyright_injectNotice_typescript_referenceTags(t *testing.T) { 336 its := assert.New(t) 337 338 c := Copyright{} 339 340 file := []byte(tsReferenceTags) 341 342 notice, err := generateTypescriptNotice(OptYear(2001)) 343 its.Nil(err) 344 345 output := c.injectNotice("foo.ts", file, notice) 346 its.Contains(string(output), "Copyright (c) 2001") 347 its.HasSuffix(string(output), string(file)) 348 349 outputRepeat := c.injectNotice("foo.ts", output, notice) 350 its.Empty(outputRepeat, "inject notice functions should return an empty slice if the header already exists") 351 } 352 353 func Test_Copyright_goInjectNotice_openSource(t *testing.T) { 354 its := assert.New(t) 355 356 c := new(Copyright) 357 358 file := []byte(`foo 359 bar 360 baz 361 `) 362 363 notice, err := generateGoNotice( 364 OptYear(2021), 365 OptLicense("Apache 2.0"), 366 OptRestrictions(DefaultRestrictionsOpenSource), 367 ) 368 its.Nil(err) 369 370 output := c.goInjectNotice("foo.go", file, notice) 371 its.Contains(string(output), "Copyright (c) 2021") 372 its.Contains(string(output), "Use of this source code is governed by a Apache 2.0") 373 its.HasSuffix(string(output), string(file)) 374 } 375 376 func generateGoNotice(opts ...Option) ([]byte, error) { 377 c := New(opts...) 378 noticeBody, err := c.compileNoticeBodyTemplate(c.NoticeBodyTemplateOrDefault()) 379 if err != nil { 380 return nil, err 381 } 382 383 compiled, err := c.compileNoticeTemplate(goNoticeTemplate, noticeBody) 384 if err != nil { 385 return nil, err 386 } 387 return []byte(compiled), nil 388 } 389 390 func generateTypescriptNotice(opts ...Option) ([]byte, error) { 391 c := New(opts...) 392 noticeBody, err := c.compileNoticeBodyTemplate(c.NoticeBodyTemplateOrDefault()) 393 if err != nil { 394 return nil, err 395 } 396 397 compiled, err := c.compileNoticeTemplate(tsNoticeTemplate, noticeBody) 398 if err != nil { 399 return nil, err 400 } 401 return []byte(compiled), nil 402 } 403 404 func Test_Copyright_GetNoticeTemplate(t *testing.T) { 405 its := assert.New(t) 406 407 c := Copyright{} 408 409 noticeTemplate, ok := c.noticeTemplateByExtension(".js") 410 its.True(ok) 411 its.Equal(jsNoticeTemplate, noticeTemplate) 412 413 // it handles no dot prefix 414 noticeTemplate, ok = c.noticeTemplateByExtension("js") 415 its.True(ok) 416 its.Equal(jsNoticeTemplate, noticeTemplate) 417 418 // it handles another file type 419 noticeTemplate, ok = c.noticeTemplateByExtension(".go") 420 its.True(ok) 421 its.Equal(goNoticeTemplate, noticeTemplate) 422 423 noticeTemplate, ok = c.noticeTemplateByExtension("not-a-real-extension") 424 its.False(ok) 425 its.Empty(noticeTemplate) 426 427 withDefault := Copyright{ 428 Config: Config{ 429 FallbackNoticeTemplate: "this is just a test", 430 }, 431 } 432 433 noticeTemplate, ok = withDefault.noticeTemplateByExtension("not-a-real-extension") 434 its.True(ok) 435 its.Equal("this is just a test", noticeTemplate) 436 } 437 438 type mockInfoDir string 439 440 func (mid mockInfoDir) Name() string { return string(mid) } 441 func (mid mockInfoDir) Size() int64 { return 1 << 8 } 442 func (mid mockInfoDir) Mode() fs.FileMode { return fs.FileMode(0755) } 443 func (mid mockInfoDir) ModTime() time.Time { return time.Now().UTC() } 444 func (mid mockInfoDir) IsDir() bool { return true } 445 func (mid mockInfoDir) Sys() interface{} { return nil } 446 447 type mockInfoFile string 448 449 func (mif mockInfoFile) Name() string { return string(mif) } 450 func (mif mockInfoFile) Size() int64 { return 1 << 8 } 451 func (mif mockInfoFile) Mode() fs.FileMode { return fs.FileMode(0755) } 452 func (mif mockInfoFile) ModTime() time.Time { return time.Now().UTC() } 453 func (mif mockInfoFile) IsDir() bool { return false } 454 func (mif mockInfoFile) Sys() interface{} { return nil } 455 456 func Test_Copyright_includeOrExclude(t *testing.T) { 457 t.Parallel() 458 its := assert.New(t) 459 460 testCases := [...]struct { 461 Config Config 462 Path string 463 Info fs.FileInfo 464 Expected error 465 }{ 466 /*0*/ {Config: Config{}, Path: ".", Info: mockInfoDir("."), Expected: ErrWalkSkip}, 467 /*1*/ {Config: Config{Excludes: []string{"/foo/**"}}, Path: "/foo/bar", Info: mockInfoDir("bar"), Expected: filepath.SkipDir}, 468 /*2*/ {Config: Config{Excludes: []string{"/foo/**"}}, Path: "/foo/bar/baz.jpg", Info: mockInfoFile("baz.jpg"), Expected: ErrWalkSkip}, 469 /*3*/ {Config: Config{IncludeFiles: []string{"/foo/bar/*.jpg"}}, Path: "/foo/bar/baz.jpg", Info: mockInfoFile("baz.jpg"), Expected: nil}, 470 /*4*/ {Config: Config{Excludes: []string{}, IncludeFiles: []string{}}, Path: "/foo/bar/baz.jpg", Info: mockInfoFile("baz.jpg"), Expected: ErrWalkSkip}, 471 /*5*/ {Config: Config{}, Path: "/foo/bar/baz.jpg", Info: mockInfoFile("baz.jpg"), Expected: nil}, 472 /*6*/ {Config: Config{}, Path: "/foo/bar/baz.jpg", Info: mockInfoDir("baz"), Expected: ErrWalkSkip}, 473 } 474 475 for index, tc := range testCases { 476 c := Copyright{Config: tc.Config} 477 its.Equal(tc.Expected, c.includeOrExclude(".", tc.Path, tc.Info), fmt.Sprintf("test %d", index)) 478 } 479 } 480 481 const ( 482 tsFile0 = `import * as axios from 'axios';` 483 tsFile1 = `/// <reference path="../types/testing.d.ts" /> 484 /// <reference path="../types/something.d.ts" /> 485 /// <reference path="../types/somethingElse.d.ts" /> 486 /// <reference path="../types/somethingMore.d.ts" /> 487 /// <reference path="../types/somethingLess.d.ts" /> 488 489 import * as axios from 'axios'; 490 ` 491 492 pyFile0 = `from __future__ import print_function 493 494 import logging 495 import os 496 import shutil 497 import sys 498 import requests 499 import uuid 500 import json` 501 502 goFile0 = `// +build tools 503 package tools 504 505 import ( 506 // goimports organizes imports for us 507 _ "golang.org/x/tools/cmd/goimports" 508 509 // golint is an opinionated linter 510 _ "golang.org/x/lint/golint" 511 512 // ineffassign is an opinionated linter 513 _ "github.com/gordonklaus/ineffassign" 514 515 // staticcheck is ineffassign but better 516 _ "honnef.co/go/tools/cmd/staticcheck" 517 ) 518 ` 519 ) 520 521 // createTestFS creates a temp dir with files in them, with _no_ copyright headers. 522 // 523 // there should be at least (1) failure. 524 func createTestFS(its *assert.Assertions) (tempDir string, revert func()) { 525 // create a temp dir 526 var err error 527 tempDir, err = os.MkdirTemp("", "copyright_test") 528 its.Nil(err) 529 revert = func() { 530 os.RemoveAll(tempDir) 531 } 532 533 // create some files 534 err = os.MkdirAll(filepath.Join(tempDir, "foo", "bar"), 0755) 535 its.Nil(err) 536 err = os.MkdirAll(filepath.Join(tempDir, "bar", "foo"), 0755) 537 its.Nil(err) 538 539 err = os.MkdirAll(filepath.Join(tempDir, "not-bar", "not-foo"), 0755) 540 its.Nil(err) 541 542 err = os.WriteFile(filepath.Join(tempDir, "file0.py"), []byte(pyFile0), 0644) 543 its.Nil(err) 544 545 err = os.WriteFile(filepath.Join(tempDir, "file0.ts"), []byte(tsFile0), 0644) 546 its.Nil(err) 547 548 err = os.WriteFile(filepath.Join(tempDir, "file1.ts"), []byte(tsFile1), 0644) 549 its.Nil(err) 550 551 err = os.WriteFile(filepath.Join(tempDir, "file0.go"), []byte(goFile0), 0644) 552 its.Nil(err) 553 554 err = os.WriteFile(filepath.Join(tempDir, "foo", "bar", "file0.py"), []byte(pyFile0), 0644) 555 its.Nil(err) 556 557 err = os.WriteFile(filepath.Join(tempDir, "foo", "bar", "file0.ts"), []byte(tsFile0), 0644) 558 its.Nil(err) 559 560 err = os.WriteFile(filepath.Join(tempDir, "foo", "bar", "file1.ts"), []byte(tsFile1), 0644) 561 its.Nil(err) 562 563 err = os.WriteFile(filepath.Join(tempDir, "foo", "bar", "file0.go"), []byte(goFile0), 0644) 564 its.Nil(err) 565 566 err = os.WriteFile(filepath.Join(tempDir, "bar", "foo", "file0.py"), []byte(pyFile0), 0644) 567 its.Nil(err) 568 569 err = os.WriteFile(filepath.Join(tempDir, "bar", "foo", "file0.ts"), []byte(tsFile0), 0644) 570 its.Nil(err) 571 572 err = os.WriteFile(filepath.Join(tempDir, "bar", "foo", "file1.ts"), []byte(tsFile1), 0644) 573 its.Nil(err) 574 575 err = os.WriteFile(filepath.Join(tempDir, "bar", "foo", "file0.go"), []byte(goFile0), 0644) 576 its.Nil(err) 577 578 err = os.WriteFile(filepath.Join(tempDir, "not-bar", "not-foo", "file0.py"), []byte(pyFile0), 0644) 579 its.Nil(err) 580 581 err = os.WriteFile(filepath.Join(tempDir, "not-bar", "not-foo", "file0.ts"), []byte(tsFile0), 0644) 582 its.Nil(err) 583 584 err = os.WriteFile(filepath.Join(tempDir, "not-bar", "not-foo", "file1.ts"), []byte(tsFile1), 0644) 585 its.Nil(err) 586 587 err = os.WriteFile(filepath.Join(tempDir, "not-bar", "not-foo", "file0.go"), []byte(goFile0), 0644) 588 its.Nil(err) 589 return 590 } 591 592 func Test_Copyright_Walk(t *testing.T) { 593 its := assert.New(t) 594 595 tempDir, revert := createTestFS(its) 596 defer revert() 597 598 c := New( 599 OptIncludeFiles("*.py", "*.ts"), 600 OptExcludes("*/not-bar/*", "*/not-foo/*"), 601 ) 602 603 var err error 604 var seen []string 605 err = c.Walk(context.TODO(), func(path string, info os.FileInfo, file, notice []byte) error { 606 seen = append(seen, path) 607 return nil 608 }, tempDir) 609 its.Nil(err) 610 expected := []string{ 611 filepath.Join(tempDir, "bar", "foo", "file0.py"), 612 filepath.Join(tempDir, "bar", "foo", "file0.ts"), 613 filepath.Join(tempDir, "bar", "foo", "file1.ts"), 614 filepath.Join(tempDir, "file0.py"), 615 filepath.Join(tempDir, "file0.ts"), 616 filepath.Join(tempDir, "file1.ts"), 617 filepath.Join(tempDir, "foo", "bar", "file0.py"), 618 filepath.Join(tempDir, "foo", "bar", "file0.ts"), 619 filepath.Join(tempDir, "foo", "bar", "file1.ts"), 620 } 621 its.Equal(expected, seen) 622 } 623 624 func Test_Copyright_Walk_noExitFirst(t *testing.T) { 625 its := assert.New(t) 626 627 tempDir, revert := createTestFS(its) 628 defer revert() 629 630 c := New( 631 OptIncludeFiles("*.py", "*.ts"), 632 OptExcludes("*/not-bar/*", "*/not-foo/*"), 633 OptExitFirst(false), 634 ) 635 636 var err error 637 var seen []string 638 err = c.Walk(context.TODO(), func(path string, info os.FileInfo, file, notice []byte) error { 639 seen = append(seen, path) 640 if len(seen) > 0 { 641 return ErrFailure 642 } 643 return nil 644 }, tempDir) 645 its.NotNil(err) 646 expected := []string{ 647 filepath.Join(tempDir, "bar", "foo", "file0.py"), 648 filepath.Join(tempDir, "bar", "foo", "file0.ts"), 649 filepath.Join(tempDir, "bar", "foo", "file1.ts"), 650 filepath.Join(tempDir, "file0.py"), 651 filepath.Join(tempDir, "file0.ts"), 652 filepath.Join(tempDir, "file1.ts"), 653 filepath.Join(tempDir, "foo", "bar", "file0.py"), 654 filepath.Join(tempDir, "foo", "bar", "file0.ts"), 655 filepath.Join(tempDir, "foo", "bar", "file1.ts"), 656 } 657 its.Equal(expected, seen) 658 } 659 660 func Test_Copyright_Walk_exitFirst(t *testing.T) { 661 its := assert.New(t) 662 663 tempDir, revert := createTestFS(its) 664 defer revert() 665 666 c := New( 667 OptIncludeFiles("*.py", "*.ts"), 668 OptExcludes("*/not-bar/*", "*/not-foo/*"), 669 OptExitFirst(true), 670 ) 671 672 var err error 673 var seen []string 674 err = c.Walk(context.TODO(), func(path string, info os.FileInfo, file, notice []byte) error { 675 seen = append(seen, path) 676 if len(seen) > 0 { 677 return ErrFailure 678 } 679 return nil 680 }, tempDir) 681 its.NotNil(err) 682 expected := []string{ 683 filepath.Join(tempDir, "bar", "foo", "file0.py"), 684 } 685 its.Equal(expected, seen) 686 } 687 688 func Test_Copyright_Inject(t *testing.T) { 689 its := assert.New(t) 690 691 tempDir, revert := createTestFS(its) 692 defer revert() 693 694 c := New( 695 OptIncludeFiles("*.py", "*.ts", "*.go"), 696 OptExcludes("*/not-bar/*", "*/not-foo/*"), 697 ) 698 699 err := c.Inject(context.TODO(), tempDir) 700 its.Nil(err) 701 702 contents, err := os.ReadFile(filepath.Join(tempDir, "bar", "foo", "file0.py")) 703 its.Nil(err) 704 its.HasPrefix(string(contents), "#\n# Copyright") 705 706 contents, err = os.ReadFile(filepath.Join(tempDir, "bar", "foo", "file0.ts")) 707 its.Nil(err) 708 its.HasPrefix(string(contents), "/**\n * Copyright") 709 } 710 711 func Test_Copyright_Inject_Shebang(t *testing.T) { 712 t.Parallel() 713 its := assert.New(t) 714 715 tempDir, err := os.MkdirTemp("", "copyright_test") 716 its.Nil(err) 717 t.Cleanup(func() { os.RemoveAll(tempDir) }) 718 719 // Write `shift.py` without 720 contents := strings.Join([]string{ 721 "\r\t", 722 " #!/usr/bin/env python", 723 "", 724 "def main():", 725 ` print("Hello world")`, 726 "", 727 "", 728 `if __name__ == "__main__":`, 729 " main()", 730 "", 731 }, "\n") 732 filename := filepath.Join(tempDir, "shift.py") 733 err = os.WriteFile(filename, []byte(contents), 0755) 734 its.Nil(err) 735 736 // Actually inject 737 c := New(OptIncludeFiles("*shift.py")) 738 err = c.Inject(context.TODO(), tempDir) 739 its.Nil(err) 740 741 // Verify injected contents are as expected 742 contentInjected, err := os.ReadFile(filename) 743 its.Nil(err) 744 expected := strings.Join([]string{ 745 "\r\t", 746 " #!/usr/bin/env python", 747 "#", 748 "# " + expectedNoticePrefix(its), 749 "# " + DefaultRestrictionsInternal, 750 "#", 751 "", 752 "", 753 "def main():", 754 ` print("Hello world")`, 755 "", 756 "", 757 `if __name__ == "__main__":`, 758 " main()", 759 "", 760 }, "\n") 761 its.Equal(expected, string(contentInjected)) 762 763 // Verify no-op if notice header is already present 764 err = c.Inject(context.TODO(), tempDir) 765 its.Nil(err) 766 contentInjected, err = os.ReadFile(filename) 767 its.Nil(err) 768 its.Equal(expected, string(contentInjected)) 769 } 770 771 func Test_Copyright_Verify(t *testing.T) { 772 its := assert.New(t) 773 774 tempDir, revert := createTestFS(its) 775 defer revert() 776 777 c := New( 778 OptIncludeFiles("*.py", "*.ts", "*.go"), 779 OptExcludes("*/not-bar/*", "*/not-foo/*"), 780 OptExitFirst(false), 781 ) 782 c.Stdout = new(bytes.Buffer) 783 c.Stderr = new(bytes.Buffer) 784 785 err := c.Verify(context.TODO(), tempDir) 786 its.NotNil(err, "we must record a failure from walking the test fs") 787 788 err = c.Inject(context.TODO(), tempDir) 789 its.Nil(err) 790 791 err = c.Verify(context.TODO(), tempDir) 792 its.Nil(err) 793 } 794 795 func Test_Copyright_Verify_Shebang(t *testing.T) { 796 t.Parallel() 797 its := assert.New(t) 798 799 tempDir, err := os.MkdirTemp("", "copyright_test") 800 its.Nil(err) 801 t.Cleanup(func() { os.RemoveAll(tempDir) }) 802 803 // Write `shift.py` already injected 804 contents := strings.Join([]string{ 805 "#!/usr/bin/env python", 806 "#", 807 "# " + expectedNoticePrefix(its), 808 "# " + DefaultRestrictionsInternal, 809 "#", 810 "", 811 "", 812 "def main():", 813 ` print("Hello world")`, 814 "", 815 "", 816 `if __name__ == "__main__":`, 817 " main()", 818 "", 819 }, "\n") 820 filename := filepath.Join(tempDir, "shift.py") 821 err = os.WriteFile(filename, []byte(contents), 0755) 822 its.Nil(err) 823 824 // Verify present 825 cfg := Config{ 826 ShowDiff: ref.Bool(false), 827 Quiet: ref.Bool(true), 828 IncludeFiles: []string{"*shift.py"}, 829 } 830 c := New(OptConfig(cfg)) 831 err = c.Verify(context.TODO(), tempDir) 832 its.Nil(err) 833 834 // Write without and fail 835 contents = strings.Join([]string{ 836 "#!/usr/bin/env python", 837 "def main():", 838 ` print("Hello world")`, 839 "", 840 "", 841 `if __name__ == "__main__":`, 842 " main()", 843 "", 844 }, "\n") 845 err = os.WriteFile(filename, []byte(contents), 0755) 846 its.Nil(err) 847 err = c.Verify(context.TODO(), tempDir) 848 its.Equal("failure; one or more steps failed", fmt.Sprintf("%v", err)) 849 } 850 851 func Test_Copyright_Remove(t *testing.T) { 852 its := assert.New(t) 853 854 tempDir, revert := createTestFS(its) 855 defer revert() 856 857 c := New( 858 OptIncludeFiles("*.py", "*.ts", "*.go"), 859 OptExcludes("*/not-bar/*", "*/not-foo/*"), 860 ) 861 c.Stdout = new(bytes.Buffer) 862 c.Stderr = new(bytes.Buffer) 863 864 err := c.Inject(context.TODO(), tempDir) 865 its.Nil(err) 866 867 err = c.Verify(context.TODO(), tempDir) 868 its.Nil(err) 869 870 err = c.Remove(context.TODO(), tempDir) 871 its.Nil(err) 872 873 err = c.Verify(context.TODO(), tempDir) 874 its.NotNil(err) 875 } 876 877 func Test_Copyright_Remove_Shebang(t *testing.T) { 878 t.Parallel() 879 its := assert.New(t) 880 881 tempDir, err := os.MkdirTemp("", "copyright_test") 882 its.Nil(err) 883 t.Cleanup(func() { os.RemoveAll(tempDir) }) 884 885 // Write `shift.py` already injected 886 contents := strings.Join([]string{ 887 "#!/usr/bin/env python", 888 "#", 889 "# " + expectedNoticePrefix(its), 890 "# " + DefaultRestrictionsInternal, 891 "#", 892 "", 893 "", 894 "def main():", 895 ` print("Hello world")`, 896 "", 897 "", 898 `if __name__ == "__main__":`, 899 " main()", 900 "", 901 }, "\n") 902 filename := filepath.Join(tempDir, "shift.py") 903 err = os.WriteFile(filename, []byte(contents), 0755) 904 its.Nil(err) 905 906 // Actually remove 907 c := New(OptIncludeFiles("*shift.py")) 908 err = c.Remove(context.TODO(), tempDir) 909 its.Nil(err) 910 911 // Verify removed contents are as expected 912 contentRemoved, err := os.ReadFile(filename) 913 its.Nil(err) 914 expected := strings.Join([]string{ 915 "#!/usr/bin/env python", 916 "def main():", 917 ` print("Hello world")`, 918 "", 919 "", 920 `if __name__ == "__main__":`, 921 " main()", 922 "", 923 }, "\n") 924 its.Equal(expected, string(contentRemoved)) 925 926 // Verify no-op if notice header is already removed 927 err = c.Remove(context.TODO(), tempDir) 928 its.Nil(err) 929 contentRemoved, err = os.ReadFile(filename) 930 its.Nil(err) 931 its.Equal(expected, string(contentRemoved)) 932 } 933 934 func Test_Copyright_Walk_singleFileRoot(t *testing.T) { 935 its := assert.New(t) 936 937 tempDir, revert := createTestFS(its) 938 defer revert() 939 940 c := New( 941 OptIncludeFiles("*.py", "*.ts"), 942 OptExcludes("*/not-bar/*", "*/not-foo/*"), 943 ) 944 945 var err error 946 var seen []string 947 err = c.Walk(context.TODO(), func(path string, info os.FileInfo, file, notice []byte) error { 948 seen = append(seen, path) 949 return nil 950 }, filepath.Join(tempDir, "file0.py")) 951 its.Nil(err) 952 expected := []string{ 953 filepath.Join(tempDir, "file0.py"), 954 } 955 its.Equal(expected, seen) 956 } 957 958 func expectedNoticePrefix(its *assert.Assertions) string { 959 vars := map[string]string{ 960 "Year": fmt.Sprintf("%d", time.Now().UTC().Year()), 961 "Company": DefaultCompany, 962 "Restrictions": "", 963 } 964 tmpl := template.New("output") 965 _, err := tmpl.Parse(DefaultNoticeBodyTemplate) 966 its.Nil(err) 967 prefixBuffer := new(bytes.Buffer) 968 err = tmpl.Execute(prefixBuffer, vars) 969 its.Nil(err) 970 return strings.TrimRight(prefixBuffer.String(), "\n") 971 }