From 84b077202f093176e82931cffe2d1b2e19f0246a Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 3 Feb 2023 15:35:53 +0200 Subject: [PATCH 1/4] Improve code coverage functionality Add support for: - Inspecting program statements and their lines - Calculating covered statements percentage - Tracking covered lines - Tracking missed lines - Excluding locations when collecting code coverage --- runtime/coverage.go | 197 +++++++++++++- runtime/coverage_test.go | 542 ++++++++++++++++++++++++++++++++++++--- runtime/environment.go | 5 + 3 files changed, 692 insertions(+), 52 deletions(-) diff --git a/runtime/coverage.go b/runtime/coverage.go index 32d056499c..b3b915758f 100644 --- a/runtime/coverage.go +++ b/runtime/coverage.go @@ -20,56 +20,229 @@ package runtime import ( "encoding/json" + "fmt" + "sort" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" ) -// LocationCoverage records coverage information for a location +// LocationCoverage records coverage information for a location. type LocationCoverage struct { - LineHits map[int]int `json:"line_hits"` + // Contains hit count for each line on a given location. + // A hit count of 0 means the line was not covered. + LineHits map[int]int + // Total number of statements on a given location. + Statements int } +// AddLineHit increments the hit count for the given line. func (c *LocationCoverage) AddLineHit(line int) { c.LineHits[line]++ } -func NewLocationCoverage() *LocationCoverage { +// Percentage returns a string representation of the covered +// statements percentage. It is defined as the ratio of covered +// lines over the total statements for a given location. +func (c *LocationCoverage) Percentage() string { + coveredLines := c.CoveredLines() + if coveredLines > c.Statements { + // We saturate the percentage at 100%, when the inspector + // fails to correctly count all statements for a given + // location. + coveredLines = c.Statements + } + percentage := 100 * float64(coveredLines) / float64(c.Statements) + return fmt.Sprintf("%0.1f%%", percentage) +} + +// CoveredLines returns the count of covered lines for a given location. +// This is the number of lines with a hit count > 0. +func (c *LocationCoverage) CoveredLines() int { + coveredLines := 0 + for _, hits := range c.LineHits { // nolint:maprange + if hits > 0 { + coveredLines += 1 + } + } + return coveredLines +} + +// MissedLines returns an array with the missed lines for a given location. +// These are all the lines with a hit count == 0. The resulting array is +// sorted in ascending order. +func (c *LocationCoverage) MissedLines() []int { + missedLines := make([]int, 0) + for line, hits := range c.LineHits { // nolint:maprange + if hits == 0 { + missedLines = append(missedLines, line) + } + } + sort.Ints(missedLines) + return missedLines +} + +// NewLocationCoverage creates and returns a *LocationCoverage with the +// given lineHits map. +func NewLocationCoverage(lineHits map[int]int) *LocationCoverage { return &LocationCoverage{ - LineHits: map[int]int{}, + LineHits: lineHits, + Statements: len(lineHits), } } -// CoverageReport is a collection of coverage per location +// CoverageReport collects coverage information per location. +// It keeps track of inspected programs per location, and can +// also exclude locations from coverage collection. type CoverageReport struct { + // Contains a *LocationCoverage per location. Coverage map[common.Location]*LocationCoverage `json:"-"` + // Contains an *ast.Program per location. + Programs map[common.Location]*ast.Program `json:"-"` + // Contains locations excluded from coverage collection. + ExcludedLocations []common.Location `json:"-"` } +// ExcludeLocation adds the given location to the array of excluded +// locations. +func (r *CoverageReport) ExcludeLocation(location Location) { + if r.IsLocationExcluded(location) { + return + } + r.ExcludedLocations = append(r.ExcludedLocations, location) +} + +// IsLocationExcluded checks whether the given location is excluded +// or not, from coverage collection. +func (r *CoverageReport) IsLocationExcluded(location Location) bool { + for _, excludedLocation := range r.ExcludedLocations { + if excludedLocation == location { + return true + } + } + return false +} + +// AddLineHit increments the hit count for the given line, on the given +// location. The method call is a NO-OP in two cases: +// - If the location is excluded from coverage collection +// - If the location's *ast.Program, has not been inspected func (r *CoverageReport) AddLineHit(location Location, line int) { - locationCoverage := r.Coverage[location] - if locationCoverage == nil { - locationCoverage = NewLocationCoverage() - r.Coverage[location] = locationCoverage + if r.IsLocationExcluded(location) { + return + } + + if !r.IsProgramInspected(location) { + return } + + locationCoverage := r.Coverage[location] locationCoverage.AddLineHit(line) } +// InspectProgram inspects the elements of the given *ast.Program, and counts its +// statements. If inspection is successful, the *ast.Program is marked as inspected. +// If the given location is excluded from coverage collection, the method call +// results in a NO-OP. +func (r *CoverageReport) InspectProgram(location Location, program *ast.Program) { + if r.IsLocationExcluded(location) { + return + } + r.Programs[location] = program + lineHits := make(map[int]int, 0) + var depth int + + inspector := ast.NewInspector(program) + inspector.Elements( + nil, func(element ast.Element, push bool) bool { + if push { + depth++ + + _, isStatement := element.(ast.Statement) + _, isDeclaration := element.(ast.Declaration) + _, isVariableDeclaration := element.(*ast.VariableDeclaration) + + // Track only the statements that are not declarations, such as: + // - *ast.CompositeDeclaration + // - *ast.SpecialFunctionDeclaration + // - *ast.FunctionDeclaration + // However, also track local (i.e. non-top level) variable declarations. + if (isStatement && !isDeclaration) || + (isVariableDeclaration && depth > 2) { + line := element.StartPosition().Line + lineHits[line] = 0 + } + } else { + depth-- + } + + return true + }) + + locationCoverage := NewLocationCoverage(lineHits) + r.Coverage[location] = locationCoverage +} + +// IsProgramInspected checks whether the *ast.Program on the given +// location, has been inspected or not. +func (r *CoverageReport) IsProgramInspected(location Location) bool { + _, isInspected := r.Programs[location] + return isInspected +} + +// CoveredStatementsPercentage returns a string representation of +// the covered statements percentage. It is defined as the ratio +// of total covered lines over total statements, for all locations. +func (r *CoverageReport) CoveredStatementsPercentage() string { + totalStatements := 0 + totalCoveredLines := 0 + for _, locationCoverage := range r.Coverage { // nolint:maprange + totalStatements += locationCoverage.Statements + totalCoveredLines += locationCoverage.CoveredLines() + } + percentage := fmt.Sprintf( + "%0.1f%%", + 100*float64(totalCoveredLines)/float64(totalStatements), + ) + return fmt.Sprintf("Coverage: %v of statements", percentage) +} + +// NewCoverageReport creates and returns a *CoverageReport. func NewCoverageReport() *CoverageReport { return &CoverageReport{ Coverage: map[common.Location]*LocationCoverage{}, + Programs: map[common.Location]*ast.Program{}, } } +// MarshalJSON serializes each common.Location/*LocationCoverage +// key/value pair on the *CoverageReport.Coverage map. func (r *CoverageReport) MarshalJSON() ([]byte, error) { type Alias CoverageReport - coverage := make(map[string]*LocationCoverage, len(r.Coverage)) + // To avoid the overhead of having the Percentage & MissedLines + // as fields in the LocationCoverage struct, we simply populate + // this LC struct, with the corresponding methods, upon marshalling. + type LC struct { + LineHits map[int]int `json:"line_hits"` + MissedLines []int `json:"missed_lines"` + Statements int `json:"statements"` + Percentage string `json:"percentage"` + } + + coverage := make(map[string]LC, len(r.Coverage)) for location, locationCoverage := range r.Coverage { // nolint:maprange typeID := location.TypeID(nil, "") locationID := typeID[:len(typeID)-1] - coverage[string(locationID)] = locationCoverage + coverage[string(locationID)] = LC{ + LineHits: locationCoverage.LineHits, + MissedLines: locationCoverage.MissedLines(), + Statements: locationCoverage.Statements, + Percentage: locationCoverage.Percentage(), + } } return json.Marshal(&struct { - Coverage map[string]*LocationCoverage `json:"coverage"` + Coverage map[string]LC `json:"coverage"` *Alias }{ Coverage: coverage, diff --git a/runtime/coverage_test.go b/runtime/coverage_test.go index c852aec554..09ebc6474e 100644 --- a/runtime/coverage_test.go +++ b/runtime/coverage_test.go @@ -28,8 +28,272 @@ import ( "github.com/onflow/cadence" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/parser" ) +func TestNewLocationCoverage(t *testing.T) { + + t.Parallel() + + // Represents line numbers with statement execution count. + // For the time being, if a line has two statements, we cannot + // distinguish between their hits separately. + // For example: "if let index = self.index(s, until, startIndex) {" + lineHits := map[int]int{3: 0, 4: 0, 5: 0, 7: 0, 9: 0, 11: 0} + locationCoverage := NewLocationCoverage(lineHits) + + assert.Equal( + t, + map[int]int{3: 0, 4: 0, 5: 0, 7: 0, 9: 0, 11: 0}, + locationCoverage.LineHits, + ) + assert.Equal( + t, + []int{3, 4, 5, 7, 9, 11}, + locationCoverage.MissedLines(), + ) + assert.Equal(t, 6, locationCoverage.Statements) + assert.Equal(t, "0.0%", locationCoverage.Percentage()) + assert.Equal(t, 0, locationCoverage.CoveredLines()) +} + +func TestLocationCoverageAddLineHit(t *testing.T) { + + t.Parallel() + + lineHits := map[int]int{3: 0, 4: 0, 5: 0, 7: 0, 9: 0, 11: 0} + locationCoverage := NewLocationCoverage(lineHits) + + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(7) + locationCoverage.AddLineHit(9) + // Line 15 was not included in the lineHits map, however we + // want it to be tracked. This will help to find out about + // cases where the inspector does not find all the statements. + // We should also discuss if the Statements counter should be + // increased in this case. + // TBD + locationCoverage.AddLineHit(15) + + assert.Equal( + t, + map[int]int{3: 2, 4: 0, 5: 0, 7: 1, 9: 1, 11: 0, 15: 1}, + locationCoverage.LineHits, + ) + assert.Equal(t, 6, locationCoverage.Statements) + assert.Equal(t, "66.7%", locationCoverage.Percentage()) +} + +func TestLocationCoverageCoveredLines(t *testing.T) { + + t.Parallel() + + lineHits := map[int]int{3: 0, 4: 0, 5: 0, 7: 0, 9: 0, 11: 0} + locationCoverage := NewLocationCoverage(lineHits) + + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(7) + locationCoverage.AddLineHit(9) + locationCoverage.AddLineHit(15) + + assert.Equal(t, 4, locationCoverage.CoveredLines()) +} + +func TestLocationCoverageMissedLines(t *testing.T) { + t.Parallel() + + lineHits := map[int]int{3: 0, 4: 0, 5: 0, 7: 0, 9: 0, 11: 0} + locationCoverage := NewLocationCoverage(lineHits) + + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(7) + locationCoverage.AddLineHit(9) + locationCoverage.AddLineHit(15) + + assert.Equal( + t, + []int{4, 5, 11}, + locationCoverage.MissedLines(), + ) +} + +func TestLocationCoveragePercentage(t *testing.T) { + + t.Parallel() + + lineHits := map[int]int{3: 0, 4: 0, 5: 0} + locationCoverage := NewLocationCoverage(lineHits) + + locationCoverage.AddLineHit(3) + locationCoverage.AddLineHit(4) + locationCoverage.AddLineHit(5) + // Note: Line 15 was not included in the lineHits map, + // but we saturate the percentage at 100%. + locationCoverage.AddLineHit(15) + + assert.Equal(t, "100.0%", locationCoverage.Percentage()) +} + +func TestNewCoverageReport(t *testing.T) { + + t.Parallel() + + coverageReport := NewCoverageReport() + + assert.Equal(t, 0, len(coverageReport.Coverage)) + assert.Equal(t, 0, len(coverageReport.Programs)) + assert.Equal(t, 0, len(coverageReport.ExcludedLocations)) +} + +func TestCoverageReportExcludeLocation(t *testing.T) { + + t.Parallel() + + coverageReport := NewCoverageReport() + + location := common.StringLocation("FooContract") + coverageReport.ExcludeLocation(location) + // We do not allow duplicate locations + coverageReport.ExcludeLocation(location) + + assert.Equal(t, 1, len(coverageReport.ExcludedLocations)) + assert.Equal(t, true, coverageReport.IsLocationExcluded(location)) +} + +func TestCoverageReportInspectProgram(t *testing.T) { + + t.Parallel() + + script := []byte(` + pub fun answer(): Int { + var i = 0 + while i < 42 { + i = i + 1 + } + return i + } + `) + + program, err := parser.ParseProgram(nil, script, parser.Config{}) + require.NoError(t, err) + + coverageReport := NewCoverageReport() + + location := common.StringLocation("AnswerScript") + coverageReport.InspectProgram(location, program) + + assert.Equal(t, 1, len(coverageReport.Coverage)) + assert.Equal(t, 1, len(coverageReport.Programs)) + assert.Equal(t, true, coverageReport.IsProgramInspected(location)) +} + +func TestCoverageReportInspectProgramForExcludedLocation(t *testing.T) { + + t.Parallel() + + script := []byte(` + pub fun answer(): Int { + var i = 0 + while i < 42 { + i = i + 1 + } + return i + } + `) + + program, err := parser.ParseProgram(nil, script, parser.Config{}) + require.NoError(t, err) + + coverageReport := NewCoverageReport() + + location := common.StringLocation("AnswerScript") + coverageReport.ExcludeLocation(location) + coverageReport.InspectProgram(location, program) + + assert.Equal(t, 0, len(coverageReport.Coverage)) + assert.Equal(t, 0, len(coverageReport.Programs)) + assert.Equal(t, false, coverageReport.IsProgramInspected(location)) +} + +func TestCoverageReportAddLineHit(t *testing.T) { + + t.Parallel() + + script := []byte(` + pub fun answer(): Int { + var i = 0 + while i < 42 { + i = i + 1 + } + return i + } + `) + + program, err := parser.ParseProgram(nil, script, parser.Config{}) + require.NoError(t, err) + + coverageReport := NewCoverageReport() + + location := common.StringLocation("AnswerScript") + coverageReport.InspectProgram(location, program) + + coverageReport.AddLineHit(location, 3) + coverageReport.AddLineHit(location, 3) + coverageReport.AddLineHit(location, 5) + + locationCoverage := coverageReport.Coverage[location] + + assert.Equal( + t, + map[int]int{3: 2, 4: 0, 5: 1, 7: 0}, + locationCoverage.LineHits, + ) + assert.Equal( + t, + []int{4, 7}, + locationCoverage.MissedLines(), + ) + assert.Equal(t, 4, locationCoverage.Statements) + assert.Equal(t, "50.0%", locationCoverage.Percentage()) + assert.Equal(t, 2, locationCoverage.CoveredLines()) +} + +func TestCoverageReportAddLineHitForExcludedLocation(t *testing.T) { + + t.Parallel() + + coverageReport := NewCoverageReport() + + location := common.StringLocation("AnswerScript") + coverageReport.ExcludeLocation(location) + + coverageReport.AddLineHit(location, 3) + coverageReport.AddLineHit(location, 5) + + assert.Equal(t, 0, len(coverageReport.Coverage)) + assert.Equal(t, 0, len(coverageReport.Programs)) + assert.Equal(t, false, coverageReport.IsProgramInspected(location)) +} + +func TestCoverageReportAddLineHitForNonInspectedProgram(t *testing.T) { + + t.Parallel() + + coverageReport := NewCoverageReport() + + location := common.StringLocation("AnswerScript") + + coverageReport.AddLineHit(location, 3) + coverageReport.AddLineHit(location, 5) + + assert.Equal(t, 0, len(coverageReport.Coverage)) + assert.Equal(t, 0, len(coverageReport.Programs)) + assert.Equal(t, false, coverageReport.IsProgramInspected(location)) +} + func TestRuntimeCoverage(t *testing.T) { t.Parallel() @@ -39,26 +303,64 @@ func TestRuntimeCoverage(t *testing.T) { }) importedScript := []byte(` - pub fun answer(): Int { - var i = 0 - while i < 42 { - i = i + 1 - } - return i - } - `) + pub let specialNumbers: {Int: String} = { + 1729: "Harshad", + 8128: "Harmonic", + 41041: "Carmichael" + } + + pub fun addSpecialNumber(_ n: Int, _ trait: String) { + specialNumbers[n] = trait + } + + pub fun getIntegerTrait(_ n: Int): String { + if n < 0 { + return "Negative" + } else if n == 0 { + return "Zero" + } else if n < 10 { + return "Small" + } else if n < 100 { + return "Big" + } else if n < 1000 { + return "Huge" + } + + if specialNumbers.containsKey(n) { + return specialNumbers[n]! + } + + return "Enormous" + } + `) script := []byte(` - import "imported" + import "imported" + + pub fun main(): Int { + let testInputs: {Int: String} = { + -1: "Negative", + 0: "Zero", + 9: "Small", + 99: "Big", + 999: "Huge", + 1001: "Enormous", + 1729: "Harshad", + 8128: "Harmonic", + 41041: "Carmichael" + } + + for input in testInputs.keys { + let result = getIntegerTrait(input) + assert(result == testInputs[input]) + } - pub fun main(): Int { - let answer = answer() - if answer != 42 { - panic("?!") - } - return answer - } - `) + addSpecialNumber(78557, "Sierpinski") + assert("Sierpinski" == getIntegerTrait(78557)) + + return 42 + } + `) runtimeInterface := &testRuntimeInterface{ getCode: func(location Location) (bytes []byte, err error) { @@ -90,28 +392,188 @@ func TestRuntimeCoverage(t *testing.T) { actual, err := json.Marshal(coverageReport) require.NoError(t, err) - require.JSONEq(t, - ` - { - "coverage": { - "S.imported": { - "line_hits": { - "3": 1, - "4": 1, - "5": 42, - "7": 1 - } - }, - "s.0000000000000000000000000000000000000000000000000000000000000000": { - "line_hits": { - "5": 1, - "6": 1, - "9": 1 - } - } - } - } - `, - string(actual), + expected := ` + { + "coverage": { + "S.imported": { + "line_hits": { + "13": 10, + "14": 1, + "15": 9, + "16": 1, + "17": 8, + "18": 1, + "19": 7, + "20": 1, + "21": 6, + "22": 1, + "25": 5, + "26": 4, + "29": 1, + "9": 1 + }, + "missed_lines": [], + "statements": 14, + "percentage": "100.0%" + }, + "s.0000000000000000000000000000000000000000000000000000000000000000": { + "line_hits": { + "17": 1, + "18": 9, + "19": 9, + "22": 1, + "23": 1, + "25": 1, + "5": 1 + }, + "missed_lines": [], + "statements": 7, + "percentage": "100.0%" + } + } + } + ` + require.JSONEq(t, expected, string(actual)) + + assert.Equal( + t, + "Coverage: 100.0% of statements", + coverageReport.CoveredStatementsPercentage(), + ) +} + +func TestRuntimeCoverageWithExcludedLocation(t *testing.T) { + + t.Parallel() + + runtime := NewInterpreterRuntime(Config{ + CoverageReportingEnabled: true, + }) + + importedScript := []byte(` + pub let specialNumbers: {Int: String} = { + 1729: "Harshad", + 8128: "Harmonic", + 41041: "Carmichael" + } + + pub fun addSpecialNumber(_ n: Int, _ trait: String) { + specialNumbers[n] = trait + } + + pub fun getIntegerTrait(_ n: Int): String { + if n < 0 { + return "Negative" + } else if n == 0 { + return "Zero" + } else if n < 10 { + return "Small" + } else if n < 100 { + return "Big" + } else if n < 1000 { + return "Huge" + } + + if specialNumbers.containsKey(n) { + return specialNumbers[n]! + } + + return "Enormous" + } + `) + + script := []byte(` + import "imported" + + pub fun main(): Int { + let testInputs: {Int: String} = { + -1: "Negative", + 0: "Zero", + 9: "Small", + 99: "Big", + 999: "Huge", + 1001: "Enormous", + 1729: "Harshad", + 8128: "Harmonic", + 41041: "Carmichael" + } + + for input in testInputs.keys { + let result = getIntegerTrait(input) + assert(result == testInputs[input]) + } + + addSpecialNumber(78557, "Sierpinski") + assert("Sierpinski" == getIntegerTrait(78557)) + + return 42 + } + `) + + runtimeInterface := &testRuntimeInterface{ + getCode: func(location Location) (bytes []byte, err error) { + switch location { + case common.StringLocation("imported"): + return importedScript, nil + default: + return nil, fmt.Errorf("unknown import location: %s", location) + } + }, + } + + coverageReport := NewCoverageReport() + scriptlocation := common.ScriptLocation{} + coverageReport.ExcludeLocation(scriptlocation) + + value, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: scriptlocation, + CoverageReport: coverageReport, + }, + ) + require.NoError(t, err) + + assert.Equal(t, cadence.NewInt(42), value) + + actual, err := json.Marshal(coverageReport) + require.NoError(t, err) + + expected := ` + { + "coverage": { + "S.imported": { + "line_hits": { + "13": 10, + "14": 1, + "15": 9, + "16": 1, + "17": 8, + "18": 1, + "19": 7, + "20": 1, + "21": 6, + "22": 1, + "25": 5, + "26": 4, + "29": 1, + "9": 1 + }, + "missed_lines": [], + "statements": 14, + "percentage": "100.0%" + } + } + } + ` + require.JSONEq(t, expected, string(actual)) + + assert.Equal( + t, + "Coverage: 100.0% of statements", + coverageReport.CoveredStatementsPercentage(), ) } diff --git a/runtime/environment.go b/runtime/environment.go index c63be6f60c..3c6425feff 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -620,6 +620,11 @@ func (e *interpreterEnvironment) newOnStatementHandler() interpreter.OnStatement return func(inter *interpreter.Interpreter, statement ast.Statement) { location := inter.Location + if !e.coverageReport.IsProgramInspected(location) { + program := inter.Program.Program + e.coverageReport.InspectProgram(location, program) + } + line := statement.StartPosition().Line e.coverageReport.AddLineHit(location, line) } From a3d875ede68bcef2b210bfc5b648e94cc0dfaf68 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 9 Feb 2023 09:50:15 +0200 Subject: [PATCH 2/4] fixup! Improve code coverage functionality --- runtime/coverage.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/runtime/coverage.go b/runtime/coverage.go index b3b915758f..eb4d4bf3d1 100644 --- a/runtime/coverage.go +++ b/runtime/coverage.go @@ -100,27 +100,20 @@ type CoverageReport struct { // Contains an *ast.Program per location. Programs map[common.Location]*ast.Program `json:"-"` // Contains locations excluded from coverage collection. - ExcludedLocations []common.Location `json:"-"` + ExcludedLocations map[common.Location]struct{} `json:"-"` } // ExcludeLocation adds the given location to the array of excluded // locations. func (r *CoverageReport) ExcludeLocation(location Location) { - if r.IsLocationExcluded(location) { - return - } - r.ExcludedLocations = append(r.ExcludedLocations, location) + r.ExcludedLocations[location] = struct{}{} } // IsLocationExcluded checks whether the given location is excluded // or not, from coverage collection. func (r *CoverageReport) IsLocationExcluded(location Location) bool { - for _, excludedLocation := range r.ExcludedLocations { - if excludedLocation == location { - return true - } - } - return false + _, ok := r.ExcludedLocations[location] + return ok } // AddLineHit increments the hit count for the given line, on the given @@ -210,8 +203,9 @@ func (r *CoverageReport) CoveredStatementsPercentage() string { // NewCoverageReport creates and returns a *CoverageReport. func NewCoverageReport() *CoverageReport { return &CoverageReport{ - Coverage: map[common.Location]*LocationCoverage{}, - Programs: map[common.Location]*ast.Program{}, + Coverage: map[common.Location]*LocationCoverage{}, + Programs: map[common.Location]*ast.Program{}, + ExcludedLocations: map[common.Location]struct{}{}, } } From 1f151ef185506251358cb65790bdc154fb5f9269 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 9 Feb 2023 15:54:02 +0200 Subject: [PATCH 3/4] fixup! fixup! Improve code coverage functionality --- runtime/coverage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/coverage.go b/runtime/coverage.go index eb4d4bf3d1..01894e6369 100644 --- a/runtime/coverage.go +++ b/runtime/coverage.go @@ -103,7 +103,7 @@ type CoverageReport struct { ExcludedLocations map[common.Location]struct{} `json:"-"` } -// ExcludeLocation adds the given location to the array of excluded +// ExcludeLocation adds the given location to the map of excluded // locations. func (r *CoverageReport) ExcludeLocation(location Location) { r.ExcludedLocations[location] = struct{}{} From cd4f4df3fa20b841afef4cf477f28d0508244555 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 10 Feb 2023 15:33:18 +0200 Subject: [PATCH 4/4] fixup! fixup! fixup! Improve code coverage functionality --- runtime/coverage.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runtime/coverage.go b/runtime/coverage.go index 01894e6369..a3dc369091 100644 --- a/runtime/coverage.go +++ b/runtime/coverage.go @@ -46,6 +46,14 @@ func (c *LocationCoverage) AddLineHit(line int) { // lines over the total statements for a given location. func (c *LocationCoverage) Percentage() string { coveredLines := c.CoveredLines() + // The ground truth of which statements are interpreted/executed + // is the `interpreterEnvironment.newOnStatementHandler()` function. + // This means that every call of `CoverageReport.AddLineHit()` from + // that callback, should be respected. The `CoverageReport.InspectProgram()` + // may have failed, for whatever reason, to find a specific line. + // This is a good insight to solidify its implementation and debug + // the inspection failure. Ideally, this condition will never be true, + // except for tests. We just leave it here, as a fail-safe mechanism. if coveredLines > c.Statements { // We saturate the percentage at 100%, when the inspector // fails to correctly count all statements for a given