From 659e84d650b5ad08ef499b848bf02ad851464e71 Mon Sep 17 00:00:00 2001 From: Mateusz Szostok Date: Sat, 24 Oct 2020 00:12:30 +0200 Subject: [PATCH] Add test coverage for checks (#48) --- internal/check/api.go | 3 + internal/check/api_test.go | 27 +++++ internal/check/file_exists_test.go | 25 ++++ internal/check/package_test.go | 43 +++++++ ..._OK_status_on_empty_issues_list.golden.txt | 2 + ...hould_print_all_reported_issues.golden.txt | 5 + ..._'no'_when_there_is_no_failures.golden.txt | 2 + ...Should_print_number_of_failures.golden.txt | 2 + internal/printer/tty.go | 27 +++-- internal/printer/tty_test.go | 110 ++++++++++++++++++ 10 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 internal/check/api_test.go create mode 100644 internal/check/package_test.go create mode 100644 internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_OK_status_on_empty_issues_list.golden.txt create mode 100644 internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_all_reported_issues.golden.txt create mode 100644 internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_'no'_when_there_is_no_failures.golden.txt create mode 100644 internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_number_of_failures.golden.txt create mode 100644 internal/printer/tty_test.go diff --git a/internal/check/api.go b/internal/check/api.go index 1c264f2..fb0b626 100644 --- a/internal/check/api.go +++ b/internal/check/api.go @@ -74,6 +74,9 @@ func (bldr *OutputBuilder) ReportIssue(msg string, opts ...ReportIssueOpt) *Outp } func (bldr *OutputBuilder) Output() Output { + if bldr == nil { + return Output{} + } return Output{Issues: bldr.issues} } diff --git a/internal/check/api_test.go b/internal/check/api_test.go new file mode 100644 index 0000000..a955084 --- /dev/null +++ b/internal/check/api_test.go @@ -0,0 +1,27 @@ +package check_test + +import ( + "testing" + + "github.com/mszostok/codeowners-validator/internal/check" + + "github.com/stretchr/testify/assert" +) + +func TestAPIBuilder(t *testing.T) { + var bldr *check.OutputBuilder = nil + + t.Run("Does not panic on ReportIssue when builder is nil", func(t *testing.T) { + assert.NotPanics(t, func() { + issue := bldr.ReportIssue("test") + assert.Nil(t, issue) + }) + }) + + t.Run("Does not panic on Output when builder is nil", func(t *testing.T) { + assert.NotPanics(t, func() { + out := bldr.Output() + assert.Empty(t, out) + }) + }) +} diff --git a/internal/check/file_exists_test.go b/internal/check/file_exists_test.go index 9b9201e..71aab3c 100644 --- a/internal/check/file_exists_test.go +++ b/internal/check/file_exists_test.go @@ -180,6 +180,31 @@ func TestFileExists(t *testing.T) { } } +func TestFileExistCheckFileSystemFailure(t *testing.T) { + // given + tmpdir, err := ioutil.TempDir("", "file-checker") + require.NoError(t, err) + defer func() { + assert.NoError(t, os.RemoveAll(tmpdir)) + }() + + err = os.MkdirAll(filepath.Join(tmpdir, "foo"), 0222) + require.NoError(t, err) + + in := loadInput("* @pico") + in.RepoDir = tmpdir + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond) + defer cancel() + + // when + out, err := check.NewFileExist().Check(ctx, in) + + // then + require.Error(t, err) + assert.Empty(t, out) +} + func newErrIssue(msg string) check.Issue { return check.Issue{ Severity: check.Error, diff --git a/internal/check/package_test.go b/internal/check/package_test.go new file mode 100644 index 0000000..bb3a927 --- /dev/null +++ b/internal/check/package_test.go @@ -0,0 +1,43 @@ +package check_test + +import ( + "context" + "errors" + "testing" + + "github.com/mszostok/codeowners-validator/internal/check" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRespectingCanceledContext(t *testing.T) { + must := func(checker check.Checker, err error) check.Checker { + require.NoError(t, err) + return checker + } + + checkers := []check.Checker{ + check.NewDuplicatedPattern(), + check.NewFileExist(), + check.NewValidSyntax(), + check.NewNotOwnedFile(check.NotOwnedFileConfig{}), + must(check.NewValidOwner(check.ValidOwnerConfig{Repository: "org/repo"}, nil)), + } + + for _, checker := range checkers { + sut := checker + t.Run(checker.Name(), func(t *testing.T) { + // given: canceled context + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + // when + out, err := sut.Check(ctx, loadInput(validCODEOWNERS)) + + // then + assert.True(t, errors.Is(err, context.Canceled)) + assert.Empty(t, out) + }) + } +} diff --git a/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_OK_status_on_empty_issues_list.golden.txt b/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_OK_status_on_empty_issues_list.golden.txt new file mode 100644 index 0000000..e401d95 --- /dev/null +++ b/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_OK_status_on_empty_issues_list.golden.txt @@ -0,0 +1,2 @@ +==> Executing Foo Checker (1s) + Check OK diff --git a/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_all_reported_issues.golden.txt b/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_all_reported_issues.golden.txt new file mode 100644 index 0000000..a1dd6b4 --- /dev/null +++ b/internal/printer/testdata/TestTTYPrinterPrintCheckResult/Should_print_all_reported_issues.golden.txt @@ -0,0 +1,5 @@ +==> Executing Foo Checker (1s) + [err] line 42: Simulate error in line 42 + [war] line 2020: Simulate warning in line 2020 + [err] Error without line number + [war] Warning without line number diff --git a/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_'no'_when_there_is_no_failures.golden.txt b/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_'no'_when_there_is_no_failures.golden.txt new file mode 100644 index 0000000..67b2f99 --- /dev/null +++ b/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_'no'_when_there_is_no_failures.golden.txt @@ -0,0 +1,2 @@ + +20 check(s) executed, no failure(s) diff --git a/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_number_of_failures.golden.txt b/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_number_of_failures.golden.txt new file mode 100644 index 0000000..95ffd2c --- /dev/null +++ b/internal/printer/testdata/TestTTYPrinterPrintSummary/Should_print_number_of_failures.golden.txt @@ -0,0 +1,2 @@ + +20 check(s) executed, 10 failure(s) diff --git a/internal/printer/tty.go b/internal/printer/tty.go index 07d14b4..fa4a35f 100644 --- a/internal/printer/tty.go +++ b/internal/printer/tty.go @@ -2,6 +2,8 @@ package printer import ( "fmt" + "io" + "os" "strings" "sync" "time" @@ -10,6 +12,9 @@ import ( "github.com/mszostok/codeowners-validator/internal/check" ) +// writer used for test purpose +var writer io.Writer = os.Stdout + type TTYPrinter struct { m sync.RWMutex } @@ -18,27 +23,27 @@ func (tty *TTYPrinter) PrintCheckResult(checkName string, duration time.Duration tty.m.Lock() defer tty.m.Unlock() - header := color.New(color.Bold).PrintfFunc() - issueBody := color.New(color.FgWhite).PrintfFunc() - okCheck := color.New(color.FgGreen).PrintlnFunc() + header := color.New(color.Bold).FprintfFunc() + issueBody := color.New(color.FgWhite).FprintfFunc() + okCheck := color.New(color.FgGreen).FprintlnFunc() - header("==> Executing %s (%v)\n", checkName, duration) + header(writer, "==> Executing %s (%v)\n", checkName, duration) for _, i := range checkOut.Issues { issueSeverity := tty.severityPrintfFunc(i.Severity) - issueSeverity(" [%s]", strings.ToLower(i.Severity.String()[:3])) + issueSeverity(writer, " [%s]", strings.ToLower(i.Severity.String()[:3])) if i.LineNo != nil { - issueBody(" line %d:", *i.LineNo) + issueBody(writer, " line %d:", *i.LineNo) } - issueBody(" %s\n", i.Message) + issueBody(writer, " %s\n", i.Message) } if len(checkOut.Issues) == 0 { - okCheck(" Check OK") + okCheck(writer, " Check OK") } } -func (*TTYPrinter) severityPrintfFunc(severity check.SeverityType) func(format string, a ...interface{}) { +func (*TTYPrinter) severityPrintfFunc(severity check.SeverityType) func(w io.Writer, format string, a ...interface{}) { p := color.New() switch severity { case check.Warning: @@ -47,7 +52,7 @@ func (*TTYPrinter) severityPrintfFunc(severity check.SeverityType) func(format s p.Add(color.FgRed) } - return p.PrintfFunc() + return p.FprintfFunc() } func (*TTYPrinter) PrintSummary(allCheck, failedChecks int) { @@ -55,5 +60,5 @@ func (*TTYPrinter) PrintSummary(allCheck, failedChecks int) { if failedChecks > 0 { failures = fmt.Sprintf("%d", failedChecks) } - fmt.Printf("\n%d check(s) executed, %s failure(s)\n", allCheck, failures) + fmt.Fprintf(writer, "\n%d check(s) executed, %s failure(s)\n", allCheck, failures) } diff --git a/internal/printer/tty_test.go b/internal/printer/tty_test.go new file mode 100644 index 0000000..e9f57d8 --- /dev/null +++ b/internal/printer/tty_test.go @@ -0,0 +1,110 @@ +package printer + +import ( + "bytes" + "io" + "testing" + "time" + + "github.com/mszostok/codeowners-validator/internal/check" + "github.com/mszostok/codeowners-validator/internal/ptr" + + "github.com/sebdah/goldie/v2" +) + +func TestTTYPrinterPrintCheckResult(t *testing.T) { + t.Run("Should print all reported issues", func(t *testing.T) { + // given + tty := TTYPrinter{} + + buff := &bytes.Buffer{} + restore := overrideWriter(buff) + defer restore() + + // when + tty.PrintCheckResult("Foo Checker", time.Second, check.Output{ + Issues: []check.Issue{ + { + Severity: check.Error, + LineNo: ptr.Uint64Ptr(42), + Message: "Simulate error in line 42", + }, + { + Severity: check.Warning, + LineNo: ptr.Uint64Ptr(2020), + Message: "Simulate warning in line 2020", + }, + { + Severity: check.Error, + Message: "Error without line number", + }, + { + Severity: check.Warning, + Message: "Warning without line number", + }, + }, + }) + + // then + g := goldie.New(t, goldie.WithNameSuffix(".golden.txt")) + g.Assert(t, t.Name(), buff.Bytes()) + }) + + t.Run("Should print OK status on empty issues list", func(t *testing.T) { + // given + tty := TTYPrinter{} + + buff := &bytes.Buffer{} + restore := overrideWriter(buff) + defer restore() + + // when + tty.PrintCheckResult("Foo Checker", time.Second, check.Output{ + Issues: nil, + }) + + // then + g := goldie.New(t, goldie.WithNameSuffix(".golden.txt")) + g.Assert(t, t.Name(), buff.Bytes()) + }) +} + +func TestTTYPrinterPrintSummary(t *testing.T) { + t.Run("Should print number of failures", func(t *testing.T) { + // given + tty := TTYPrinter{} + + buff := &bytes.Buffer{} + restore := overrideWriter(buff) + defer restore() + + // when + tty.PrintSummary(20, 10) + + // then + g := goldie.New(t, goldie.WithNameSuffix(".golden.txt")) + g.Assert(t, t.Name(), buff.Bytes()) + }) + + t.Run("Should print 'no' when there is no failures", func(t *testing.T) { + // given + tty := TTYPrinter{} + + buff := &bytes.Buffer{} + restore := overrideWriter(buff) + defer restore() + + // when + tty.PrintSummary(20, 0) + + // then + g := goldie.New(t, goldie.WithNameSuffix(".golden.txt")) + g.Assert(t, t.Name(), buff.Bytes()) + }) +} + +func overrideWriter(in io.Writer) func() { + old := writer + writer = in + return func() { writer = old } +}