From 95e4f6c0bcfbe506740228794267689eabb41506 Mon Sep 17 00:00:00 2001 From: Hajime Terasawa Date: Thu, 16 Jun 2022 15:59:09 +0900 Subject: [PATCH] feat: sort the report output --- pkg/report/counter.go | 14 +- pkg/report/counter_test.go | 39 ++++- pkg/report/report.go | 87 +++++++---- pkg/report/report_test.go | 301 +++++++++++++++++++++++++++++++++++++ 4 files changed, 402 insertions(+), 39 deletions(-) diff --git a/pkg/report/counter.go b/pkg/report/counter.go index fe9de89..91ddd2b 100644 --- a/pkg/report/counter.go +++ b/pkg/report/counter.go @@ -4,8 +4,8 @@ import ( "github.com/commentcov/commentcov/proto" ) -// scopedCounter holds counters by scope. -type scopedCounter map[string]*Counter +// ScopedCounter holds counters by scope. +type ScopedCounter map[string]*Counter // Counter is a coverage conuter. type Counter struct { @@ -21,6 +21,12 @@ func NewCounter() *Counter { } } +// Merge merges 2 counters into 1. +func (c *Counter) Merge(other *Counter) { + c.Covered = c.Covered + other.Covered + c.Total = c.Total + other.Total +} + // CalcRate returns a rate from the current counter's state. func (c *Counter) CalcRate() float64 { var t int @@ -33,8 +39,8 @@ func (c *Counter) CalcRate() float64 { return 100.0 * float64(c.Covered) / float64(t) } -// Profile returns a rate from the current counter's state. -func (c *Counter) Profile(item *proto.CoverageItem) { +// Add adds the values of the given item. +func (c *Counter) Add(item *proto.CoverageItem) { if len(item.HeaderComments) > 0 { c.Covered++ c.Total++ diff --git a/pkg/report/counter_test.go b/pkg/report/counter_test.go index fe1e91a..9948fa4 100644 --- a/pkg/report/counter_test.go +++ b/pkg/report/counter_test.go @@ -34,6 +34,41 @@ func TestNewCounter(t *testing.T) { } } +func TestMerge(t *testing.T) { + tests := []struct { + name string + counter *report.Counter + other *report.Counter + want *report.Counter + }{ + + { + name: "standard", + counter: &report.Counter{ + Covered: 5, + Total: 10, + }, + other: &report.Counter{ + Covered: 5, + Total: 10, + }, + want: &report.Counter{ + Covered: 10, + Total: 20, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.counter.Merge(tt.other) + if diff := cmp.Diff(tt.want, tt.counter); diff != "" { + t.Errorf("*Counter values are mismatch (-want +got):%s\n", diff) + } + }) + } +} + func TestCalcRate(t *testing.T) { tests := []struct { name string @@ -79,7 +114,7 @@ func TestCalcRate(t *testing.T) { } } -func TestProfile(t *testing.T) { +func TestAdd(t *testing.T) { tests := []struct { name string counter *report.Counter @@ -158,7 +193,7 @@ func TestProfile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.counter.Profile(tt.item) + tt.counter.Add(tt.item) got := tt.counter if diff := cmp.Diff(tt.want, got); diff != "" { t.Errorf("*Counter values are mismatch (-want +got):%s\n", diff) diff --git a/pkg/report/report.go b/pkg/report/report.go index 3d35655..6689ae4 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -2,6 +2,7 @@ package report import ( "fmt" + "sort" "github.com/commentcov/commentcov/proto" ) @@ -50,53 +51,69 @@ func Report(mode Mode, items []*proto.CoverageItem) { // byFile reports coverage per file. func byFile(items []*proto.CoverageItem) { - sc := make(scopedCounter) + cc, files, scopes := Profile(items) - for _, item := range items { - scope := item.File + sc := make(ScopedCounter) + for _, file := range files { + sc[file] = NewCounter() - if _, ok := sc[scope]; !ok { - sc[scope] = NewCounter() + for _, scope := range scopes { + if counter, ok := cc[file][scope]; ok { + sc[file].Merge(counter) + } } - - sc[scope].Profile(item) } - for scope, counter := range sc { - percent := counter.CalcRate() - fmt.Printf("%v: %v\n", scope, percent) + for _, file := range files { + percent := sc[file].CalcRate() + fmt.Printf("%v: %v\n", file, percent) } } // byScope reports coverage by node type. func byScope(items []*proto.CoverageItem) { - sc := make(scopedCounter) + cc, files, scopes := Profile(items) - unknownScope := proto.CoverageItem_UNKNOWN.String() - for _, item := range items { - scope := item.Scope.String() - - if scope == unknownScope { - continue - } + sc := make(ScopedCounter) + for _, scope := range scopes { + sc[scope] = NewCounter() + } - if _, ok := sc[scope]; !ok { - sc[scope] = NewCounter() + for _, file := range files { + for _, scope := range scopes { + if counter, ok := cc[file][scope]; ok { + sc[scope].Merge(counter) + } } - - sc[scope].Profile(item) } - for scope, counter := range sc { - percent := counter.CalcRate() + for _, scope := range scopes { + percent := sc[scope].CalcRate() fmt.Printf("%v: %v\n", scope, percent) } } // byFileScope reports coverage by node type per file. func byFileScope(items []*proto.CoverageItem) { - cc := make(map[string]scopedCounter) + cc, files, scopes := Profile(items) + + for _, file := range files { + for _, scope := range scopes { + if counter, ok := cc[file][scope]; ok { + percent := counter.CalcRate() + fmt.Printf("%v,%v: %v\n", file, scope, percent) + } + } + } +} + +// Profile converts a list of items into the map of filename:scope:couter. +func Profile(items []*proto.CoverageItem) (map[string]ScopedCounter, []string, []string) { + cc := make(map[string]ScopedCounter) + files := []string{} + scopes := []string{} + scopeMemo := make(map[string]struct{}) unknownScope := proto.CoverageItem_UNKNOWN.String() for _, item := range items { scope := item.Scope.String() @@ -105,21 +122,25 @@ func byFileScope(items []*proto.CoverageItem) { continue } + if _, ok := scopeMemo[scope]; !ok { + scopeMemo[scope] = struct{}{} + scopes = append(scopes, scope) + } + if _, ok := cc[item.File]; !ok { - cc[item.File] = make(scopedCounter) + cc[item.File] = make(ScopedCounter) + files = append(files, item.File) } if _, ok := cc[item.File][scope]; !ok { cc[item.File][scope] = NewCounter() } - cc[item.File][scope].Profile(item) + cc[item.File][scope].Add(item) } - for file, sc := range cc { - for scope, counter := range sc { - percent := counter.CalcRate() - fmt.Printf("%v,%v: %v\n", file, scope, percent) - } - } + sort.Strings(files) + sort.Strings(scopes) + + return cc, files, scopes } diff --git a/pkg/report/report_test.go b/pkg/report/report_test.go index ed491ac..a26ec11 100644 --- a/pkg/report/report_test.go +++ b/pkg/report/report_test.go @@ -1,11 +1,21 @@ package report_test import ( + "sort" "testing" "github.com/google/go-cmp/cmp" "github.com/commentcov/commentcov/pkg/report" + "github.com/commentcov/commentcov/proto" +) + +var ( + sortedSliceTrans = cmp.Transformer("Sort", func(in []string) []string { + out := append([]string(nil), in...) // Copy input to avoid mutating it + sort.Strings(out) + return out + }) ) func TestStringToMode(t *testing.T) { @@ -56,3 +66,294 @@ func TestStringToMode(t *testing.T) { }) } } + +func TestProfile(t *testing.T) { + tests := []struct { + name string + items []*proto.CoverageItem + wantCounter map[string]report.ScopedCounter + wantFiles []string + wantScopes []string + }{ + { + name: "single item", + items: []*proto.CoverageItem{ + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 10, + EndColumn: 10, + }, + File: "hoge.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 1, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + }, + wantCounter: map[string]report.ScopedCounter{ + "hoge.go": { + "PUBLIC_FUNCTION": &report.Counter{ + Covered: 1, + Total: 1, + }, + }, + }, + wantFiles: []string{ + "hoge.go", + }, + wantScopes: []string{ + "PUBLIC_FUNCTION", + }, + }, + + { + name: "multi items", + items: []*proto.CoverageItem{ + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 10, + EndColumn: 10, + }, + File: "hoge.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 1, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 20, + EndColumn: 20, + }, + File: "hoge.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 11, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + }, + wantCounter: map[string]report.ScopedCounter{ + "hoge.go": { + "PUBLIC_FUNCTION": &report.Counter{ + Covered: 2, + Total: 2, + }, + }, + }, + wantFiles: []string{ + "hoge.go", + }, + wantScopes: []string{ + "PUBLIC_FUNCTION", + }, + }, + + { + name: "file duplicated", + items: []*proto.CoverageItem{ + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 10, + EndColumn: 10, + }, + File: "hoge.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 1, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + + { + Scope: proto.CoverageItem_PUBLIC_VARIABLE, + TargetBlock: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 20, + EndColumn: 20, + }, + File: "hoge.go", + Identifier: "MyConst", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 11, + EndColumn: 18, + }, + Comment: "MyConst Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + }, + wantCounter: map[string]report.ScopedCounter{ + "hoge.go": { + "PUBLIC_FUNCTION": &report.Counter{ + Covered: 1, + Total: 1, + }, + "PUBLIC_VARIABLE": &report.Counter{ + Covered: 1, + Total: 1, + }, + }, + }, + wantFiles: []string{ + "hoge.go", + }, + wantScopes: []string{ + "PUBLIC_FUNCTION", + "PUBLIC_VARIABLE", + }, + }, + + { + name: "scope duplicated", + items: []*proto.CoverageItem{ + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 10, + EndColumn: 10, + }, + File: "hoge.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 1, + StartColumn: 1, + EndLine: 1, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + + { + Scope: proto.CoverageItem_PUBLIC_FUNCTION, + TargetBlock: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 20, + EndColumn: 20, + }, + File: "fuga.go", + Identifier: "MyFunction", + Extension: ".go", + HeaderComments: []*proto.Comment{ + { + Block: &proto.Block{ + StartLine: 11, + StartColumn: 11, + EndLine: 11, + EndColumn: 18, + }, + Comment: "MyFunction Header\n", + }, + }, + InlineComments: []*proto.Comment{}, + }, + }, + wantCounter: map[string]report.ScopedCounter{ + "hoge.go": { + "PUBLIC_FUNCTION": &report.Counter{ + Covered: 1, + Total: 1, + }, + }, + "fuga.go": { + "PUBLIC_FUNCTION": &report.Counter{ + Covered: 1, + Total: 1, + }, + }, + }, + wantFiles: []string{ + "hoge.go", + "fuga.go", + }, + wantScopes: []string{ + "PUBLIC_FUNCTION", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCounter, gotFiles, gotScopes := report.Profile(tt.items) + + if diff := cmp.Diff(tt.wantCounter, gotCounter); diff != "" { + t.Errorf("map[string]report.ScopedCounter values are mismatch (-want +got):%s\n", diff) + } + + if diff := cmp.Diff(tt.wantFiles, gotFiles, sortedSliceTrans); diff != "" { + t.Errorf("[]string values are mismatch (-want +got):%s\n", diff) + } + + if diff := cmp.Diff(tt.wantScopes, gotScopes, sortedSliceTrans); diff != "" { + t.Errorf("[]string values are mismatch (-want +got):%s\n", diff) + } + }) + } +}