mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2026-03-01 04:28:51 +00:00
Neotest coverage blocks are overlapping sometimes #1355
Labels
No labels
I1
I2
I3
I4
S1
S2
S3
S4
U0
U1
U2
U3
U3
U4
blocked
bug
bug
cli
compiler
config
config
consensus
dependencies
discussion
documentation
enhancement
epic
feature
go
good first issue
help wanted
neotest
network
oracle
performance
question
rpc
security
smartcontract
task
task
task
test
vm
wallet
windows
windows
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
nspcc-dev/neo-go#1355
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @AnnaShaleva on GitHub (Aug 16, 2024).
Current Behavior
The original issue is posted in https://github.com/nspcc-dev/neo-go/pull/3462#issuecomment-2291269916
Expected Behavior
Block coverage matches the expectations
@Slava0135 commented on GitHub (Sep 6, 2024):
I looked at this closer and it seems like SeqPoints can overlap sometimes (usually one can cover entire function), is this intended?
@AnnaShaleva commented on GitHub (Oct 15, 2024):
This problem is more complicated than it seems to be.
Yes, it is intended, because our compiler generates DebugInfo according to NEP-19 standard. And here what the standard tells about debug sequence points:
Hence, given some [non-builtin and non-inlined] function, a function will always have a separate sequence point corresponding to the whole function body (like PutNamed function of Container contract). Moreover, functions bodies are not the only place that may contain overlapping sequence points. For example, if a single line contain multiple wrapped calls for those sequence points are emitted, then several wrapped sequence points will be generated, e.g. lines like this (taken from
nspcc-dev/neofs-contract@6a5813a25d/contracts/container/contract.go (L784)):will have two nested sequence point (which is correct and expected):
So basically, for the DebugInfo itself it's quite native that a sequence point boundaries may include several other sequence points. But the situation is changed when we come to
neotestcoverage. For coverage blocks overlapping is not acceptable. Here what Go blog tells about it:So we definitely need some additional modifications over sequence points in order to emit proper coverage profile. #3575 attempts to fix this issue, but I consider we can't just remove all outer sequence points from the set of nested sequence points, because it will work for the case of function definitions but won't work properly for the case of nested calls like
string(id[:len(estimateKeyPrefix)]) != estimateKeyPrefix {. If we remove an outer point from this statement, then it will be treated as "uncovered" which is not the case since the number of calls to outer instruction equals to the number of calls to inner instruction.Also, another problem is that currently neotest coverage doesn't calculate the number of statements included into coverage block properly. It uses the number of lines instead of the number of instructions which is not the worse solution, but it's still inaccurate:
nspcc-dev/neo-go@a9242535db/pkg/neotest/coverage.go (L163)These two problems are closely connected, so we definitely need some "smart" merging algorithm that will convert the set of sequence points to the proper coverage blocks. It may remove function declaration points, but it should also merge one-line instructions into a single coverage block with proper calculation of the number of statements (probably by squashing all instructions that are located in one line). That's why I can't fully accept the solution presented in #3575.
But at the same time it's not that easy to filter out unnecessary instructions because we don't have any code structure information other than sequence point boundaries displayed in debug info.
@roman-khimov, what do you think wrt this problem?
@Slava0135 commented on GitHub (Oct 15, 2024):
I guess we would have to actually use AST and maybe keep extra info from codegen
@AnnaShaleva commented on GitHub (Oct 15, 2024):
I thought about it, but ideally we need to generate coverage using only debug info specification, and debug info itself is strictly fixed by the standard.
@roman-khimov commented on GitHub (Oct 15, 2024):
I think we can improve without changing NEP-19. The format itself is rather rich. We can build a map of instruction indexes to a list of sequence points. We can build a map of code lines to a list of sequence points and/or to a list of instruction indexes. We know function boundaries. But we need more of smaller examples to see what behavior is more appropriate for what case. At least I'd try that first.
@AnnaShaleva commented on GitHub (Oct 16, 2024):
The problem is that right now in general case we don't. For package-level function declarations we can exclude some instructions from coverage calculations based on the
rangeproperty of methods specified in DebugInfo (we should try it, BTW). However, for anonymous function declarations we don't have such information as a function boundaries. Hence, for function like this there will be generated a sequence point that covers the whole function:@roman-khimov commented on GitHub (Oct 16, 2024):
Shouldn't it be a separate https://github.com/neo-project/proposals/blob/master/nep-19.mediawiki#user-content-Method? Dynamic name, of course, but from https://github.com/neo-project/proposals/blob/master/nep-19.mediawiki#methods:
@roman-khimov commented on GitHub (Oct 16, 2024):
And these are more rare anyway, while regular functions can be improved already.
@AnnaShaleva commented on GitHub (Oct 16, 2024):
It's a contravercial question, because it's not a fully-qualified private or public method of the contract; it's an anonymous function. And our debug info does not include anonymous functions to the methods list. Is it a bug? I'd say no, but you may argue.
@roman-khimov commented on GitHub (Oct 16, 2024):
I will. It's some code block for sure and it can be moved into a section of its own. Also IIRC we're not capturing state properly right now for these. You've probably seen some
func_name.1.1(or something like that) in panics and Go profiler, that's exactly that --- these code blocks are identified as separate functions.@AnnaShaleva commented on GitHub (Oct 17, 2024):
A good example, ref. https://github.com/nspcc-dev/neo-go/issues/3619.
@AnnaShaleva commented on GitHub (Oct 17, 2024):
RET-related problem will be fixed by https://github.com/nspcc-dev/neo-go/issues/3622. The remaining problems with smaller overlapping blocks should be fixed on the coverage side, but this problem is not that critical and may be solved by coverage blocks merge.All in all, the remaining tasks are:
string(id[:len(estimateKeyPrefix)]) != estimateKeyPrefix {), may be we can port their solution.