diff --git a/.testcoverage.example.yml b/.testcoverage.example.yml index da9937d..32f82a3 100644 --- a/.testcoverage.example.yml +++ b/.testcoverage.example.yml @@ -41,11 +41,31 @@ exclude: - \.pb\.go$ # excludes all protobuf generated files - ^pkg/bar # exclude package `pkg/bar` -# File name of go-test-coverage breakdown file, which can be used to -# analyze coverage difference. +# If specified, saves the current test coverage breakdown to this file. +# +# Typically, this breakdown is generated only for main (base) branches and +# stored as an artifact. Later, this file can be used in feature branches +# to compare test coverage against the base branch. breakdown-file-name: '' diff: - # File name of go-test-coverage breakdown file which will be used to - # report coverage difference. - base-breakdown-file-name: '' \ No newline at end of file + # Path to the test coverage breakdown file from the base branch. + # + # This file is usually generated and stored in the main (base) branch, + # controled via `breakdown-file-name` property. + # When set in a feature branch, it allows the tool to compute and report + # the coverage difference between the current (feature) branch and the base. + base-breakdown-file-name: '' + + # Allowed threshold for the test coverage difference (in percentage) + # between the feature branch and the base branch. + # + # By default, this is disabled (set to nil). Valid values range from + # -100.0 to +100.0. + # + # Example: + # If set to 0.5, an error will be reported if the feature branch has + # less than 0.5% more coverage than the base. + # + # If set to -0.5, the check allows up to 0.5% less coverage than the base. + threshold: nil \ No newline at end of file diff --git a/README.md b/README.md index 0ba3b3a..7ec4d91 100644 --- a/README.md +++ b/README.md @@ -125,14 +125,34 @@ exclude: - \.pb\.go$ # excludes all protobuf generated files - ^pkg/bar # exclude package `pkg/bar` -# File name of go-test-coverage breakdown file, which can be used to -# analyze coverage difference. +# If specified, saves the current test coverage breakdown to this file. +# +# Typically, this breakdown is generated only for main (base) branches and +# stored as an artifact. Later, this file can be used in feature branches +# to compare test coverage against the base branch. breakdown-file-name: '' diff: - # File name of go-test-coverage breakdown file which will be used to - # report coverage difference. + # Path to the test coverage breakdown file from the base branch. + # + # This file is usually generated and stored in the main (base) branch, + # controled via `breakdown-file-name` property. + # When set in a feature branch, it allows the tool to compute and report + # the coverage difference between the current (feature) branch and the base. base-breakdown-file-name: '' + + # Allowed threshold for the test coverage difference (in percentage) + # between the feature branch and the base branch. + # + # By default, this is disabled (set to nil). Valid values range from + # -100.0 to +100.0. + # + # Example: + # If set to 0.5, an error will be reported if the feature branch has + # less than 0.5% more coverage than the base. + # + # If set to -0.5, the check allows up to 0.5% less coverage than the base. + threshold: nil ``` ### Exclude Code from Coverage diff --git a/pkg/testcoverage/check.go b/pkg/testcoverage/check.go index 834cf10..85623b1 100644 --- a/pkg/testcoverage/check.go +++ b/pkg/testcoverage/check.go @@ -105,6 +105,7 @@ func Analyze(cfg Config, current, base []coverage.Stats) AnalyzeResult { return AnalyzeResult{ Threshold: thr, + DiffThreshold: cfg.Diff.Threshold, HasFileOverrides: hasFileOverrides, HasPackageOverrides: hasPackageOverrides, FilesBelowThreshold: checkCoverageStatsBelowThreshold(current, thr.File, overrideRules), @@ -115,6 +116,7 @@ func Analyze(cfg Config, current, base []coverage.Stats) AnalyzeResult { TotalStats: coverage.StatsCalcTotal(current), HasBaseBreakdown: len(base) > 0, Diff: calculateStatsDiff(current, base), + DiffPercentage: TotalPercentageDiff(current, base), } } diff --git a/pkg/testcoverage/check_test.go b/pkg/testcoverage/check_test.go index 1d39758..e610441 100644 --- a/pkg/testcoverage/check_test.go +++ b/pkg/testcoverage/check_test.go @@ -267,6 +267,109 @@ func TestCheck(t *testing.T) { }) } +func TestCheckDiff(t *testing.T) { + t.Parallel() + + if testing.Short() { + return + } + + brakedownFile := t.TempDir() + "/breakdown.testcoverage" + brakedownCurrentFile := t.TempDir() + "/breakdown-current.testcoverage" + brakedownFileEdited := "breakdown-edit.testcoverage" + + // run check to generate brakedown file + cfg := Config{ + Profile: profileOK, + BreakdownFileName: brakedownFile, + SourceDir: sourceDir, + } + buf := &bytes.Buffer{} + pass, err := Check(buf, cfg) + assert.True(t, pass) + assert.NoError(t, err) + + // should pass since brakedown is the same + cfg = Config{ + Profile: profileOK, + SourceDir: sourceDir, + Diff: Diff{ + BaseBreakdownFileName: brakedownFile, + Threshold: ptr(0.0), + }, + } + buf = &bytes.Buffer{} + pass, err = Check(buf, cfg) + assert.True(t, pass) + assert.NoError(t, err) + assertDiffNoChange(t, buf.String()) + assertDiffPercentage(t, buf.String(), 0.0) + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, true) + + // should pass since diff is negative + cfg = Config{ + Profile: profileOK, + SourceDir: sourceDir, + Diff: Diff{ + BaseBreakdownFileName: brakedownFile, + Threshold: ptr(-0.001), + }, + } + buf = &bytes.Buffer{} + pass, err = Check(buf, cfg) + assert.True(t, pass) + assert.NoError(t, err) + assertDiffNoChange(t, buf.String()) + assertDiffPercentage(t, buf.String(), 0.0) + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, true) + + // should NOT pass since brakedown is the same, and diff is positive + cfg = Config{ + Profile: profileOK, + SourceDir: sourceDir, + Diff: Diff{ + BaseBreakdownFileName: brakedownFile, + Threshold: ptr(0.1), + }, + } + buf = &bytes.Buffer{} + pass, err = Check(buf, cfg) + assert.False(t, pass) + assert.NoError(t, err) + assertDiffNoChange(t, buf.String()) + assertDiffPercentage(t, buf.String(), 0.0) + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, false) + + // change brakedown file to have positive difference + base := readStats(t, brakedownFile) + base[0].Covered = 0 + base[1].Covered = 0 + + tmpFile, err := os.CreateTemp(t.TempDir(), brakedownFileEdited) + assert.NoError(t, err) + _, err = tmpFile.Write(coverage.StatsSerialize(base)) + assert.NoError(t, err) + + // check should now pass since difference has increased + cfg = Config{ + Profile: profileOK, + SourceDir: sourceDir, + BreakdownFileName: brakedownCurrentFile, + Diff: Diff{ + BaseBreakdownFileName: tmpFile.Name(), + Threshold: ptr(1.0), + }, + } + buf = &bytes.Buffer{} + pass, err = Check(buf, cfg) + assert.True(t, pass) + assert.NoError(t, err) + + diff := TotalPercentageDiff(readStats(t, brakedownCurrentFile), base) + assertDiffPercentage(t, buf.String(), diff) + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, true) +} + //nolint:paralleltest // must not be parallel because it uses env func TestCheckNoParallel(t *testing.T) { if testing.Short() { @@ -450,6 +553,65 @@ func Test_Analyze(t *testing.T) { assert.False(t, result.Pass()) assertPrefix(t, result, prefix, true) }) + + t.Run("diff stats", func(t *testing.T) { + t.Parallel() + + stats := randStats(prefix, 10, 100) + + cfg := Config{} + result := Analyze(cfg, stats, stats) + assert.Empty(t, result.Diff) + assert.True(t, result.Pass()) + assert.Equal(t, 0.0, result.DiffPercentage) //nolint:testifylint //relax + }) + + t.Run("diff below threshold", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 10, Covered: 1}} + stats := []coverage.Stats{{Name: "foo", Total: 10, Covered: 8}} + + cfg := Config{ + Diff: Diff{Threshold: ptr(999.0)}, + } + result := Analyze(cfg, stats, base) + assert.NotEmpty(t, result.Diff) + assert.False(t, result.Pass()) + assert.False(t, result.MeetsDiffThreshold()) + assert.Equal(t, 70.0, result.DiffPercentage) //nolint:testifylint //relax + }) + + t.Run("diff above threshold", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 10, Covered: 1}} + stats := []coverage.Stats{{Name: "foo", Total: 10, Covered: 8}} + + cfg := Config{ + Diff: Diff{Threshold: ptr(1.0)}, + } + result := Analyze(cfg, stats, base) + assert.NotEmpty(t, result.Diff) + assert.True(t, result.Pass()) + assert.True(t, result.MeetsDiffThreshold()) + assert.Equal(t, 70.0, result.DiffPercentage) //nolint:testifylint //relax + }) + + t.Run("diff above threshold (small diff)", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 10000, Covered: 9999}} + stats := []coverage.Stats{{Name: "foo", Total: 10000, Covered: 10000}} + + cfg := Config{ + Diff: Diff{Threshold: ptr(0.0)}, + } + result := Analyze(cfg, stats, base) + assert.True(t, result.Pass()) + assert.True(t, result.MeetsDiffThreshold()) + assert.Equal(t, 0.01, result.DiffPercentage) //nolint:testifylint //relax + }) } func TestLoadBaseCoverageBreakdown(t *testing.T) { diff --git a/pkg/testcoverage/config.go b/pkg/testcoverage/config.go index fc245e0..929b9c1 100644 --- a/pkg/testcoverage/config.go +++ b/pkg/testcoverage/config.go @@ -53,7 +53,8 @@ type Exclude struct { } type Diff struct { - BaseBreakdownFileName string `yaml:"base-breakdown-file-name"` + BaseBreakdownFileName string `yaml:"base-breakdown-file-name"` + Threshold *float64 `yaml:"threshold,omitempty"` } type Badge struct { diff --git a/pkg/testcoverage/config_test.go b/pkg/testcoverage/config_test.go index d4f1bce..3bf8f28 100644 --- a/pkg/testcoverage/config_test.go +++ b/pkg/testcoverage/config_test.go @@ -240,6 +240,7 @@ func nonZeroConfig() Config { BreakdownFileName: "breakdown.testcoverage", Diff: Diff{ BaseBreakdownFileName: "breakdown.testcoverage", + Threshold: ptr(-1.01), }, GithubActionOutput: true, } @@ -261,7 +262,8 @@ exclude: - path2 breakdown-file-name: 'breakdown.testcoverage' diff: - base-breakdown-file-name: 'breakdown.testcoverage' + base-breakdown-file-name: 'breakdown.testcoverage' + threshold: -1.01 github-action-output: true` } diff --git a/pkg/testcoverage/coverage/types.go b/pkg/testcoverage/coverage/types.go index 185e1d9..80aaef7 100644 --- a/pkg/testcoverage/coverage/types.go +++ b/pkg/testcoverage/coverage/types.go @@ -27,7 +27,11 @@ func (s Stats) CoveredPercentage() int { } func (s Stats) CoveredPercentageF() float64 { - return coveredPercentageF(s.Total, s.Covered) + return coveredPercentageF(s.Total, s.Covered, true) +} + +func (s Stats) CoveredPercentageFNR() float64 { + return coveredPercentageF(s.Total, s.Covered, false) } //nolint:mnd // relax @@ -53,11 +57,11 @@ func StatsSearchMap(stats []Stats) map[string]Stats { } func CoveredPercentage(total, covered int64) int { - return int(coveredPercentageF(total, covered)) + return int(coveredPercentageF(total, covered, true)) } //nolint:mnd // relax -func coveredPercentageF(total, covered int64) float64 { +func coveredPercentageF(total, covered int64, round bool) float64 { if total == 0 { return 0 } @@ -68,6 +72,10 @@ func coveredPercentageF(total, covered int64) float64 { p := float64(covered*100) / float64(total) + if !round { + return p + } + // round to %.1f return float64(int(math.Round(p*10))) / 10 } diff --git a/pkg/testcoverage/export_test.go b/pkg/testcoverage/export_test.go index 4765f28..a85a0b8 100644 --- a/pkg/testcoverage/export_test.go +++ b/pkg/testcoverage/export_test.go @@ -17,6 +17,7 @@ var ( LoadBaseCoverageBreakdown = loadBaseCoverageBreakdown CompressUncoveredLines = compressUncoveredLines ReportUncoveredLines = reportUncoveredLines + StatusStr = statusStr ) type ( diff --git a/pkg/testcoverage/helpers_test.go b/pkg/testcoverage/helpers_test.go index 206682a..c4c18b9 100644 --- a/pkg/testcoverage/helpers_test.go +++ b/pkg/testcoverage/helpers_test.go @@ -3,6 +3,7 @@ package testcoverage_test import ( crand "crypto/rand" "encoding/hex" + "fmt" "math/rand" "os" "strings" @@ -14,6 +15,10 @@ import ( "github.com/vladopajic/go-test-coverage/v2/pkg/testcoverage/coverage" ) +func ptr[T any](t T) *T { + return &t +} + func mergeStats(a, b []coverage.Stats) []coverage.Stats { r := make([]coverage.Stats, 0, len(a)+len(b)) r = append(r, a...) @@ -22,6 +27,10 @@ func mergeStats(a, b []coverage.Stats) []coverage.Stats { return r } +func copyStats(s []coverage.Stats) []coverage.Stats { + return mergeStats(make([]coverage.Stats, 0), s) +} + func randStats(localPrefix string, minc, maxc int) []coverage.Stats { const count = 100 @@ -189,6 +198,35 @@ func assertNoUncoveredLinesInfo(t *testing.T, content string) { assert.Empty(t, uncoveredReport) } +func assertDiffNoChange(t *testing.T, content string) { + t.Helper() + + assert.Contains(t, content, "No coverage changes in any files compared to the base") +} + +func assertDiffChange(t *testing.T, content string, lines int) { + t.Helper() + + //nolint:lll //relax + str := fmt.Sprintf("Test coverage has changed in the current files, with %d lines missing coverage", lines) + assert.Contains(t, content, str) +} + +func assertDiffThreshold(t *testing.T, content string, thr float64, isSatisfied bool) { + t.Helper() + + //nolint:lll //relax + str := fmt.Sprintf("Coverage difference threshold (%.2f%%) satisfied:\t %s", thr, StatusStr(isSatisfied)) + assert.Contains(t, content, str) +} + +func assertDiffPercentage(t *testing.T, content string, p float64) { + t.Helper() + + str := fmt.Sprintf("Coverage difference: %.2f%%", p) + assert.Contains(t, content, str) +} + func assertGithubActionErrorsCount(t *testing.T, content string, count int) { t.Helper() @@ -245,3 +283,15 @@ func assertGithubOutputValues(t *testing.T, file string) { assertNonEmptyValue(t, content, GaOutputBadgeText) assertNonEmptyValue(t, content, GaOutputReport) } + +func readStats(t *testing.T, file string) []coverage.Stats { + t.Helper() + + contentBytes, err := os.ReadFile(file) + assert.NoError(t, err) + assert.NotEmpty(t, contentBytes) + stats, err := coverage.StatsDeserialize(contentBytes) + assert.NoError(t, err) + + return stats +} diff --git a/pkg/testcoverage/report.go b/pkg/testcoverage/report.go index 4568e25..12eacb3 100644 --- a/pkg/testcoverage/report.go +++ b/pkg/testcoverage/report.go @@ -27,14 +27,6 @@ func reportCoverage(w io.Writer, result AnalyzeResult) { tabber := tabwriter.NewWriter(w, 1, 8, 2, '\t', 0) //nolint:mnd // relax defer tabber.Flush() - statusStr := func(passing bool) string { - if passing { - return "PASS" - } - - return "FAIL" - } - thr := result.Threshold if thr.File > 0 || result.HasFileOverrides { // File threshold report @@ -95,6 +87,7 @@ func reportUncoveredLines(w io.Writer, result AnalyzeResult) { fmt.Fprintf(tabber, "\n") } +//nolint:lll // relax func reportDiff(w io.Writer, result AnalyzeResult) { if !result.HasBaseBreakdown { return @@ -103,13 +96,19 @@ func reportDiff(w io.Writer, result AnalyzeResult) { tabber := tabwriter.NewWriter(w, 1, 8, 2, '\t', 0) //nolint:mnd // relax defer tabber.Flush() + if result.DiffThreshold != nil { + status := statusStr(result.MeetsDiffThreshold()) + fmt.Fprintf(tabber, "\nCoverage difference threshold (%.2f%%) satisfied:\t %s", *result.DiffThreshold, status) + fmt.Fprintf(tabber, "\nCoverage difference: %.2f%%\n", result.DiffPercentage) + } + if len(result.Diff) == 0 { - fmt.Fprintf(tabber, "\nCurrent tests coverage has not changed.\n") + fmt.Fprintf(tabber, "\nNo coverage changes in any files compared to the base.\n") return } - td := TotalLinesDiff(result.Diff) - fmt.Fprintf(tabber, "\nCurrent tests coverage has changed with %d lines missing coverage.", td) + td := TotalLinesMissingCoverage(result.Diff) + fmt.Fprintf(tabber, "\nTest coverage has changed in the current files, with %d lines missing coverage.", td) fmt.Fprintf(tabber, "\n file:\tuncovered:\tcurrent coverage:\tbase coverage:") for _, d := range result.Diff { @@ -242,3 +241,11 @@ func compressUncoveredLines(w io.Writer, ull []int) { printRange(last, ull[len(ull)-1]) } } + +func statusStr(passing bool) string { + if passing { + return "PASS" + } + + return "FAIL" +} diff --git a/pkg/testcoverage/report_test.go b/pkg/testcoverage/report_test.go index e24cabb..06921a5 100644 --- a/pkg/testcoverage/report_test.go +++ b/pkg/testcoverage/report_test.go @@ -25,6 +25,7 @@ func Test_ReportForHuman(t *testing.T) { buf := &bytes.Buffer{} ReportForHuman(buf, AnalyzeResult{Threshold: thr, TotalStats: coverage.Stats{}}) + assertHumanReport(t, buf.String(), 3, 0) assertNoUncoveredLinesInfo(t, buf.String()) }) @@ -34,6 +35,7 @@ func Test_ReportForHuman(t *testing.T) { buf := &bytes.Buffer{} ReportForHuman(buf, AnalyzeResult{Threshold: thr, TotalStats: coverage.Stats{Total: 1}}) + assertHumanReport(t, buf.String(), 2, 1) assertNoUncoveredLinesInfo(t, buf.String()) }) @@ -48,6 +50,7 @@ func Test_ReportForHuman(t *testing.T) { allStats := mergeStats(statsWithError, statsNoError) result := Analyze(cfg, allStats, nil) ReportForHuman(buf, result) + headReport, uncoveredReport := splitReport(t, buf.String()) assertHumanReport(t, headReport, 0, 1) assertContainStats(t, headReport, statsWithError) @@ -70,6 +73,7 @@ func Test_ReportForHuman(t *testing.T) { allStats := mergeStats(statsWithError, statsNoError) result := Analyze(cfg, allStats, nil) ReportForHuman(buf, result) + headReport, uncoveredReport := splitReport(t, buf.String()) assertHumanReport(t, headReport, 0, 1) assertContainStats(t, headReport, MakePackageStats(statsWithError)) @@ -83,6 +87,13 @@ func Test_ReportForHuman(t *testing.T) { coverage.StatsPluckName(coverage.StatsFilterWithCoveredLines(allStats)), ) }) +} + +//nolint:dupl // relax +func Test_ReportForHumanDiff(t *testing.T) { + t.Parallel() + + const prefix = "organization.org" t.Run("diff - no change", func(t *testing.T) { t.Parallel() @@ -94,14 +105,14 @@ func Test_ReportForHuman(t *testing.T) { result := Analyze(cfg, stats, stats) ReportForHuman(buf, result) - assert.Contains(t, buf.String(), "Current tests coverage has not changed") + assertDiffNoChange(t, buf.String()) }) t.Run("diff - has change", func(t *testing.T) { t.Parallel() stats := randStats(prefix, 10, 100) - base := mergeStats(make([]coverage.Stats, 0), stats) + base := copyStats(stats) stats = append(stats, coverage.Stats{Name: "foo", Total: 9, Covered: 8}) stats = append(stats, coverage.Stats{Name: "foo-new", Total: 9, Covered: 8}) @@ -113,16 +124,70 @@ func Test_ReportForHuman(t *testing.T) { result := Analyze(cfg, stats, base) ReportForHuman(buf, result) - assert.Contains(t, buf.String(), - "Current tests coverage has changed with 2 lines missing coverage", - ) + assertDiffChange(t, buf.String(), 2) + assert.Contains(t, buf.String(), "foo\t\t 1\t\t88.9% (8/9)\t\t100% (10/10)") + assert.Contains(t, buf.String(), "foo-new\t 1\t\t88.9% (8/9)\t") + }) + + t.Run("diff - threshold failed", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 10, Covered: 1}} + stats := []coverage.Stats{{Name: "foo", Total: 10, Covered: 8}} + + buf := &bytes.Buffer{} + cfg := Config{ + Diff: Diff{Threshold: ptr(999.0)}, + } + result := Analyze(cfg, stats, base) + ReportForHuman(buf, result) + + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, false) + assertDiffPercentage(t, buf.String(), 70) + assertDiffChange(t, buf.String(), 2) + }) + + t.Run("diff - threshold pass", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 10, Covered: 1}} + stats := []coverage.Stats{{Name: "foo", Total: 10, Covered: 8}} + + buf := &bytes.Buffer{} + cfg := Config{ + Diff: Diff{Threshold: ptr(70.0)}, + } + result := Analyze(cfg, stats, base) + ReportForHuman(buf, result) + + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, true) + assertDiffPercentage(t, buf.String(), 70) + assertDiffChange(t, buf.String(), 2) + }) + + t.Run("diff - negative threshold pass", func(t *testing.T) { + t.Parallel() + + base := []coverage.Stats{{Name: "foo", Total: 100, Covered: 100}} + stats := []coverage.Stats{{Name: "foo", Total: 100, Covered: 90}} + + buf := &bytes.Buffer{} + cfg := Config{ + Diff: Diff{Threshold: ptr(-11.0)}, + } + result := Analyze(cfg, stats, base) + ReportForHuman(buf, result) + + assertDiffThreshold(t, buf.String(), *cfg.Diff.Threshold, true) + assertDiffPercentage(t, buf.String(), -10) + assertDiffChange(t, buf.String(), 10) }) } func Test_ReportForGithubAction(t *testing.T) { t.Parallel() - prefix := "organization.org/pkg/" + const prefix = "organization.org/pkg/" t.Run("total coverage - pass", func(t *testing.T) { t.Parallel() diff --git a/pkg/testcoverage/types.go b/pkg/testcoverage/types.go index 5e2aa48..2ff9b77 100644 --- a/pkg/testcoverage/types.go +++ b/pkg/testcoverage/types.go @@ -2,6 +2,7 @@ package testcoverage import ( "maps" + "math" "slices" "strings" @@ -10,12 +11,14 @@ import ( type AnalyzeResult struct { Threshold Threshold + DiffThreshold *float64 FilesBelowThreshold []coverage.Stats PackagesBelowThreshold []coverage.Stats FilesWithUncoveredLines []coverage.Stats TotalStats coverage.Stats HasBaseBreakdown bool Diff []FileCoverageDiff + DiffPercentage float64 HasFileOverrides bool HasPackageOverrides bool } @@ -23,7 +26,16 @@ type AnalyzeResult struct { func (r *AnalyzeResult) Pass() bool { return r.MeetsTotalCoverage() && len(r.FilesBelowThreshold) == 0 && - len(r.PackagesBelowThreshold) == 0 + len(r.PackagesBelowThreshold) == 0 && + r.MeetsDiffThreshold() +} + +func (r *AnalyzeResult) MeetsDiffThreshold() bool { + if r.DiffThreshold == nil || !r.HasBaseBreakdown { + return true + } + + return *r.DiffThreshold <= r.DiffPercentage } func (r *AnalyzeResult) MeetsTotalCoverage() bool { @@ -109,7 +121,7 @@ func calculateStatsDiff(current, base []coverage.Stats) []FileCoverageDiff { return res } -func TotalLinesDiff(diff []FileCoverageDiff) int { +func TotalLinesMissingCoverage(diff []FileCoverageDiff) int { r := 0 for _, d := range diff { r += d.Current.UncoveredLinesCount() @@ -117,3 +129,16 @@ func TotalLinesDiff(diff []FileCoverageDiff) int { return r } + +func TotalPercentageDiff(current, base []coverage.Stats) float64 { + curretStats := coverage.StatsCalcTotal(current) + baseStats := coverage.StatsCalcTotal(base) + + cp := curretStats.CoveredPercentageFNR() + bp := baseStats.CoveredPercentageFNR() + + p := cp - bp + + // round to %.2f + return float64(int(math.Round(p*100))) / 100 //nolint:mnd //relax +}