From 96b5d90894dbaec1f3942e7e45659fa3efb0bd8c Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Wed, 29 Jun 2022 17:05:31 +0300 Subject: [PATCH] neotest: POC of test coverage Signed-off-by: Evgeniy Stratonikov --- pkg/neotest/basic.go | 2 +- pkg/neotest/client.go | 5 +++ pkg/neotest/compile.go | 20 ++++++--- pkg/neotest/coverage.go | 95 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 pkg/neotest/coverage.go diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index dd9077887..0a9a62f1f 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -335,7 +335,7 @@ func (e *Executor) AddNewBlock(t testing.TB, txs ...*transaction.Transaction) *b return b } -// GenerateNewBlocks adds the specified number of empty blocks to the chain. +// GenerateNewBlocks adds the specified number of empty Blocks to the chain. func (e *Executor) GenerateNewBlocks(t testing.TB, count int) []*block.Block { blocks := make([]*block.Block, count) for i := 0; i < count; i++ { diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go index 513780d7f..46a6ed1db 100644 --- a/pkg/neotest/client.go +++ b/pkg/neotest/client.go @@ -1,6 +1,7 @@ package neotest import ( + "fmt" "testing" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -48,8 +49,12 @@ func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker { // TestInvoke creates test the VM and invokes the method with the args. func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) { + fmt.Println("TEST INVOKE") tx := c.PrepareInvokeNoSign(t, method, args...) b := c.NewUnsignedBlock(t, tx) + + calculateCoverage(t, c.Chain, tx, b) + ic := c.Chain.GetTestVM(trigger.Application, tx, b) t.Cleanup(ic.Finalize) diff --git a/pkg/neotest/compile.go b/pkg/neotest/compile.go index fee77c17e..9abe45b8b 100644 --- a/pkg/neotest/compile.go +++ b/pkg/neotest/compile.go @@ -2,6 +2,7 @@ package neotest import ( "io" + "path/filepath" "testing" "github.com/nspcc-dev/neo-go/cli/smartcontract" @@ -16,9 +17,10 @@ import ( // Contract contains contract info for deployment. type Contract struct { - Hash util.Uint160 - NEF *nef.File - Manifest *manifest.Manifest + Hash util.Uint160 + NEF *nef.File + Manifest *manifest.Manifest + DebugInfo *compiler.DebugInfo } // contracts caches the compiled contracts from FS across multiple tests. @@ -44,6 +46,11 @@ func CompileSource(t testing.TB, sender util.Uint160, src io.Reader, opts *compi // CompileFile compiles a contract from the file and returns its NEF, manifest and hash. func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath string) *Contract { + absPath, err := filepath.Abs(srcPath) + if err == nil { + srcPath = absPath + } + if c, ok := contracts[srcPath]; ok { return c } @@ -72,9 +79,10 @@ func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath s require.NoError(t, err) c := &Contract{ - Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), - NEF: ne, - Manifest: m, + Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), + NEF: ne, + Manifest: m, + DebugInfo: di, } contracts[srcPath] = c return c diff --git a/pkg/neotest/coverage.go b/pkg/neotest/coverage.go new file mode 100644 index 000000000..86c45906d --- /dev/null +++ b/pkg/neotest/coverage.go @@ -0,0 +1,95 @@ +package neotest + +import ( + "fmt" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +var ( + Blocks map[string][]testing.CoverBlock + Counters map[string][]uint32 +) + +func init() { + var cover testing.Cover + cover.Mode = testing.CoverMode() + Blocks = make(map[string][]testing.CoverBlock) + Counters = make(map[string][]uint32) + cover.Blocks = Blocks + cover.Counters = Counters + testing.RegisterCover(cover) +} + +func calculateCoverage(t testing.TB, bc blockchainer.Blockchainer, tx *transaction.Transaction, b *block.Block) { + fmt.Println("CALCULATE COVERAGE") + ic := bc.GetTestVM(trigger.Application, tx, b) + t.Cleanup(ic.Finalize) + + ic.VM.LoadWithFlags(tx.Script, callflag.All) + + hm := make(map[util.Uint160][]int) + +runLoop: + for { + switch { + case ic.VM.HasStopped(): + break runLoop + default: + h := ic.VM.GetCurrentScriptHash() + if err := ic.VM.Step(); err != nil { + break runLoop + } + if ic.VM.Context() != nil { + hm[h] = append(hm[h], ic.VM.Context().IP()) + } + } + } + + // Calculate coverage. +coverageLoop: + for h := range hm { + fmt.Println("TRY HASH", h) + for name, c := range contracts { + fmt.Println("TRY CONTRACT", name, c.Hash) + if !c.Hash.Equals(h) { + continue + } + if c.DebugInfo == nil { + continue coverageLoop + } + + var coverBlocks []testing.CoverBlock + var coverCounts []uint32 + + ipLoop: + for _, ip := range hm[h] { + for _, m := range c.DebugInfo.Methods { + for _, sp := range m.SeqPoints { + if sp.Opcode == ip { + fmt.Println("FOUND COVER BLOCK") + coverCounts = append(coverCounts, 1) + coverBlocks = append(coverBlocks, testing.CoverBlock{ + Line0: uint32(sp.StartLine), + Col0: uint16(sp.StartCol), + Line1: uint32(sp.EndLine), + Col1: uint16(sp.EndCol), + Stmts: 1, + }) + continue ipLoop + } + } + } + } + Blocks[name] = coverBlocks + Counters[name] = coverCounts + continue coverageLoop + } + } +}