diff --git a/src/Neo.SmartContract.Testing/README.md b/src/Neo.SmartContract.Testing/README.md
index a413ec068..13f9cf371 100644
--- a/src/Neo.SmartContract.Testing/README.md
+++ b/src/Neo.SmartContract.Testing/README.md
@@ -242,7 +242,7 @@ Assert.AreEqual(123, neo.BalanceOf(engine.ValidatorsAddress));
using (ScriptBuilder script = new())
{
- script.EmitDynamicCall(neo.Hash, nameof(neo.BalanceOf), engine.ValidatorsAddress);
+ script.EmitDynamicCall(neo.Hash, "balanceOf", engine.ValidatorsAddress);
Assert.AreEqual(123, engine.Execute(script.ToArray()).GetInteger());
}
@@ -416,7 +416,53 @@ public class CoverageContractTests
}
```
-Keep in mind that the coverage is at the instruction level.
+Keep in mind that the coverage is at the instruction level, but you can also get the project's coverage on the source code using the debug file (`*.nefdbgnfo`) generated by `Neo.Compiler.CSharp`. To do this, you need to compile the project with the `-d` or `--debug` argument, and set up a unit test like the following:
+
+```csharp
+[TestClass]
+public class CoverageContractTests
+{
+ ///
+ /// Required coverage to be success
+ ///
+ public static decimal RequiredCoverage { get; set; } = 1M;
+
+ [AssemblyCleanup]
+ public static void EnsureCoverage()
+ {
+ // Join here all of your coverage sources
+
+ var coverage = Nep17ContractTests.Coverage;
+ coverage?.Join(OwnerContractTests.Coverage);
+
+ // Dump coverage to console
+
+ Assert.IsNotNull(coverage, "Coverage can't be null");
+ Console.WriteLine(coverage.Dump());
+
+ // Write basic instruction html coverage
+
+ File.WriteAllText("coverage.instruction.html", coverage.Dump(DumpFormat.Html));
+
+ // Load our debug file
+
+ if (NeoDebugInfo.TryLoad("templates/neocontractnep17/Artifacts/Nep17Contract.nefdbgnfo", out var dbg))
+ {
+ // Write the cobertura format
+
+ File.WriteAllText("coverage.cobertura.xml", coverage.Dump(new CoberturaFormat((coverage, dbg))));
+
+ // Write the report to the specific path
+
+ CoverageReporting.CreateReport("coverage.cobertura.xml", "./coverageReport/");
+ }
+
+ // Ensure that the coverage is more than X% at the end of the tests
+
+ Assert.IsTrue(coverage.CoveredLinesPercentage >= RequiredCoverage, $"Coverage is less than {RequiredCoverage:P2}");
+ }
+}
+```
### Known limitations
diff --git a/src/Neo.SmartContract.Testing/Storage/EngineCheckpoint.cs b/src/Neo.SmartContract.Testing/Storage/EngineCheckpoint.cs
index 770851d52..15bd2958c 100644
--- a/src/Neo.SmartContract.Testing/Storage/EngineCheckpoint.cs
+++ b/src/Neo.SmartContract.Testing/Storage/EngineCheckpoint.cs
@@ -19,7 +19,7 @@ public class EngineCheckpoint
/// Constructor
///
/// Snapshot
- public EngineCheckpoint(SnapshotCache snapshot)
+ public EngineCheckpoint(DataCache snapshot)
{
var list = new List<(byte[], byte[])>();
@@ -63,7 +63,7 @@ public EngineCheckpoint(Stream stream)
/// Restore
///
/// Snapshot
- public void Restore(SnapshotCache snapshot)
+ public void Restore(DataCache snapshot)
{
// Clean snapshot
diff --git a/src/Neo.SmartContract.Testing/Storage/EngineStorage.cs b/src/Neo.SmartContract.Testing/Storage/EngineStorage.cs
index 06aaaf281..72c3461a8 100644
--- a/src/Neo.SmartContract.Testing/Storage/EngineStorage.cs
+++ b/src/Neo.SmartContract.Testing/Storage/EngineStorage.cs
@@ -22,7 +22,7 @@ public class EngineStorage
///
/// Snapshot
///
- public SnapshotCache Snapshot { get; private set; }
+ public DataCache Snapshot { get; private set; }
///
/// Return true if native contract are initialized
@@ -33,10 +33,17 @@ public class EngineStorage
/// Constructor
///
/// Store
- public EngineStorage(IStore store)
+ public EngineStorage(IStore store) : this(store, new SnapshotCache(store.GetSnapshot())) { }
+
+ ///
+ /// Constructor
+ ///
+ /// Store
+ /// Snapshot cache
+ internal EngineStorage(IStore store, DataCache snapshotCache)
{
Store = store;
- Snapshot = new SnapshotCache(Store.GetSnapshot());
+ Snapshot = snapshotCache;
}
///
@@ -52,7 +59,10 @@ public void Commit()
///
public void Rollback()
{
- Snapshot.Dispose();
+ if (Snapshot is IDisposable sp)
+ {
+ sp.Dispose();
+ }
Snapshot = new SnapshotCache(Store.GetSnapshot());
}
diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs
index 4ffb2ccaf..1ff8647a6 100644
--- a/src/Neo.SmartContract.Testing/TestEngine.cs
+++ b/src/Neo.SmartContract.Testing/TestEngine.cs
@@ -305,22 +305,7 @@ public T Deploy(NefFile nef, ContractManifest manifest, object? data = null,
{
// Deploy
- if (EnableCoverageCapture)
- {
- UInt160 expectedHash = GetDeployHash(nef, manifest);
-
- if (!Coverage.ContainsKey(expectedHash))
- {
- var coveredContract = new CoveredContract(MethodDetection, expectedHash, new ContractState()
- {
- Nef = nef,
- Hash = expectedHash,
- Manifest = manifest
- });
- Coverage[coveredContract.Hash] = coveredContract;
- }
- }
-
+ //UInt160 expectedHash = GetDeployHash(nef, manifest);
var state = Native.ContractManagement.Deploy(nef.ToArray(), Encoding.UTF8.GetBytes(manifest.ToJson().ToString(false)), data);
if (state is null)
diff --git a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
index 5be6dda57..3f84d4933 100644
--- a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
+++ b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
@@ -2,6 +2,7 @@
using Neo.Persistence;
using Neo.SmartContract.Native;
using Neo.SmartContract.Testing.Extensions;
+using Neo.SmartContract.Testing.Storage;
using Neo.VM;
using Neo.VM.Types;
using System;
@@ -146,7 +147,7 @@ private void RecoverCoverage(Instruction instruction)
{
// We need the contract state without pay gas
- var state = NativeContract.ContractManagement.GetContract(Engine.Storage.Snapshot, contractHash);
+ var state = NativeContract.ContractManagement.GetContract(Snapshot, contractHash);
coveredContract = new(Engine.MethodDetection, contractHash, state);
Engine.Coverage[contractHash] = coveredContract;
@@ -208,9 +209,30 @@ protected override void OnSysCall(InteropDescriptor descriptor)
// Invoke
- var hasReturnValue = customMock.Method.ReturnType != typeof(void);
- var returnValue = customMock.Method.Invoke(customMock.Contract, parameters);
- if (hasReturnValue)
+ object? returnValue;
+ EngineStorage backup = Engine.Storage;
+
+ try
+ {
+ // We need to switch the Engine's snapshot in case
+ // that a mock want to query the storage, it's not committed
+
+ Engine.Storage = new EngineStorage(backup.Store, Snapshot);
+
+ // Invoke snapshot
+
+ returnValue = customMock.Method.Invoke(customMock.Contract, parameters);
+ }
+ catch
+ {
+ throw;
+ }
+ finally
+ {
+ Engine.Storage = backup;
+ }
+
+ if (customMock.Method.ReturnType != typeof(void))
Push(Convert(returnValue));
else
Push(StackItem.Null);
diff --git a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
index 710d72f69..50548346d 100644
--- a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
+++ b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
@@ -204,7 +204,7 @@ public virtual void TestTransfer()
UInt160? calledFrom = null;
BigInteger? calledAmount = null;
- byte[]? calledData = null;
+ UInt160? calledData = null;
var mock = Engine.Deploy(NefFile, manifest.ToJson().ToString(), null, m =>
{
@@ -212,14 +212,29 @@ public virtual void TestTransfer()
.Setup(s => s.onNEP17Payment(It.IsAny(), It.IsAny(), It.IsAny