Skip to content

Refactoring records + primary constructor instrumentation #1761

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

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
21 changes: 2 additions & 19 deletions src/coverlet.core/Symbols/CecilSymbolHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,10 +1364,9 @@ private bool SkipExpressionBreakpointsSequences(MethodDefinition methodDefinitio

public bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction)
{
if (!skipAutoProps || !methodDefinition.IsConstructor) return false;
if (!skipAutoProps) return false;

return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction) ||
SkipDefaultInitializationSystemObject(instruction);
return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction);
}

private static bool SkipGeneratedBackingFieldAssignment(MethodDefinition methodDefinition, Instruction instruction)
Expand Down Expand Up @@ -1401,22 +1400,6 @@ instance void .ctor () cil managed
autogeneratedBackingFields.Select(x => x.FullName).Contains(fr.FullName);
}

private static bool SkipDefaultInitializationSystemObject(Instruction instruction)
{
/*
A type always has a constructor with a default instantiation of System.Object. For record types these
instructions can have a own sequence point. This means that even the default constructor would be instrumented.
To skip this we search for call instructions with a method reference that declares System.Object.

IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
*/
return instruction.OpCode == OpCodes.Ldarg &&
instruction.Next?.OpCode == OpCodes.Call &&
instruction.Next?.Operand is MethodReference mr && mr.DeclaringType.FullName.Equals(typeof(System.Object).FullName);
}

private static bool SkipBranchGeneratedExceptionFilter(Instruction branchInstruction, MethodDefinition methodDefinition)
{
if (!methodDefinition.Body.HasExceptionHandlers)
Expand Down
138 changes: 91 additions & 47 deletions test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ public partial class CoverageTests
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipAutoProps(bool skipAutoProps)
public void SkipClassWithAutoProps(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<AutoProps>(instance =>
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ClassWithAutoProps>(instance =>
{
instance.AutoPropsNonInit = 10;
instance.AutoPropsInit = 20;
int readValue = instance.AutoPropsNonInit;
readValue = instance.AutoPropsInit;
readValue = instance.AutoPropsInitKeyword;
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));
Expand All @@ -40,8 +41,8 @@ public void SkipAutoProps(bool skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13)
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 14)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 14)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 9, 11)
.AssertLinesCovered(BuildConfiguration.Debug, (7, 1))
.AssertLinesCovered(BuildConfiguration.Release, (10, 1));
Expand All @@ -50,9 +51,9 @@ public void SkipAutoProps(bool skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 14)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13);
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 14);
}
}
finally
Expand All @@ -64,42 +65,39 @@ public void SkipAutoProps(bool skipAutoProps)
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipAutoPropsInRecords(bool skipAutoProps)
public void SkipClassWithAutoPropsPrimaryConstructor(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<RecordWithPropertyInit>(instance =>
{
instance.RecordAutoPropsNonInit = string.Empty;
instance.RecordAutoPropsInit = string.Empty;
string readValue = instance.RecordAutoPropsInit;
readValue = instance.RecordAutoPropsNonInit;
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ClassWithAutoPropsPrimaryConstructor>(instance =>
{
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));

return 0;
}, [path, skipAutoProps.ToString()]);

if (skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 23, 24)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 23, 24)
.AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1))
.AssertLinesCovered(BuildConfiguration.Release, (21, 1));
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 28, 28)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 28, 28)
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 30, 31, 32)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 30, 31, 32);
}
else
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 18, 24)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 21, 21)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 23, 24);
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 28, 28)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 30, 32)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 28, 28)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 30, 32);
}
}
finally
Expand All @@ -111,37 +109,44 @@ public void SkipAutoPropsInRecords(bool skipAutoProps)
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipRecordWithProperties(bool skipAutoProps)
public void SkipRecordWithAutoProps(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ClassWithRecordsAutoProperties>(instance =>
{
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<RecordWithAutoProps>(instance =>
{
instance.AutoPropsNonInit = 10;
instance.AutoPropsInit = 20;
int readValue = instance.AutoPropsNonInit;
readValue = instance.AutoPropsInit;
readValue = instance.AutoPropsInitKeyword;
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));

return 0;
}, [path, skipAutoProps.ToString()]);

if (skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 29, 29)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 29, 29)
.AssertLinesCovered(BuildConfiguration.Debug, (32, 1), (33, 1), (34, 1))
.AssertLinesCovered(BuildConfiguration.Release, (33, 1));
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 43, 45)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 43, 45)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 40, 42)
.AssertLinesCovered(BuildConfiguration.Debug, (39, 1))
.AssertLinesCovered(BuildConfiguration.Release, (39, 1));
}
else
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCovered(BuildConfiguration.Debug, (29, 1), (31, 1), (32, 1), (33, 1), (34, 1))
.AssertLinesCovered(BuildConfiguration.Release, (29, 1), (31, 1), (33, 1));
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 38, 45) // go on here
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 39, 39)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 41, 45);
}
}
finally
Expand All @@ -153,14 +158,14 @@ public void SkipRecordWithProperties(bool skipAutoProps)
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipInheritingRecordsWithProperties(bool skipAutoProps)
public void SkipRecordWithAutoPropsPrimaryConstructor(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ClassWithInheritingRecordsAndAutoProperties>(instance =>
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<RecordsWithPrimaryConstructor>(instance =>
{
return Task.CompletedTask;
},
Expand All @@ -173,18 +178,57 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39)
.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39)
.AssertLinesCovered(BuildConfiguration.Debug, (41, 1), (44, 1), (45, 1), (46, 1))
.AssertLinesCovered(BuildConfiguration.Release, (45, 1));
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 50, 50)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 36, 36);
}
else
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 50, 50)
.AssertLinesCoveredFromTo(BuildConfiguration.Release, 36, 36);
}
}
finally
{
File.Delete(path);
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipRecordWithAutoPropsPrimaryConstructorMultiline(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<RecordsWithPrimaryConstructor>(instance =>
{
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));

return 0;
}, [path, skipAutoProps.ToString()]);

if (skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 52, 55)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 52, 55);
}
else
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCovered(BuildConfiguration.Debug, (39, 1), (41, 1), (44, 1), (45, 1), (46, 1))
.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1));
.AssertLinesCovered(BuildConfiguration.Debug, 52, 55)
.AssertLinesNotCovered(BuildConfiguration.Debug, 53, 54)
.AssertLinesCovered(BuildConfiguration.Release, 52, 55)
.AssertLinesNotCovered(BuildConfiguration.Release, 53, 54);
}
}
finally
Expand Down
Loading
Loading