diff --git a/examples/nft-nd-nns/go.mod b/examples/nft-nd-nns/go.mod index 03db73d5d1..67fa239304 100644 --- a/examples/nft-nd-nns/go.mod +++ b/examples/nft-nd-nns/go.mod @@ -3,7 +3,7 @@ module github.com/nspcc-dev/neo-go/examples/nft-nd-nns go 1.22 require ( - github.com/nspcc-dev/neo-go v0.106.4-0.20241007161539-0968c3a81fb0 + github.com/nspcc-dev/neo-go v0.106.4-0.20241016130346-d8e945978af6 github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec github.com/stretchr/testify v1.9.0 ) diff --git a/examples/nft-nd-nns/go.sum b/examples/nft-nd-nns/go.sum index afc7ebda52..5f827d3ac8 100644 --- a/examples/nft-nd-nns/go.sum +++ b/examples/nft-nd-nns/go.sum @@ -74,8 +74,8 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20240830112754-291b000d1f3b h1:DRG4c github.com/nspcc-dev/go-ordered-json v0.0.0-20240830112754-291b000d1f3b/go.mod h1:d3cUseu4Asxfo9/QA/w4TtGjM0AbC9ynyab+PfH+Bso= github.com/nspcc-dev/hrw/v2 v2.0.1 h1:CxYUkBeJvNfMEn2lHhrV6FjY8pZPceSxXUtMVq0BUOU= github.com/nspcc-dev/hrw/v2 v2.0.1/go.mod h1:iZAs5hT2q47EGq6AZ0FjaUI6ggntOi7vrY4utfzk5VA= -github.com/nspcc-dev/neo-go v0.106.4-0.20241007161539-0968c3a81fb0 h1:8IYGFnZQ+wyH5Qdtq4oxBEyiWSNMdi6AXSHQunyZ9VU= -github.com/nspcc-dev/neo-go v0.106.4-0.20241007161539-0968c3a81fb0/go.mod h1:ds91T4WJwtk7eWUo0fuVC36HpTQKkkdj5AjNxbjXAR0= +github.com/nspcc-dev/neo-go v0.106.4-0.20241016130346-d8e945978af6 h1:3/V1U2ZgbFZQ5xGjTD9NMsz4NHzPy/nYJzcaHDVDu88= +github.com/nspcc-dev/neo-go v0.106.4-0.20241016130346-d8e945978af6/go.mod h1:ds91T4WJwtk7eWUo0fuVC36HpTQKkkdj5AjNxbjXAR0= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec h1:vDrbVXF2+2uP0RlkZmem3QYATcXCu9BzzGGCNsNcK7Q= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY= github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 h1:arN0Ypn+jawZpu1BND7TGRn44InAVIqKygndsx0y2no= diff --git a/examples/nft-nd-nns/nns_test.go b/examples/nft-nd-nns/nns_test.go index d0d043c7cf..607bc44656 100644 --- a/examples/nft-nd-nns/nns_test.go +++ b/examples/nft-nd-nns/nns_test.go @@ -381,7 +381,7 @@ func TestTransfer(t *testing.T) { func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data any) {}`), &compiler.Options{Name: "foo"}) e.DeployContract(t, ctr, nil) - e.EnableCoverage() // contracts above have no source files which leads to unprocessable coverage data + e.EnableCoverage(t) // contracts above have no source files which leads to unprocessable coverage data cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil) cFrom.Invoke(t, 1, "totalSupply") cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com")) diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index 3a0de03056..3e03b5a682 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -48,7 +48,7 @@ func NewExecutor(t testing.TB, bc *core.Blockchain, validator, committee Signer) Validator: validator, Committee: committee, CommitteeHash: committee.ScriptHash(), - collectCoverage: isCoverageEnabled(), + collectCoverage: isCoverageEnabled(t), } } @@ -452,8 +452,8 @@ func (e *Executor) GetTxExecResult(t testing.TB, h util.Uint256) *state.AppExecR } // EnableCoverage enables coverage collection for this executor, but only when `go test` is running with coverage enabled. -func (e *Executor) EnableCoverage() { - e.collectCoverage = isCoverageEnabled() +func (e *Executor) EnableCoverage(t testing.TB) { + e.collectCoverage = isCoverageEnabled(t) } // DisableCoverage disables coverage collection for this executor until enabled explicitly through EnableCoverage. diff --git a/pkg/neotest/coverage.go b/pkg/neotest/coverage.go index f5bbf2e349..f22580ab30 100644 --- a/pkg/neotest/coverage.go +++ b/pkg/neotest/coverage.go @@ -1,10 +1,12 @@ package neotest import ( + "cmp" "flag" "fmt" "io" "os" + "slices" "strconv" "sync" "testing" @@ -16,13 +18,22 @@ import ( ) const ( - // goCoverProfileFlag specifies the name of `go test` flag that tells it where to save coverage data. - // Neotest coverage can be enabled only when this flag is set. + // goCoverProfileFlag specifies the name of `go test` command flag `coverprofile` + // that tells it where to save coverage data. Neotest coverage can be enabled + // only when this flag is set. goCoverProfileFlag = "test.coverprofile" + // goCoverModeFlag specifies the name of `go test` command flag `covermode` that + // specifies the coverage calculation mode. + goCoverModeFlag = "test.covermode" // disableNeotestCover is name of the environmental variable used to explicitly disable neotest coverage. disableNeotestCover = "DISABLE_NEOTEST_COVER" ) +const ( + // goCoverModeSet is the name of "set" go test coverage mode. + goCoverModeSet = "set" +) + var ( // coverageLock protects all vars below from concurrent modification when tests are run in parallel. coverageLock sync.Mutex @@ -34,6 +45,8 @@ var ( coverageEnabled bool // coverProfile specifies the file all coverage data is written to, unless empty. coverProfile = "" + // coverMode is the mode of go coverage collection. + coverMode = goCoverModeSet ) type scriptRawCoverage struct { @@ -59,7 +72,7 @@ type coverBlock struct { // documentName makes it clear when a `string` maps path to the script file. type documentName = string -func isCoverageEnabled() bool { +func isCoverageEnabled(t testing.TB) bool { coverageLock.Lock() defer coverageLock.Unlock() @@ -72,7 +85,7 @@ func isCoverageEnabled() bool { if v, ok := os.LookupEnv(disableNeotestCover); ok { disabled, err := strconv.ParseBool(v) if err != nil { - panic(fmt.Sprintf("coverage: error when parsing environment variable '%s', expected bool, but got '%s'", disableNeotestCover, v)) + t.Fatalf("coverage: error when parsing environment variable '%s', expected bool, but got '%s'", disableNeotestCover, v) } disabledByEnvironment = disabled } @@ -83,16 +96,22 @@ func isCoverageEnabled() bool { goToolCoverageEnabled = true coverProfile = f.Value.String() } + if f.Name == goCoverModeFlag && f.Value != nil && f.Value.String() != "" { + coverMode = f.Value.String() + } }) coverageEnabled = !disabledByEnvironment && goToolCoverageEnabled if coverageEnabled { + if coverMode != goCoverModeSet { + t.Fatalf("coverage: only '%s' cover mode is currently supported (#3587), got '%s'", goCoverModeSet, coverMode) + } // This is needed so go cover tool doesn't overwrite // the file with our coverage when all tests are done. err := flag.Set(goCoverProfileFlag, "") if err != nil { - panic(err) + t.Fatalf("coverage: failed to overwrite coverprofile flag: %v", err) } } @@ -119,25 +138,25 @@ func reportCoverage(t testing.TB) { } func writeCoverageReport(w io.Writer) { - fmt.Fprintf(w, "mode: set\n") + fmt.Fprintf(w, "mode: %s\n", coverMode) cover := processCover() for name, blocks := range cover { for _, b := range blocks { - c := 0 - if b.counts > 0 { - c = 1 + var counts = b.counts + if coverMode == goCoverModeSet && counts > 0 { + counts = 1 } fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n", name, b.startLine, b.startCol, b.endLine, b.endCol, b.stmts, - c, + counts, ) } } } -func processCover() map[documentName][]coverBlock { +func processCover() map[documentName][]*coverBlock { documents := make(map[documentName]struct{}) for _, scriptRawCoverage := range rawCoverage { for _, documentName := range scriptRawCoverage.debugInfo.Documents { @@ -145,7 +164,7 @@ func processCover() map[documentName][]coverBlock { } } - cover := make(map[documentName][]coverBlock) + cover := make(map[documentName][]*coverBlock) for documentName := range documents { mappedBlocks := make(map[int]*coverBlock) @@ -180,10 +199,19 @@ func processCover() map[documentName][]coverBlock { } } - var blocks []coverBlock + var blocks = make([]*coverBlock, 0, len(mappedBlocks)) for _, b := range mappedBlocks { - blocks = append(blocks, *b) + blocks = append(blocks, b) } + slices.SortFunc(blocks, func(a, b *coverBlock) int { + return cmp.Or( + cmp.Compare(a.startLine, b.startLine), + cmp.Compare(a.endLine, b.endLine), + cmp.Compare(a.startCol, b.startCol), + cmp.Compare(a.endCol, b.endCol), + cmp.Compare(a.stmts, b.stmts), + cmp.Compare(a.counts, b.counts)) + }) cover[documentName] = blocks }