-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial coverage support to neotest #3462
Conversation
Showcase (using https://github.com/nspcc-dev/neofs-contract) coverage3.mp4It seems like some coverage data is lost when running all package tests at once, need more testing... |
Forgot to add reporting if script was already compiled. It does fix problem in the test file on video, but for some reason in other files almost all coverage data is lost. |
Fixed the issue. Also the question is, where do we need add the coverage instrumentation? Currently, its only in |
d3472d3
to
77173f5
Compare
rebased branch on master |
It seems to work, but I found out that when running this test, coverage profile for ListContainerSizes will be empty, even though it was clearly run inside the test. Need to write unit tests for this feature somehow, to make sure it works as expected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, consider reformatting commit structure according to the https://github.com/nspcc-dev/.github/blob/master/git.md#logical-separation.
A really nice feature.
That would be nice. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3462 +/- ##
==========================================
- Coverage 86.05% 85.86% -0.20%
==========================================
Files 330 331 +1
Lines 38692 38806 +114
==========================================
+ Hits 33297 33319 +22
- Misses 3849 3934 +85
- Partials 1546 1553 +7 ☔ View full report in Codecov by Sentry. |
Before I fix anything, we need to agree on some details:
|
As I said in review, to me the first option is more preferable because it's explicit, i.e. allows users of neotest to pick those tests that will affect coverage. I think that sometimes there might be a situation when you need to exclude some test from the coverage if this test is designated for some other purpose. For example, our TestCreateBasicChain should not affect the coverage of our test contracts. Also, with this approach an explicit context is passed to the user of neotest so that it's clear that coverage is being collected (or not) for this test. And finally, implicit coverage collection bothers compilation step, so this step is actually not a compilation step anymore. But I'd like to hear @fyfyrchik and @roman-khimov opinion on this topic.
To me the second approach also looks better because cleanups are native for Go, it's easy-to-go way, and I don't feel negative about the unsetting flag "hack".
I think we'll be able to modify coverage module accordingly if something is changed. And for now this approach works fine. |
Some explicit controls are needed, but I'd default them to "enabled". Most of the time we need it when doing things with
Cleanups are OK. |
In the case we do this by adding optional argument (via struct reference) to neotest's |
I've looked at the code around once more and what really bugs me is that all things are currently package-level (which was noted previously by @fyfyrchik and @AnnaShaleva), so to have some control over this functionality we'll inevitably make some package-level
An alternative could be tying coverage hooks to |
Agree with the proposed plan, especially with the second point, since it's good to have an explicit coverage enabling/disabling functionality. |
just needed to add coverage hook into |
|
Also, these changes improved performance, because coverage is reported only once per contract deployed (before, it was per Invoke call). So I think using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Slava0135, the current implementation is very close to the desired one. Also please, consider reformatting/reordering commits according to https://github.com/nspcc-dev/.github/blob/master/git.md#commits.
Done? You want to store executed Ops per executor (and then merge them somehow?)
It's done in the current implementation. I think we don't need per-executor Ops, we're fine with per-executor collectCoverage
variable. @roman-khimov, right?
There is problem, AddSystemFee uses TestInvoke but doesn't provide executor
Just provide it to AddSystemFee
, nothing prevents you from improving this API (but please, make it in a separate commit since it's a separate logical change):
@@ -298,12 +298,12 @@ func NewDeployTxBy(t testing.TB, bc *core.Blockchain, signer Signer, c *Contract
// AddSystemFee adds system fee to the transaction. If negative value specified,
// then system fee is defined by test invocation.
-func AddSystemFee(bc *core.Blockchain, tx *transaction.Transaction, sysFee int64) {
+func AddSystemFee(e *Executor, tx *transaction.Transaction, sysFee int64) {
if sysFee >= 0 {
tx.SystemFee = sysFee
return
}
- v, _ := TestInvoke(bc, tx) // ignore error to support failing transactions
+ v, _ := TestInvoke(e, tx) // ignore error to support failing transactions
tx.SystemFee = v.GasConsumed()
}
@@ -395,7 +395,8 @@ func (e *Executor) AddBlockCheckHalt(t testing.TB, txs ...*transaction.Transacti
}
// TestInvoke creates a test VM with a dummy block and executes a transaction in it.
-func TestInvoke(bc *core.Blockchain, tx *transaction.Transaction) (*vm.VM, error) {
+func TestInvoke(e *Executor, tx *transaction.Transaction) (*vm.VM, error) {
+ bc := e.Chain
lastBlock, err := bc.GetBlock(bc.GetHeaderHash(bc.BlockHeight()))
if err != nil {
return nil, err
@@ -412,8 +413,8 @@ func TestInvoke(bc *core.Blockchain, tx *transaction.Transaction) (*vm.VM, error
ttx := *tx
ic, _ := bc.GetTestVM(trigger.Application, &ttx, b)
- if isCoverageEnabled() {
- ic.VM.SetOnExecHook(coverageHook())
+ if e.collectCoverage {
+ ic.VM.SetOnExecHook(coverageHook)
}
defer ic.Finalize()
Some thoughts on parallel execution: We need mutex for:
But also we need mutex for |
So we are making it breaking change? |
For some reason collecting coverage doesn't work for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this will (probably) make execution slower
Only if coverage enabled.
leave coverageHook a function that accepts Executor in which OPs are collected and stored, then merged in reportCoverage.
This approach binds OPs to executor, as a result we're going to have per-Executor rawCoverage
field. It's similar to the approach that was discussed earlier, but the problem is that we still can't remove global rawCoverage
variable because we need to merge the results of all tests and store them somewhere. I consider we still have to introduce mutex for this global to hold it during processCover
, but as an optimisation of coverageHook
the proposed option can be implemented.
So we are making it breaking change?
Yes, neotest is developing and I don't see anything critical in this. The migration is rather trivial for the users.
For some reason collecting coverage doesn't work for _deploy even if I add hook into AddNetworkFee which is called by NewDeployTxBy.
It happens because NewDeployTxBy
does not calculate system fee for the deployment transaction, and hence, no test invocation is being performed for deploying script. NewDeployTxBy
uses a fixed system fee for deploy transaction:
Line 276 in ffcbe6a
tx := transaction.New(script, 100*native.GASFactor) |
I think we should change this behaviour and evaluate system fee by test invocation, exactly like it's done for ordinary transactions. Note, you should use AddSystemFee, not AddNetworkFee.
These methods need Executor's context to properly process coverage, thus these methods are not independent anymore. Ref. #3462 (review). Signed-off-by: Anna Shaleva <[email protected]>
Should be fixed. (for some reason |
f4185ed
to
b13a8b7
Compare
It seems like there are issues with CI coverage, because neotest replaces original coverage data. |
Yep, and we definitely need some way to store the original coverage reports.
Let's do this. I firstly thought about storing coverage results in a separate file, but overwriting is not the only problem, because we need to actually gather normal coverage for these packages, and it's turned off while gathering neotest coverage. |
The problem with flag is that we can't directly integrate it into |
b13a8b7
to
5a1dd04
Compare
Naming gets really bad with coverage package, we now have 3 different flags that enable/disable coverage |
I added env variable, it works locally when running tests - i think it should work in workflow too. |
Are we good with "inverse" env variables? Right now if you set |
The global
I'm good with inverse meaning, because usually smart-contracts are a separate go package (or even a separate project) that contains nothing than contracts and thus, supposed to be tested not in a standard Go way. But may be @roman-khimov or @fyfyrchik have different opinion. For now I'd suggest to keep it inverted.
It's not quite correct, the implementation should be adjusted, take a look at the #3462 (comment). |
5a1dd04
to
3e31b11
Compare
ok, i disabled coverage in |
All contracts in NeoGo serve testing needs, I doubt we need coverage for them. It may be enabled for example contracts, but only What really deserves a separate coverage target is neofs-contracts repo. Please, take a look at nspcc-dev/neofs-contract#429. |
@Slava0135, also ref. #3558 and #3559 for further work, if you'd like to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise LGTM. May be checked with the help of nspcc-dev/neofs-contract#429.
@Slava0135, could you please update the Makefile (#3462 (comment))? It's the only conversation left from my side, and after that we can merge if there are no more comments from @roman-khimov or @fyfyrchik. |
Test coverage is automatically enabled when go test is running with coverage enabled. It can be disabled for any Executor by using relevant methods. Coverage is gathered by capturing VM OPs during test contract execution and mapping them to the contract source code using the DebugInfo information. Signed-off-by: Slava0135 <[email protected]>
3e31b11
to
d0c4547
Compare
done |
Oh my god, I am so happy it is finally in the main repo and can be used. |
Requires #3460Problem
Neotest doesn't provide coverage collection support when running contract tests
Solution
Executed opcodes collected by using
OnExecHook
for each script.Then, using
DebugSeqPoints
, coverage for source file is evaluated and saved in file after each test.Current implementation has some quirks with it:
coverProfile
flag is reset, so go tooling doesn't overwrite coverage file. This can be a problem when running both contract and non-contract tests in the same project.set
mode is supported.