go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/copyright/copyright_test.go (about)

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