forked from neo-project/neo-devpack-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TestEngine: Get instruction coverage and move to net standard (neo-pr…
…oject#898) * Get execution coverage * Improve coverage comments * Get coverage * GetCoverage extension * format * Net standard and CoveredPercentage * AbiMethod * clean * Allow coverage on Read&Write properties * format * Update README.md * Allow to sum coverages * clean * Allow to sum coverage contracts * Allow to join coverage from multiple sources * change to public and ready to review * Format and load methods in contract * LF * Rename to CoverageHit * Enable or disable recover coverage * Fix hit
- Loading branch information
Showing
17 changed files
with
1,087 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.Diagnostics; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
namespace Neo.SmartContract.Testing.Coverage | ||
{ | ||
[DebuggerDisplay("{Name},{PCount}")] | ||
public class AbiMethod : IEquatable<AbiMethod> | ||
{ | ||
/// <summary> | ||
/// Method name | ||
/// </summary> | ||
public string Name { get; } | ||
|
||
/// <summary> | ||
/// Parameters count | ||
/// </summary> | ||
public int PCount { get; } | ||
|
||
/// <summary> | ||
/// Constructor | ||
/// </summary> | ||
/// <param name="name">Method name</param> | ||
/// <param name="pCount">Parameters count</param> | ||
public AbiMethod(string name, int pCount) | ||
{ | ||
Name = name; | ||
PCount = pCount; | ||
} | ||
|
||
/// <summary> | ||
/// Create from expression | ||
/// </summary> | ||
/// <param name="expression">Expression</param> | ||
/// <returns>AbiMethod</returns> | ||
public static AbiMethod[] CreateFromExpression(Expression expression) | ||
{ | ||
if (expression is MemberExpression memberExpression) | ||
{ | ||
if (memberExpression.Member is PropertyInfo pInfo) | ||
{ | ||
if (pInfo.CanRead) | ||
{ | ||
var display = pInfo.GetGetMethod()?.GetCustomAttribute<DisplayNameAttribute>(); | ||
var nameRead = display is not null ? display.DisplayName : memberExpression.Member.Name; | ||
|
||
if (pInfo.CanWrite) | ||
{ | ||
// If Property CanWrite, we return both methods | ||
|
||
display = pInfo.GetSetMethod()?.GetCustomAttribute<DisplayNameAttribute>(); | ||
var nameWrite = display is not null ? display.DisplayName : memberExpression.Member.Name; | ||
|
||
return new AbiMethod[] | ||
{ | ||
new AbiMethod(nameRead, 0), | ||
new AbiMethod(nameWrite, 1) | ||
}; | ||
} | ||
|
||
// Only read property | ||
|
||
return new AbiMethod[] { new AbiMethod(nameRead, 0) }; | ||
} | ||
} | ||
} | ||
else if (expression is MethodCallExpression methodExpression) | ||
{ | ||
if (methodExpression.Method is MethodInfo mInfo) | ||
{ | ||
var display = mInfo.GetCustomAttribute<DisplayNameAttribute>(); | ||
var name = display is not null ? display.DisplayName : mInfo.Name; | ||
|
||
return new AbiMethod[] { new AbiMethod(name, mInfo.GetParameters().Length) }; | ||
} | ||
} | ||
|
||
return Array.Empty<AbiMethod>(); | ||
} | ||
|
||
public override bool Equals(object obj) | ||
{ | ||
if (obj is not AbiMethod other) return false; | ||
|
||
return PCount == other.PCount && Name == other.Name; | ||
} | ||
|
||
bool IEquatable<AbiMethod>.Equals(AbiMethod other) => PCount == other.PCount && Name == other.Name; | ||
public override int GetHashCode() => HashCode.Combine(PCount, Name); | ||
public override string ToString() => $"{Name},{PCount}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Neo.SmartContract.Testing.Coverage | ||
{ | ||
public abstract class CoverageBase : IEnumerable<CoverageHit> | ||
{ | ||
/// <summary> | ||
/// Coverage | ||
/// </summary> | ||
public abstract IEnumerable<CoverageHit> Coverage { get; } | ||
|
||
/// <summary> | ||
/// Total instructions (could be different from Coverage.Count if, for example, a contract JUMPS to PUSHDATA content) | ||
/// </summary> | ||
public virtual int TotalInstructions => Coverage.Where(u => !u.OutOfScript).Count(); | ||
|
||
/// <summary> | ||
/// Covered Instructions (OutOfScript are not taken into account) | ||
/// </summary> | ||
public virtual int CoveredInstructions => Coverage.Where(u => !u.OutOfScript && u.Hits > 0).Count(); | ||
|
||
/// <summary> | ||
/// All instructions that have been touched | ||
/// </summary> | ||
public virtual int HitsInstructions => Coverage.Where(u => u.Hits > 0).Count(); | ||
|
||
/// <summary> | ||
/// Covered Percentage | ||
/// </summary> | ||
public float CoveredPercentage | ||
{ | ||
get | ||
{ | ||
var total = TotalInstructions; | ||
if (total == 0) return 0F; | ||
|
||
return (float)CoveredInstructions / total * 100F; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Get Coverage from the Contract coverage | ||
/// </summary> | ||
/// <param name="offset">Offset</param> | ||
/// <param name="length">Length</param> | ||
/// <returns>Coverage</returns> | ||
public IEnumerable<CoverageHit> GetCoverageFrom(int offset, int length) | ||
{ | ||
var to = offset + length; | ||
|
||
foreach (var kvp in Coverage) | ||
{ | ||
if (kvp.Offset >= offset && kvp.Offset <= to) | ||
{ | ||
yield return kvp; | ||
} | ||
} | ||
} | ||
|
||
#region IEnumerable | ||
|
||
public IEnumerator<CoverageHit> GetEnumerator() => Coverage.GetEnumerator(); | ||
IEnumerator IEnumerable.GetEnumerator() => Coverage.GetEnumerator(); | ||
|
||
#endregion | ||
|
||
// Allow to sum coverages | ||
|
||
public static CoverageBase? operator +(CoverageBase? a, CoverageBase? b) | ||
{ | ||
if (a is null) return b; | ||
if (b is null) return a; | ||
|
||
return new CoveredCollection(a, b); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using System; | ||
using System.Diagnostics; | ||
|
||
namespace Neo.SmartContract.Testing.Coverage | ||
{ | ||
[DebuggerDisplay("Offset:{Offset}, OutOfScript:{OutOfScript}, Hits:{Hits}, GasTotal:{GasTotal}, GasMin:{GasMin}, GasMax:{GasMax}, GasAvg:{GasAvg}")] | ||
public class CoverageHit | ||
{ | ||
/// <summary> | ||
/// The instruction offset | ||
/// </summary> | ||
public int Offset { get; } | ||
|
||
/// <summary> | ||
/// The instruction is out of the script | ||
/// </summary> | ||
public bool OutOfScript { get; } | ||
|
||
/// <summary> | ||
/// Hits | ||
/// </summary> | ||
public int Hits { get; private set; } | ||
|
||
/// <summary> | ||
/// Minimum used gas | ||
/// </summary> | ||
public long GasMin { get; private set; } | ||
|
||
/// <summary> | ||
/// Minimum used gas | ||
/// </summary> | ||
public long GasMax { get; private set; } | ||
|
||
/// <summary> | ||
/// Total used gas | ||
/// </summary> | ||
public long GasTotal { get; private set; } | ||
|
||
/// <summary> | ||
/// Average used gas | ||
/// </summary> | ||
public long GasAvg => Hits == 0 ? 0 : GasTotal / Hits; | ||
|
||
/// <summary> | ||
/// Constructor | ||
/// </summary> | ||
/// <param name="offset">Offset</param> | ||
/// <param name="outOfScript">Out of script</param> | ||
public CoverageHit(int offset, bool outOfScript = false) | ||
{ | ||
Offset = offset; | ||
OutOfScript = outOfScript; | ||
} | ||
|
||
/// <summary> | ||
/// Hits | ||
/// </summary> | ||
/// <param name="gas">Gas</param> | ||
public void Hit(long gas) | ||
{ | ||
Hits++; | ||
|
||
if (Hits == 1) | ||
{ | ||
GasMin = gas; | ||
GasMax = gas; | ||
} | ||
else | ||
{ | ||
GasMin = Math.Min(GasMin, gas); | ||
GasMax = Math.Max(GasMax, gas); | ||
} | ||
|
||
GasTotal += gas; | ||
} | ||
|
||
/// <summary> | ||
/// Hits | ||
/// </summary> | ||
/// <param name="value">Value</param> | ||
public void Hit(CoverageHit value) | ||
{ | ||
if (value.Hits == 0) return; | ||
|
||
Hits += value.Hits; | ||
|
||
if (Hits == 1) | ||
{ | ||
GasMin = value.GasMin; | ||
GasMax = value.GasMax; | ||
} | ||
else | ||
{ | ||
GasMin = Math.Min(GasMin, value.GasMin); | ||
GasMax = Math.Max(GasMax, value.GasMax); | ||
} | ||
|
||
GasTotal += value.GasTotal; | ||
} | ||
|
||
/// <summary> | ||
/// Clone data | ||
/// </summary> | ||
/// <returns>CoverageData</returns> | ||
public CoverageHit Clone() | ||
{ | ||
return new CoverageHit(Offset, OutOfScript) | ||
{ | ||
GasMax = GasMax, | ||
GasMin = GasMin, | ||
GasTotal = GasTotal, | ||
Hits = Hits | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// String representation | ||
/// </summary> | ||
/// <returns></returns> | ||
public override string ToString() | ||
{ | ||
return $"Offset:{Offset}, OutOfScript:{OutOfScript}, Hits:{Hits}, GasTotal:{GasTotal}, GasMin:{GasMin}, GasMax:{GasMax}, GasAvg:{GasAvg}"; | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Neo.SmartContract.Testing.Coverage | ||
{ | ||
public class CoveredCollection : CoverageBase | ||
{ | ||
/// <summary> | ||
/// Entries | ||
/// </summary> | ||
public CoverageBase[] Entries { get; } | ||
|
||
/// <summary> | ||
/// Coverage | ||
/// </summary> | ||
public override IEnumerable<CoverageHit> Coverage | ||
{ | ||
get | ||
{ | ||
foreach (var method in Entries) | ||
{ | ||
foreach (var entry in method.Coverage) | ||
{ | ||
yield return entry; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Constructor | ||
/// </summary> | ||
/// <param name="entries">Entries</param> | ||
public CoveredCollection(params CoverageBase[] entries) | ||
{ | ||
Entries = entries; | ||
} | ||
} | ||
} |
Oops, something went wrong.