Skip to content

Commit adf6ec9

Browse files
committed
neotest: Use temp coverage collection for each Executor
Previously, each `Executor` acquired global lock on each VM instruction. This led to test slowdowns as the number of Executor instances increased. Also, filling out the coverage file was scheduled by every `DeployContractBy()` / `DeployContractCheckFAULT()` call incl. sub-test calls. This introduces two changes to the process: 1. Each `Executor` schedules report once on cleanup of the test this instance is created for. 2. Each `Executor` collects coverage data within itself, then merges collected data into global space on report. For example, this reduced duration of current NeoFS contract tests was from ~12m to ~7s. Closes #3558. Signed-off-by: Leonard Lyubich <[email protected]>
1 parent e249080 commit adf6ec9

File tree

3 files changed

+41
-17
lines changed

3 files changed

+41
-17
lines changed

pkg/neotest/basic.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"math/big"
77
"strings"
8+
"sync"
9+
"sync/atomic"
810
"testing"
911

1012
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
@@ -33,7 +35,11 @@ type Executor struct {
3335
Committee Signer
3436
CommitteeHash util.Uint160
3537
// collectCoverage is true if coverage is being collected when running this executor.
36-
collectCoverage bool
38+
collectCoverage bool
39+
t testing.TB
40+
reportCoverageScheduled atomic.Bool
41+
coverageLock sync.RWMutex
42+
rawCoverage map[util.Uint160]*scriptRawCoverage
3743
}
3844

3945
// NewExecutor creates a new executor instance from the provided blockchain and committee.
@@ -49,6 +55,8 @@ func NewExecutor(t testing.TB, bc *core.Blockchain, validator, committee Signer)
4955
Committee: committee,
5056
CommitteeHash: committee.ScriptHash(),
5157
collectCoverage: isCoverageEnabled(t),
58+
t: t,
59+
rawCoverage: make(map[util.Uint160]*scriptRawCoverage),
5260
}
5361
}
5462

@@ -148,7 +156,7 @@ func (e *Executor) DeployContract(t testing.TB, c *Contract, data any) util.Uint
148156
// data is an optional argument to `_deploy`.
149157
// It returns the hash of the deploy transaction.
150158
func (e *Executor) DeployContractBy(t testing.TB, signer Signer, c *Contract, data any) util.Uint256 {
151-
e.trackCoverage(t, c)
159+
e.trackCoverage(c)
152160
tx := e.NewDeployTxBy(t, signer, c, data)
153161
e.AddNewBlock(t, tx)
154162
e.CheckHalt(t, tx.Hash())
@@ -168,19 +176,21 @@ func (e *Executor) DeployContractBy(t testing.TB, signer Signer, c *Contract, da
168176
// DeployContractCheckFAULT compiles and deploys a contract to the bc using the validator
169177
// account. It checks that the deploy transaction FAULTed with the specified error.
170178
func (e *Executor) DeployContractCheckFAULT(t testing.TB, c *Contract, data any, errMessage string) {
171-
e.trackCoverage(t, c)
179+
e.trackCoverage(c)
172180
tx := e.NewDeployTx(t, c, data)
173181
e.AddNewBlock(t, tx)
174182
e.CheckFault(t, tx.Hash(), errMessage)
175183
}
176184

177185
// trackCoverage switches on coverage tracking for provided script if `go test` is running with coverage enabled.
178-
func (e *Executor) trackCoverage(t testing.TB, c *Contract) {
186+
func (e *Executor) trackCoverage(c *Contract) {
179187
if e.collectCoverage {
180188
addScriptToCoverage(c)
181-
t.Cleanup(func() {
182-
reportCoverage(t)
183-
})
189+
if !e.reportCoverageScheduled.Swap(true) {
190+
e.t.Cleanup(func() {
191+
e.reportCoverage()
192+
})
193+
}
184194
}
185195
}
186196

@@ -417,7 +427,7 @@ func (e *Executor) TestInvoke(tx *transaction.Transaction) (*vm.VM, error) {
417427
ic, _ := e.Chain.GetTestVM(trigger.Application, &ttx, b)
418428

419429
if e.collectCoverage {
420-
ic.VM.SetOnExecHook(coverageHook)
430+
ic.VM.SetOnExecHook(e.coverageHook)
421431
}
422432

423433
defer ic.Finalize()

pkg/neotest/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (c *ContractInvoker) TestInvokeScript(t testing.TB, script []byte, signers
6464
t.Cleanup(ic.Finalize)
6565

6666
if c.collectCoverage {
67-
ic.VM.SetOnExecHook(coverageHook)
67+
ic.VM.SetOnExecHook(c.coverageHook)
6868
}
6969

7070
ic.VM.LoadWithFlags(tx.Script, callflag.All)
@@ -83,7 +83,7 @@ func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...any) (
8383
t.Cleanup(ic.Finalize)
8484

8585
if c.collectCoverage {
86-
ic.VM.SetOnExecHook(coverageHook)
86+
ic.VM.SetOnExecHook(c.coverageHook)
8787
}
8888

8989
ic.VM.LoadWithFlags(tx.Script, callflag.All)

pkg/neotest/coverage.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"github.com/nspcc-dev/neo-go/pkg/compiler"
1515
"github.com/nspcc-dev/neo-go/pkg/util"
16-
"github.com/nspcc-dev/neo-go/pkg/vm"
1716
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
1817
)
1918

@@ -118,20 +117,35 @@ func isCoverageEnabled(t testing.TB) bool {
118117
return coverageEnabled
119118
}
120119

121-
var coverageHook vm.OnExecHook = func(scriptHash util.Uint160, offset int, opcode opcode.Opcode) {
122-
coverageLock.Lock()
123-
defer coverageLock.Unlock()
124-
if cov, ok := rawCoverage[scriptHash]; ok {
120+
func (e *Executor) coverageHook(scriptHash util.Uint160, offset int, _ opcode.Opcode) {
121+
e.coverageLock.Lock()
122+
defer e.coverageLock.Unlock()
123+
if cov, ok := e.rawCoverage[scriptHash]; ok {
125124
cov.offsetsVisited = append(cov.offsetsVisited, offset)
126125
}
127126
}
128127

129-
func reportCoverage(t testing.TB) {
128+
func (e *Executor) reportCoverage() {
130129
coverageLock.Lock()
131130
defer coverageLock.Unlock()
131+
132+
e.coverageLock.RLock()
133+
for h, cov := range e.rawCoverage {
134+
fullCov := rawCoverage[h]
135+
if fullCov == nil {
136+
fullCov = &scriptRawCoverage{
137+
debugInfo: cov.debugInfo,
138+
}
139+
rawCoverage[h] = fullCov
140+
}
141+
142+
fullCov.offsetsVisited = append(fullCov.offsetsVisited, cov.offsetsVisited...)
143+
}
144+
e.coverageLock.RUnlock()
145+
132146
f, err := os.Create(coverProfile)
133147
if err != nil {
134-
t.Fatalf("coverage: can't create file '%s' to write coverage report", coverProfile)
148+
e.t.Fatalf("coverage: can't create file '%s' to write coverage report", coverProfile)
135149
}
136150
defer f.Close()
137151
writeCoverageReport(f)

0 commit comments

Comments
 (0)