Skip to content

Commit

Permalink
Add checking capabilities (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmenezes authored Jul 22, 2020
1 parent c94a6b9 commit 72ba2fd
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 0 deletions.
79 changes: 79 additions & 0 deletions checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package codeowners

import (
"fmt"
"io"
)

var availableCheckers map[string]Checker

func init() {
availableCheckers = make(map[string]Checker)
}

// AvailableCheckers returns list of registered checkers
func AvailableCheckers() []string {
names := make([]string, len(availableCheckers))
i := 0
for checkerName := range availableCheckers {
names[i] = checkerName
i++
}
return names
}

// RegisterChecker adds checker to be used later when checking CODEOWNERS files
func RegisterChecker(name string, checker Checker) error {
_, found := availableCheckers[name]
if found {
return fmt.Errorf("Checker %s already exists", name)
}
availableCheckers[name] = checker
return nil
}

// Checker provides tools for validating CODEOWNER file contents
type Checker interface {
CheckLine(lineNo int, filePattern string, owners ...string) []CheckResult
}

// SeverityLevel exposes all possible levels of severity check results
type SeverityLevel int

// All possible severiy levels
const (
Error SeverityLevel = iota // Error serverity level
Warning // Warning serverity level
)

// String returns the string representation of this severity level
func (l SeverityLevel) String() string {
return [...]string{"Error", "Warning"}[l]
}

// CheckResult provides structured way to evaluate results of a CODEOWNERS validation check
type CheckResult struct {
LineNo int
Message string
Severity SeverityLevel
CheckName string
}

// Check evaluates the file contents against the checkers and return the results back.
func Check(r io.Reader, checkers ...string) ([]CheckResult, error) {
results := []CheckResult{}
decoder := NewDecoder(r)
for decoder.More() {
token, lineNo := decoder.Token()
for _, checker := range checkers {
c, ok := availableCheckers[checker]
if !ok {
return nil, fmt.Errorf("'%s' not found", checker)
}
lineResults := c.CheckLine(lineNo, token.Path(), token.Owners()...)
results = append(results, lineResults...)
}
}

return results, nil
}
94 changes: 94 additions & 0 deletions checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package codeowners_test

import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/fmenezes/codeowners"
_ "github.com/fmenezes/codeowners/checkers"
)

const dummyCheckerName string = "dummy"

type dummyChecker struct {
}

func (c dummyChecker) CheckLine(lineNo int, filePath string, owners ...string) []codeowners.CheckResult {
return []codeowners.CheckResult{
{
LineNo: 1,
Message: "Dummy Error",
Severity: codeowners.Error,
CheckName: dummyCheckerName,
},
}
}

func TestRegisterChecker(t *testing.T) {
err := codeowners.RegisterChecker(dummyCheckerName, dummyChecker{})
if err != nil {
t.Error(err)
}
found := false
for _, checker := range codeowners.AvailableCheckers() {
if checker == dummyCheckerName {
found = true
}
}
if !found {
t.Errorf("%s not properly registered", dummyCheckerName)
}
}

func TestRegisterCheckerAgain(t *testing.T) {
codeowners.RegisterChecker(dummyCheckerName, dummyChecker{})
err := codeowners.RegisterChecker(dummyCheckerName, dummyChecker{})
if err == nil {
t.Errorf("%s should be already registered, expecting an error", dummyCheckerName)
}
}

func TestSeverityLevelLabels(t *testing.T) {
if codeowners.Error.String() != "Error" {
t.Errorf("codeowners.Error.String() should evaluate to 'Error'")
}
if codeowners.Warning.String() != "Warning" {
t.Errorf("codeowners.Warning.String() should evaluate to 'Warning'")
}
}

func TestSimpleCheck(t *testing.T) {
input := `filepattern @owner`
want := []codeowners.CheckResult{
{
LineNo: 1,
Message: "Dummy Error",
Severity: codeowners.Error,
CheckName: dummyCheckerName,
},
}

codeowners.RegisterChecker(dummyCheckerName, dummyChecker{})
got, err := codeowners.Check(strings.NewReader(input), dummyCheckerName)
if err != nil {
t.Errorf("Input %s, Error %v", input, err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("Input %s, Want %v, Got %v", input, want, got)
}
}

func ExampleCheck() {
contents := strings.NewReader(`filepattern`)
checks, err := codeowners.Check(contents, "NoOwner")
if err != nil {

}
for _, check := range checks {
fmt.Printf("%d ::%s:: %s [%s]\n", check.LineNo, check.Severity, check.Message, check.CheckName)
}
//Output:
//1 ::Error:: No owners specified [NoOwner]
}
2 changes: 2 additions & 0 deletions checkers/checkers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package checkers contain pre built checkers to validate CODEOWNER files
package checkers
28 changes: 28 additions & 0 deletions checkers/noowner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package checkers

import "github.com/fmenezes/codeowners"

const noOwnerCheckerName string = "NoOwner"

func init() {
codeowners.RegisterChecker(noOwnerCheckerName, NoOwner{})
}

// NoOwner represents checker to decide validate presence of owners in each of CODEOWNERS lines
type NoOwner struct{}

// CheckLine runs this NoOwner's check against each line
func (c NoOwner) CheckLine(lineNo int, pattern string, owners ...string) []codeowners.CheckResult {
results := []codeowners.CheckResult{}

if len(owners) == 0 {
results = append(results, codeowners.CheckResult{
LineNo: lineNo,
Message: "No owners specified",
Severity: codeowners.Error,
CheckName: noOwnerCheckerName,
})

}
return results
}
35 changes: 35 additions & 0 deletions checkers/noowner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package checkers_test

import (
"reflect"
"testing"

"github.com/fmenezes/codeowners"
"github.com/fmenezes/codeowners/checkers"
)

func TestNoOwnerCheck(t *testing.T) {
input := struct {
lineNo int
pattern string
owners []string
}{
lineNo: 1,
pattern: "filepattern",
owners: []string{},
}
want := []codeowners.CheckResult{
{
LineNo: 1,
Message: "No owners specified",
Severity: codeowners.Error,
CheckName: "NoOwner",
},
}

checker := checkers.NoOwner{}
got := checker.CheckLine(input.lineNo, input.pattern, input.owners...)
if !reflect.DeepEqual(got, want) {
t.Errorf("Input: %v, Want: %v, Got: %v", input, want, got)
}
}

0 comments on commit 72ba2fd

Please sign in to comment.