Skip to content

Commit

Permalink
Allow specifying a mapping with human-friendly aliases for locations …
Browse files Browse the repository at this point in the history
…in coverage reports

Having AddressLocation or StringLocation in coverage reports, is not usable
when trying to use these reports with standard tools, such as LCOV.
  • Loading branch information
m-Peter committed Oct 9, 2023
1 parent 735a071 commit 38640c0
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 42 deletions.
39 changes: 37 additions & 2 deletions runtime/coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ type CoverageReport struct {
// This filter can be used to inject custom logic on
// each location/program inspection.
LocationFilter LocationFilter `json:"-"`
// Contains a mapping with human-friendly names for
// locations. This could be the filepath of a location.
LocationMappings map[string]string `json:"-"`
}

// WithLocationFilter sets the LocationFilter for the current
Expand All @@ -130,6 +133,14 @@ func (r *CoverageReport) WithLocationFilter(
r.LocationFilter = locationFilter
}

// WithLocationMappings sets the LocationMappings for the current
// CoverageReport.
func (r *CoverageReport) WithLocationMappings(
locationMappings map[string]string,
) {
r.LocationMappings = locationMappings
}

// ExcludeLocation adds the given location to the map of excluded
// locations.
func (r *CoverageReport) ExcludeLocation(location Location) {
Expand Down Expand Up @@ -423,7 +434,8 @@ type lcAlias struct {
func (r *CoverageReport) MarshalJSON() ([]byte, error) {
coverage := make(map[string]lcAlias, len(r.Coverage))
for location, locationCoverage := range r.Coverage { // nolint:maprange
coverage[location.ID()] = lcAlias{
locationSource := r.locationSource(location)
coverage[locationSource] = lcAlias{
LineHits: locationCoverage.LineHits,
MissedLines: locationCoverage.MissedLines(),
Statements: locationCoverage.Statements,
Expand Down Expand Up @@ -505,7 +517,8 @@ func (r *CoverageReport) MarshalLCOV() ([]byte, error) {
buf := new(bytes.Buffer)
for _, location := range locations {
coverage := r.Coverage[location]
_, err := fmt.Fprintf(buf, "TN:\nSF:%s\n", location.ID())
locationSource := r.locationSource(location)
_, err := fmt.Fprintf(buf, "TN:\nSF:%s\n", locationSource)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -539,3 +552,25 @@ func (r *CoverageReport) MarshalLCOV() ([]byte, error) {

return buf.Bytes(), nil
}

// Given a common.Location, returns its mapped source, if any.
// Defaults to the location's ID().
func (r *CoverageReport) locationSource(location common.Location) string {
var locationIdentifier string

switch loc := location.(type) {
case common.AddressLocation:
locationIdentifier = loc.Name
case common.StringLocation:
locationIdentifier = loc.String()
default:
locationIdentifier = loc.ID()
}

locationSource, ok := r.LocationMappings[locationIdentifier]
if !ok {
locationSource = location.ID()
}

return locationSource
}
236 changes: 196 additions & 40 deletions runtime/coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,89 @@ func TestCoverageReportWithAddressLocation(t *testing.T) {
require.JSONEq(t, expected, string(actual))
}

func TestCoverageReportWithLocationMappings(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)

locationMappings := map[string]string{
"Answer": "cadence/scripts/answer.cdc",
}
coverageReport := NewCoverageReport()
coverageReport.WithLocationMappings(locationMappings)

t.Run("with AddressLocation", func(t *testing.T) {
location := common.AddressLocation{
Address: common.MustBytesToAddress([]byte{1, 2}),
Name: "Answer",
}
coverageReport.InspectProgram(location, program)

actual, err := json.Marshal(coverageReport)
require.NoError(t, err)

expected := `
{
"coverage": {
"cadence/scripts/answer.cdc": {
"line_hits": {
"3": 0,
"4": 0,
"5": 0,
"7": 0
},
"missed_lines": [3, 4, 5, 7],
"statements": 4,
"percentage": "0.0%"
}
},
"excluded_locations": []
}
`
require.JSONEq(t, expected, string(actual))
})

t.Run("with StringLocation", func(t *testing.T) {
location := common.StringLocation("Answer")
coverageReport.InspectProgram(location, program)

actual, err := json.Marshal(coverageReport)
require.NoError(t, err)

expected := `
{
"coverage": {
"cadence/scripts/answer.cdc": {
"line_hits": {
"3": 0,
"4": 0,
"5": 0,
"7": 0
},
"missed_lines": [3, 4, 5, 7],
"statements": 4,
"percentage": "0.0%"
}
},
"excluded_locations": []
}
`
require.JSONEq(t, expected, string(actual))
})
}

func TestCoverageReportReset(t *testing.T) {

t.Parallel()
Expand Down Expand Up @@ -1790,43 +1873,114 @@ func TestCoverageReportLCOVFormat(t *testing.T) {
}
`)

coverageReport := NewCoverageReport()
scriptlocation := common.ScriptLocation{}
coverageReport.ExcludeLocation(scriptlocation)

runtimeInterface := &testRuntimeInterface{
getCode: func(location Location) (bytes []byte, err error) {
switch location {
case common.StringLocation("IntegerTraits"):
return integerTraits, nil
default:
return nil, fmt.Errorf("unknown import location: %s", location)
}
},
}

runtime := newTestInterpreterRuntime()
runtime.defaultConfig.CoverageReport = coverageReport

value, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: scriptlocation,
CoverageReport: coverageReport,
},
)
require.NoError(t, err)
t.Run("without location mappings", func(t *testing.T) {
coverageReport := NewCoverageReport()
scriptlocation := common.ScriptLocation{}
coverageReport.ExcludeLocation(scriptlocation)

runtimeInterface := &testRuntimeInterface{
getCode: func(location Location) (bytes []byte, err error) {
switch location {
case common.StringLocation("IntegerTraits"):
return integerTraits, nil
default:
return nil, fmt.Errorf("unknown import location: %s", location)
}
},
}

runtime := newTestInterpreterRuntime()
runtime.defaultConfig.CoverageReport = coverageReport

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 := coverageReport.MarshalLCOV()
require.NoError(t, err)

expected := `TN:
SF:S.IntegerTraits
DA:9,1
DA:13,10
DA:14,1
DA:15,9
DA:16,1
DA:17,8
DA:18,1
DA:19,7
DA:20,1
DA:21,6
DA:22,1
DA:25,5
DA:26,4
DA:29,1
LF:14
LH:14
end_of_record
`

assert.Equal(t, cadence.NewInt(42), value)
require.Equal(t, expected, string(actual))

actual, err := coverageReport.MarshalLCOV()
require.NoError(t, err)
assert.Equal(
t,
"Coverage: 100.0% of statements",
coverageReport.String(),
)
})

expected := `TN:
SF:S.IntegerTraits
t.Run("with location mappings", func(t *testing.T) {
locationMappings := map[string]string{
"IntegerTraits": "cadence/contracts/IntegerTraits.cdc",
}
coverageReport := NewCoverageReport()
coverageReport.WithLocationMappings(locationMappings)
scriptlocation := common.ScriptLocation{}
coverageReport.ExcludeLocation(scriptlocation)

runtimeInterface := &testRuntimeInterface{
getCode: func(location Location) (bytes []byte, err error) {
switch location {
case common.StringLocation("IntegerTraits"):
return integerTraits, nil
default:
return nil, fmt.Errorf("unknown import location: %s", location)
}
},
}

runtime := newTestInterpreterRuntime()
runtime.defaultConfig.CoverageReport = coverageReport

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 := coverageReport.MarshalLCOV()
require.NoError(t, err)

expected := `TN:
SF:cadence/contracts/IntegerTraits.cdc
DA:9,1
DA:13,10
DA:14,1
Expand All @@ -1845,11 +1999,13 @@ LF:14
LH:14
end_of_record
`
require.Equal(t, expected, string(actual))
require.Equal(t, expected, string(actual))

assert.Equal(
t,
"Coverage: 100.0% of statements",
coverageReport.String(),
)
})

assert.Equal(
t,
"Coverage: 100.0% of statements",
coverageReport.String(),
)
}

0 comments on commit 38640c0

Please sign in to comment.