From 94d2f8188c15c4f9b4993d7ed146acf450637e40 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 13 Jul 2023 11:38:26 +0300 Subject: [PATCH] Add flag for skipping code coverage inspection on scripts and transactions --- flowkit/tests/resources.go | 18 +++++- internal/test/test.go | 25 ++++++-- internal/test/test_test.go | 126 ++++++++++++++++++++++++++++++++++--- 3 files changed, 153 insertions(+), 16 deletions(-) diff --git a/flowkit/tests/resources.go b/flowkit/tests/resources.go index 2a455f3c2..9ec9d1bdb 100644 --- a/flowkit/tests/resources.go +++ b/flowkit/tests/resources.go @@ -406,6 +406,7 @@ var TestScriptWithFileRead = Resource{ var TestScriptWithCoverage = Resource{ Filename: "testScriptWithCoverage.cdc", Source: []byte(` + import Test import "FooContract" pub let foo = FooContract() @@ -429,7 +430,7 @@ var TestScriptWithCoverage = Resource{ let result = foo.getIntegerTrait(input) // Assert - assert(result == testInputs[input]) + Test.assert(result == testInputs[input]) } } @@ -438,7 +439,20 @@ var TestScriptWithCoverage = Resource{ foo.addSpecialNumber(78557, "Sierpinski") // Assert - assert("Sierpinski" == foo.getIntegerTrait(78557)) + Test.assert("Sierpinski" == foo.getIntegerTrait(78557)) + } + + pub fun testExecuteScript() { + // Arrange + let blockchain = Test.newEmulatorBlockchain() + + // Act + let code = "pub fun main(): Int { return 42 }" + let result = blockchain.executeScript(code, []) + let answer = (result.returnValue as! Int?)! + + // Assert + Test.assert(answer == 42) } `), } diff --git a/internal/test/test.go b/internal/test/test.go index a2766e021..12772902b 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -42,9 +42,14 @@ import ( // are considered to be helper/utility scripts for test files. const helperScriptSubstr = "_helper" +// When the value of flagsTests.CoverCode equals "contracts", +// scripts and transactions are excluded from coverage report. +const contractsCoverCode = "contracts" + type flagsTests struct { Cover bool `default:"false" flag:"cover" info:"Use the cover flag to calculate coverage report"` - CoverProfile string `default:"coverage.json" flag:"coverprofile" info:"Filename to write the calculated coverage report"` + CoverProfile string `default:"coverage.json" flag:"coverprofile" info:"Filename to write the calculated coverage report. Supported extensions are .json and .lcov"` + CoverCode string `default:"all" flag:"covercode" info:"Use the covercode flag to calculate coverage report only for certain types of code. Available values are \"all\" & \"contracts\""` } var testFlags = flagsTests{} @@ -86,7 +91,7 @@ func run( testFiles[filename] = code } - res, coverageReport, err := testCode(testFiles, state, testFlags.Cover) + res, coverageReport, err := testCode(testFiles, state, testFlags) if err != nil { return nil, err } @@ -123,12 +128,24 @@ func run( func testCode( testFiles map[string][]byte, state *flowkit.State, - coverageEnabled bool, + flags flagsTests, ) (map[string]cdcTests.Results, *runtime.CoverageReport, error) { var coverageReport *runtime.CoverageReport runner := cdcTests.NewTestRunner() - if coverageEnabled { + if flags.Cover { coverageReport = runtime.NewCoverageReport() + if flags.CoverCode == contractsCoverCode { + coverageReport.WithLocationFilter( + func(location common.Location) bool { + _, addressLoc := location.(common.AddressLocation) + _, stringLoc := location.(common.StringLocation) + // We only allow inspection of AddressLocation or StringLocation, + // since scripts and transactions cannot be attributed to their + // source files anyway. + return addressLoc || stringLoc + }, + ) + } runner = runner.WithCoverageReport(coverageReport) } diff --git a/internal/test/test_test.go b/internal/test/test_test.go index 782b12a5c..74b40c3ba 100644 --- a/internal/test/test_test.go +++ b/internal/test/test_test.go @@ -41,7 +41,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -56,7 +56,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -81,7 +81,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -121,7 +121,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -137,7 +137,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -161,7 +161,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - _, _, err := testCode(testFiles, state, false) + _, _, err := testCode(testFiles, state, flagsTests{}) require.Error(t, err) assert.Error( @@ -186,7 +186,7 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, _, err := testCode(testFiles, state, false) + results, _, err := testCode(testFiles, state, flagsTests{}) require.NoError(t, err) require.Len(t, results, 1) @@ -209,11 +209,96 @@ func TestExecutingTests(t *testing.T) { testFiles := map[string][]byte{ script.Filename: script.Source, } - results, coverageReport, err := testCode(testFiles, state, true) + flags := flagsTests{ + Cover: true, + } + results, coverageReport, err := testCode(testFiles, state, flags) require.NoError(t, err) - require.Len(t, results, 1) - assert.NoError(t, results[script.Filename][0].Error) + require.Len(t, results[script.Filename], 3) + for _, result := range results[script.Filename] { + assert.NoError(t, result.Error) + } + + location := common.StringLocation("FooContract") + coverage := coverageReport.Coverage[location] + + assert.Equal(t, []int{}, coverage.MissedLines()) + assert.Equal(t, 15, coverage.Statements) + assert.Equal(t, "100.0%", coverage.Percentage()) + assert.EqualValues( + t, + map[int]int{ + 6: 1, 14: 1, 18: 10, 19: 1, 20: 9, 21: 1, 22: 8, 23: 1, + 24: 7, 25: 1, 26: 6, 27: 1, 30: 5, 31: 4, 34: 1, + }, + coverage.LineHits, + ) + + assert.True(t, coverageReport.TotalLocations() > 1) + assert.ElementsMatch( + t, + []string{ + "s.7465737400000000000000000000000000000000000000000000000000000000", + "I.Crypto", + "I.Test", + "A.0ae53cb6e3f42a79.FlowToken", + "A.f8d6e0586b0a20c7.FlowStorageFees", + "A.f8d6e0586b0a20c7.FlowDKG", + "A.f8d6e0586b0a20c7.ExampleNFT", + "A.f8d6e0586b0a20c7.NonFungibleToken", + "A.f8d6e0586b0a20c7.FlowIDTableStaking", + "A.f8d6e0586b0a20c7.FlowClusterQC", + "A.f8d6e0586b0a20c7.NodeVersionBeacon", + "A.f8d6e0586b0a20c7.StakingProxy", + "A.f8d6e0586b0a20c7.FUSD", + "A.e5a8b7f23e8b548f.FlowFees", + "A.ee82856bf20e2aa6.FungibleToken", + "A.f8d6e0586b0a20c7.FlowStakingCollection", + "A.f8d6e0586b0a20c7.MetadataViews", + "A.f8d6e0586b0a20c7.ViewResolver", + "A.f8d6e0586b0a20c7.NFTStorefrontV2", + "A.f8d6e0586b0a20c7.NFTStorefront", + "A.f8d6e0586b0a20c7.LockedTokens", + "A.f8d6e0586b0a20c7.FlowServiceAccount", + "A.f8d6e0586b0a20c7.FlowEpoch", + }, + coverageReport.ExcludedLocationIDs(), + ) + assert.Equal( + t, + "Coverage: 97.2% of statements", + coverageReport.String(), + ) + }) + + t.Run("with code coverage for contracts only", func(t *testing.T) { + t.Parallel() + + // Setup + _, state, _ := util.TestMocks(t) + + state.Contracts().AddOrUpdate(config.Contract{ + Name: tests.ContractFooCoverage.Name, + Location: tests.ContractFooCoverage.Filename, + }) + + // Execute script + script := tests.TestScriptWithCoverage + testFiles := map[string][]byte{ + script.Filename: script.Source, + } + flags := flagsTests{ + Cover: true, + CoverCode: contractsCoverCode, + } + results, coverageReport, err := testCode(testFiles, state, flags) + + require.NoError(t, err) + require.Len(t, results[script.Filename], 3) + for _, result := range results[script.Filename] { + assert.NoError(t, result.Error) + } location := common.StringLocation("FooContract") coverage := coverageReport.Coverage[location] @@ -230,12 +315,33 @@ func TestExecutingTests(t *testing.T) { coverage.LineHits, ) + assert.Equal(t, 1, coverageReport.TotalLocations()) assert.ElementsMatch( t, []string{ "s.7465737400000000000000000000000000000000000000000000000000000000", "I.Crypto", "I.Test", + "A.0ae53cb6e3f42a79.FlowToken", + "A.f8d6e0586b0a20c7.FlowStorageFees", + "A.f8d6e0586b0a20c7.FlowDKG", + "A.f8d6e0586b0a20c7.ExampleNFT", + "A.f8d6e0586b0a20c7.NonFungibleToken", + "A.f8d6e0586b0a20c7.FlowIDTableStaking", + "A.f8d6e0586b0a20c7.FlowClusterQC", + "A.f8d6e0586b0a20c7.NodeVersionBeacon", + "A.f8d6e0586b0a20c7.StakingProxy", + "A.f8d6e0586b0a20c7.FUSD", + "A.e5a8b7f23e8b548f.FlowFees", + "A.ee82856bf20e2aa6.FungibleToken", + "A.f8d6e0586b0a20c7.FlowStakingCollection", + "A.f8d6e0586b0a20c7.MetadataViews", + "A.f8d6e0586b0a20c7.ViewResolver", + "A.f8d6e0586b0a20c7.NFTStorefrontV2", + "A.f8d6e0586b0a20c7.NFTStorefront", + "A.f8d6e0586b0a20c7.LockedTokens", + "A.f8d6e0586b0a20c7.FlowServiceAccount", + "A.f8d6e0586b0a20c7.FlowEpoch", }, coverageReport.ExcludedLocationIDs(), )