From c810f32aad9789b4b9631f13cdefbbc4f5c9764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Mon, 21 Apr 2025 21:20:09 +0200 Subject: [PATCH 01/16] init --- src/coverlet.core/Instrumentation/Instrumenter.cs | 4 ++-- .../Coverage/CoverageTests.AutoProps.cs | 4 +++- .../Coverage/InstrumenterHelper.Assertions.cs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 2878ff432..9fbd1150d 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -592,8 +592,8 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, - currentInstruction) || IsInsideExcludedMethodSection(sequencePoint)) + if (/*_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, + currentInstruction) ||*/ IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs index 7deabe6f0..a26bd2581 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs @@ -69,7 +69,7 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) string path = Path.GetTempFileName(); try { - FunctionExecutor.Run(async (string[] parameters) => + FunctionExecutor.RunInProcess(async (string[] parameters) => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { @@ -87,6 +87,7 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) if (skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show:true) .Document("Instrumentation.AutoProps.cs") .AssertNonInstrumentedLines(BuildConfiguration.Debug, 23, 24) .AssertNonInstrumentedLines(BuildConfiguration.Release, 23, 24) @@ -96,6 +97,7 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) else { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) .Document("Instrumentation.AutoProps.cs") .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 18, 24) .AssertLinesCoveredFromTo(BuildConfiguration.Release, 21, 21) diff --git a/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs b/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs index 582be3993..c7afeb814 100644 --- a/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs +++ b/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs @@ -32,7 +32,7 @@ public static CoverageResult GenerateReport(this CoverageResult coverageResult, { Process.Start("cmd", "/C " + Path.GetFullPath(Path.Combine(directory, "index.htm"))); } - + Process.Start("cmd", "/C " + Path.GetFullPath(Path.Combine(directory, "index.htm"))); return coverageResult; } From 18686a4147622499357132c67a4ec5d549f8abf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Wed, 23 Apr 2025 07:41:10 +0200 Subject: [PATCH 02/16] as is analyzis --- src/coverlet.core/Instrumentation/Instrumenter.cs | 4 ++-- .../Coverage/CoverageTests.AutoProps.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 9fbd1150d..2878ff432 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -592,8 +592,8 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (/*_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, - currentInstruction) ||*/ IsInsideExcludedMethodSection(sequencePoint)) + if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, + currentInstruction) || IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs index a26bd2581..04e298ada 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs @@ -118,7 +118,7 @@ public void SkipRecordWithProperties(bool skipAutoProps) string path = Path.GetTempFileName(); try { - FunctionExecutor.Run(async (string[] parameters) => + FunctionExecutor.RunInProcess(async (string[] parameters) => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { @@ -132,6 +132,7 @@ public void SkipRecordWithProperties(bool skipAutoProps) if (skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) .Document("Instrumentation.AutoProps.cs") .AssertNonInstrumentedLines(BuildConfiguration.Debug, 29, 29) .AssertNonInstrumentedLines(BuildConfiguration.Release, 29, 29) @@ -141,6 +142,7 @@ public void SkipRecordWithProperties(bool skipAutoProps) else { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) .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)); From 8a6ef365d6586c47f2a0ecff3928047021e48aac Mon Sep 17 00:00:00 2001 From: "David Mueller x." Date: Sat, 26 Apr 2025 23:09:08 +0200 Subject: [PATCH 03/16] test --- .../Coverage/CoverageTests.AutoProps.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs index 04e298ada..604405db3 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs @@ -162,7 +162,7 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) string path = Path.GetTempFileName(); try { - FunctionExecutor.Run(async (string[] parameters) => + FunctionExecutor.RunInProcess(async (string[] parameters) => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { @@ -176,6 +176,7 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) if (skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) .Document("Instrumentation.AutoProps.cs") .AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) .AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) @@ -186,6 +187,7 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) else { TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) .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)); From 4bb46b4d0aeaa8953fe3a0d3471941decaebd05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Mon, 28 Apr 2025 10:25:21 +0200 Subject: [PATCH 04/16] added new test file Instrumentation.Records to move all record topics to one test file --- .../Samples/Instrumentation.Records.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs new file mode 100644 index 000000000..e91a13a09 --- /dev/null +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Coverlet.Core.CoverageSamples.Tests +{ + public record RecordWithPropertyInit + { + private int _myRecordVal = 0; + public RecordWithPropertyInit() + { + _myRecordVal = new Random().Next(); + } + public string RecordAutoPropsNonInit { get; set; } + public string RecordAutoPropsInit { get; set; } = string.Empty; + } + + public class ClassWithRecordsAutoProperties + { + record RecordWithPrimaryConstructor(string Prop1, string Prop2); + + public ClassWithRecordsAutoProperties() + { + var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); + } + } + + public class ClassWithInheritingRecordsAndAutoProperties + { + record BaseRecord(int A); + + record InheritedRecord(int A) : BaseRecord(A); + + public ClassWithInheritingRecordsAndAutoProperties() + { + var record = new InheritedRecord(1); + } + } + + public class ClassWithRecordsEmptyPrimaryConstructor + { + internal record First + { + public string Bar() => "baz"; + } + + internal record Second() + { + public string Bar() => "baz"; + } + } + + public class ClassWithAbstractRecords + { + public abstract record AuditData() + { + public abstract string GetAuditType(); + } + + public abstract record AuditData + { + private protected AuditData() + { + + } + + public abstract string GetAuditType(); + } + } +} From cf200cf6d3ae75c6d41ee7c734fb8e0c81806bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Wed, 30 Apr 2025 00:46:41 +0200 Subject: [PATCH 05/16] refactoring --- .../Coverage/CoverageTests.AutoProps.cs | 138 ---------------- .../Coverage/CoverageTests.Records.cs | 154 ++++++++++++++++++ .../Samples/Instrumentation.AutoProps.cs | 64 ++++---- .../Samples/Instrumentation.Records.cs | 10 +- 4 files changed, 189 insertions(+), 177 deletions(-) create mode 100644 test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs index 604405db3..cb2a12f3e 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs @@ -60,143 +60,5 @@ public void SkipAutoProps(bool skipAutoProps) File.Delete(path); } } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipAutoPropsInRecords(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show:true) - .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)); - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.AutoProps.cs") - .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 18, 24) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 21, 21) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 23, 24); - } - } - finally - { - File.Delete(path); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipRecordWithProperties(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .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)); - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .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)); - } - } - finally - { - File.Delete(path); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipInheritingRecordsWithProperties(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .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)); - - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .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)); - } - } - finally - { - File.Delete(path); - } - } } } diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs new file mode 100644 index 000000000..5f1bef860 --- /dev/null +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -0,0 +1,154 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Threading.Tasks; +using Coverlet.Core.CoverageSamples.Tests; +using Coverlet.Core.Tests; +using Coverlet.Core; +using Coverlet.Tests.Utils; +using Xunit; + +namespace Coverlet.CoreCoverage.Tests +{ + public partial class CoverageTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipAutoPropsInRecords(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.RunInProcess(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.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)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.cs") + .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 18, 24) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 21, 21) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 23, 24); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipRecordWithProperties(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.RunInProcess(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.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)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (29, 1), (31, 1), (32, 1), (33, 1), (34, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (29, 1), (31, 1), (33, 1)); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipInheritingRecordsWithProperties(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.RunInProcess(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.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)); + + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (39, 1), (41, 1), (44, 1), (45, 1), (46, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); + } + } + finally + { + File.Delete(path); + } + } + } +} diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs index d649d94a8..3dd3b3da0 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs @@ -13,38 +13,38 @@ public AutoProps() public int AutoPropsInit { get; set; } = 10; } - public record RecordWithPropertyInit - { - private int _myRecordVal = 0; - public RecordWithPropertyInit() - { - _myRecordVal = new Random().Next(); - } - public string RecordAutoPropsNonInit { get; set; } - public string RecordAutoPropsInit { get; set; } = string.Empty; - } - - public class ClassWithRecordsAutoProperties - { - record RecordWithPrimaryConstructor(string Prop1, string Prop2); - - public ClassWithRecordsAutoProperties() - { - var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); - } - } - - public class ClassWithInheritingRecordsAndAutoProperties - { - record BaseRecord(int A); - - record InheritedRecord(int A) : BaseRecord(A); - - public ClassWithInheritingRecordsAndAutoProperties() - { - var record = new InheritedRecord(1); - } - } + //public record RecordWithPropertyInit + //{ + // private int _myRecordVal = 0; + // public RecordWithPropertyInit() + // { + // _myRecordVal = new Random().Next(); + // } + // public string RecordAutoPropsNonInit { get; set; } + // public string RecordAutoPropsInit { get; set; } = string.Empty; + //} + + //public class ClassWithRecordsAutoProperties + //{ + // record RecordWithPrimaryConstructor(string Prop1, string Prop2); + + // public ClassWithRecordsAutoProperties() + // { + // var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); + // } + //} + + //public class ClassWithInheritingRecordsAndAutoProperties + //{ + // record BaseRecord(int A); + + // record InheritedRecord(int A) : BaseRecord(A); + + // public ClassWithInheritingRecordsAndAutoProperties() + // { + // var record = new InheritedRecord(1); + // } + //} } diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs index e91a13a09..ff6f0bee8 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Coverlet.Core.CoverageSamples.Tests { @@ -54,14 +50,14 @@ internal record Second() public class ClassWithAbstractRecords { - public abstract record AuditData() + public abstract record FirstAuditData() { public abstract string GetAuditType(); } - public abstract record AuditData + public abstract record SecondAuditData { - private protected AuditData() + private protected SecondAuditData() { } From 508bd958ee5fcc90d52a389847ca2f6317104e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sat, 3 May 2025 00:58:18 +0200 Subject: [PATCH 06/16] added tests --- .../Coverage/CoverageTests.Records.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 5f1bef860..e95e69ae2 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -150,5 +150,95 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) File.Delete(path); } } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.RunInProcess(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.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)); + + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (39, 1), (41, 1), (44, 1), (45, 1), (46, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.RunInProcess(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.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)); + + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (39, 1), (41, 1), (44, 1), (45, 1), (46, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); + } + } + finally + { + File.Delete(path); + } + } } } From 39eebcdc5041323dbd5cc31aad533fc37bd31850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Mon, 12 May 2025 00:29:11 +0200 Subject: [PATCH 07/16] getting ported test green --- .../Coverage/CoverageTests.Records.cs | 46 +++++++++---------- .../Samples/Instrumentation.Records.cs | 10 +++- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index e95e69ae2..06d3e1676 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -41,19 +41,19 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) + .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); } else { TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.cs") - .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 18, 24) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 21, 21) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 23, 24); + .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); } } finally @@ -86,18 +86,18 @@ public void SkipRecordWithProperties(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) + .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); } else { TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (29, 1), (31, 1), (32, 1), (33, 1), (34, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (29, 1), (31, 1), (33, 1)); + .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); } } finally @@ -130,10 +130,10 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) + .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); } else @@ -141,8 +141,8 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); } } finally @@ -176,8 +176,8 @@ public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) .GenerateReport(show: true) .Document("Instrumentation.Records.cs") .AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) - .AssertLinesCovered(BuildConfiguration.Debug, (41, 1), (44, 1), (45, 1), (46, 1)) + //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) + .AssertLinesCovered(BuildConfiguration.Debug, (42, 1), (47, 1)) .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); } @@ -186,8 +186,8 @@ public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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, (42, 1), (47, 1)); + //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); } } finally diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs index ff6f0bee8..1aa0341c4 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -37,15 +37,21 @@ public ClassWithInheritingRecordsAndAutoProperties() public class ClassWithRecordsEmptyPrimaryConstructor { - internal record First + record First { public string Bar() => "baz"; } - internal record Second() + record Second() { public string Bar() => "baz"; } + + public ClassWithRecordsEmptyPrimaryConstructor() + { + new First().Bar(); + new Second().Bar(); + } } public class ClassWithAbstractRecords From b28dde7fd421c4c0ddcd686d934b0750f7e35c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Tue, 20 May 2025 23:44:45 +0200 Subject: [PATCH 08/16] refactored tests --- .../Coverage/CoverageTests.Records.cs | 10 ++++----- .../Samples/Instrumentation.Records.cs | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 06d3e1676..2d6a27594 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -220,9 +220,9 @@ public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) - .AssertLinesCovered(BuildConfiguration.Debug, (41, 1), (44, 1), (45, 1), (46, 1)) + //.AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) + //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) + .AssertLinesCovered(BuildConfiguration.Debug, (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); } @@ -231,8 +231,8 @@ public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.Records.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, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)); + //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); } } finally diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs index 1aa0341c4..4ba4aa880 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -70,5 +70,27 @@ private protected SecondAuditData() public abstract string GetAuditType(); } + + public record ConcreteFirstAuditData : FirstAuditData + { + public override string GetAuditType() + { + return string.Empty; + } + } + + public record ConcreteSecondAuditData : SecondAuditData + { + public override string GetAuditType() + { + return string.Empty; + } + } + + public ClassWithAbstractRecords() + { + new ConcreteFirstAuditData().GetAuditType(); + new ConcreteSecondAuditData().GetAuditType(); + } } } From d8a28a0bf84cad5cb184a41ed9c9bf458d36fb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sat, 14 Jun 2025 22:29:10 +0200 Subject: [PATCH 09/16] local tests --- .../Instrumentation/Instrumenter.cs | 4 +- .../Coverage/CoverageTests.Records.cs | 362 +++++++++--------- .../Samples/Instrumentation.Records.cs | 142 +++---- 3 files changed, 254 insertions(+), 254 deletions(-) diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 2878ff432..9fbd1150d 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -592,8 +592,8 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, - currentInstruction) || IsInsideExcludedMethodSection(sequencePoint)) + if (/*_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, + currentInstruction) ||*/ IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 2d6a27594..15386fd13 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -13,143 +13,143 @@ namespace Coverlet.CoreCoverage.Tests { public partial class CoverageTests { - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipAutoPropsInRecords(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) - .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); - } - } - finally - { - File.Delete(path); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipRecordWithProperties(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) - .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); - } - } - finally - { - File.Delete(path); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipInheritingRecordsWithProperties(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) - .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); - - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); - } - } - finally - { - File.Delete(path); - } - } + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public void SkipAutoPropsInRecords(bool skipAutoProps) + //{ + // string path = Path.GetTempFileName(); + // try + // { + // FunctionExecutor.RunInProcess(async (string[] parameters) => + // { + // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); + + // return 0; + // }, [path, skipAutoProps.ToString()]); + + // if (skipAutoProps) + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) + // .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) + // .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); + // } + // else + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) + // .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) + // .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); + // } + // } + // finally + // { + // File.Delete(path); + // } + //} + + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public void SkipRecordWithProperties(bool skipAutoProps) + //{ + // string path = Path.GetTempFileName(); + // try + // { + // FunctionExecutor.RunInProcess(async (string[] parameters) => + // { + // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + // { + // return Task.CompletedTask; + // }, + // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + // return 0; + // }, [path, skipAutoProps.ToString()]); + + // if (skipAutoProps) + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) + // .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) + // .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); + // } + // else + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); + // } + // } + // finally + // { + // File.Delete(path); + // } + //} + + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public void SkipInheritingRecordsWithProperties(bool skipAutoProps) + //{ + // string path = Path.GetTempFileName(); + // try + // { + // FunctionExecutor.RunInProcess(async (string[] parameters) => + // { + // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + // { + // return Task.CompletedTask; + // }, + // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + // return 0; + // }, [path, skipAutoProps.ToString()]); + + // if (skipAutoProps) + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) + // .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) + // .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); + + // } + // else + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); + // } + // } + // finally + // { + // File.Delete(path); + // } + //} [Theory] [InlineData(true)] @@ -196,49 +196,49 @@ public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) - { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.RunInProcess(async (string[] parameters) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - return 0; - }, [path, skipAutoProps.ToString()]); - - if (skipAutoProps) - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - //.AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) - //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) - .AssertLinesCovered(BuildConfiguration.Debug, (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); - - } - else - { - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) - .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)); - //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); - } - } - finally - { - File.Delete(path); - } - } + //[Theory] + //[InlineData(true)] + //[InlineData(false)] + //public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) + //{ + // string path = Path.GetTempFileName(); + // try + // { + // FunctionExecutor.RunInProcess(async (string[] parameters) => + // { + // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + // { + // return Task.CompletedTask; + // }, + // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + // return 0; + // }, [path, skipAutoProps.ToString()]); + + // if (skipAutoProps) + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // //.AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) + // //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) + // .AssertLinesCovered(BuildConfiguration.Debug, (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) + // .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); + + // } + // else + // { + // TestInstrumentationHelper.GetCoverageResult(path) + // .GenerateReport(show: true) + // .Document("Instrumentation.Records.cs") + // .AssertLinesCovered(BuildConfiguration.Debug, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)); + // //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); + // } + // } + // finally + // { + // File.Delete(path); + // } + //} } } diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs index 4ba4aa880..17580f02d 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -2,38 +2,38 @@ namespace Coverlet.Core.CoverageSamples.Tests { - public record RecordWithPropertyInit - { - private int _myRecordVal = 0; - public RecordWithPropertyInit() - { - _myRecordVal = new Random().Next(); - } - public string RecordAutoPropsNonInit { get; set; } - public string RecordAutoPropsInit { get; set; } = string.Empty; - } - - public class ClassWithRecordsAutoProperties - { - record RecordWithPrimaryConstructor(string Prop1, string Prop2); - - public ClassWithRecordsAutoProperties() - { - var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); - } - } - - public class ClassWithInheritingRecordsAndAutoProperties - { - record BaseRecord(int A); - - record InheritedRecord(int A) : BaseRecord(A); - - public ClassWithInheritingRecordsAndAutoProperties() - { - var record = new InheritedRecord(1); - } - } + //public record RecordWithPropertyInit + //{ + // private int _myRecordVal = 0; + // public RecordWithPropertyInit() + // { + // _myRecordVal = new Random().Next(); + // } + // public string RecordAutoPropsNonInit { get; set; } + // public string RecordAutoPropsInit { get; set; } = string.Empty; + //} + + //public class ClassWithRecordsAutoProperties + //{ + // record RecordWithPrimaryConstructor(string Prop1, string Prop2); + + // public ClassWithRecordsAutoProperties() + // { + // var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); + // } + //} + + //public class ClassWithInheritingRecordsAndAutoProperties + //{ + // record BaseRecord(int A); + + // record InheritedRecord(int A) : BaseRecord(A); + + // public ClassWithInheritingRecordsAndAutoProperties() + // { + // var record = new InheritedRecord(1); + // } + //} public class ClassWithRecordsEmptyPrimaryConstructor { @@ -54,43 +54,43 @@ public ClassWithRecordsEmptyPrimaryConstructor() } } - public class ClassWithAbstractRecords - { - public abstract record FirstAuditData() - { - public abstract string GetAuditType(); - } - - public abstract record SecondAuditData - { - private protected SecondAuditData() - { - - } - - public abstract string GetAuditType(); - } - - public record ConcreteFirstAuditData : FirstAuditData - { - public override string GetAuditType() - { - return string.Empty; - } - } - - public record ConcreteSecondAuditData : SecondAuditData - { - public override string GetAuditType() - { - return string.Empty; - } - } - - public ClassWithAbstractRecords() - { - new ConcreteFirstAuditData().GetAuditType(); - new ConcreteSecondAuditData().GetAuditType(); - } - } + // public class ClassWithAbstractRecords + // { + // public abstract record FirstAuditData() + // { + // public abstract string GetAuditType(); + // } + + // public abstract record SecondAuditData + // { + // private protected SecondAuditData() + // { + + // } + + // public abstract string GetAuditType(); + // } + + // public record ConcreteFirstAuditData : FirstAuditData + // { + // public override string GetAuditType() + // { + // return string.Empty; + // } + // } + + // public record ConcreteSecondAuditData : SecondAuditData + // { + // public override string GetAuditType() + // { + // return string.Empty; + // } + // } + + // public ClassWithAbstractRecords() + // { + // new ConcreteFirstAuditData().GetAuditType(); + // new ConcreteSecondAuditData().GetAuditType(); + // } + //} } From 3aa23ec7dce2b56232edaf3e038f663931bf8ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sat, 21 Jun 2025 23:33:06 +0200 Subject: [PATCH 10/16] fixed tests + uncommented symbol helper code --- .../Symbols/CecilSymbolHelper.cs | 108 ++--- .../Coverage/CoverageTests.Records.cs | 371 +++++++++--------- .../Samples/Instrumentation.Records.cs | 148 +++---- 3 files changed, 308 insertions(+), 319 deletions(-) diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index a278c5da3..b0b6aca68 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -1362,60 +1362,60 @@ private bool SkipExpressionBreakpointsSequences(MethodDefinition methodDefinitio return false; } - public bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction) - { - if (!skipAutoProps || !methodDefinition.IsConstructor) return false; - - return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction) || - SkipDefaultInitializationSystemObject(instruction); - } - - private static bool SkipGeneratedBackingFieldAssignment(MethodDefinition methodDefinition, Instruction instruction) - { - /* - For inline initialization of properties the compiler generates a field that is set in the constructor of the class. - To skip this we search for compiler generated fields that are set in the constructor. - - .field private string 'k__BackingField' - .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - IL_0000: ldarg.0 - IL_0001: ldsfld string[System.Runtime] System.String::Empty - IL_0006: stfld string TestRepro.ClassWithPropertyInit::'k__BackingField' - ... - } - ... - */ - IEnumerable autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => - x.CustomAttributes.Any(ca => ca.AttributeType.FullName.Equals(typeof(CompilerGeneratedAttribute).FullName)) && - x.FullName.EndsWith("k__BackingField")); - - return instruction.OpCode == OpCodes.Ldarg && - instruction.Next?.Next?.OpCode == OpCodes.Stfld && - instruction.Next?.Next?.Operand is FieldReference fr && - 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); - } + //public bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction) + //{ + // if (!skipAutoProps || !methodDefinition.IsConstructor) return false; + + // return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction) || + // SkipDefaultInitializationSystemObject(instruction); + //} + + //private static bool SkipGeneratedBackingFieldAssignment(MethodDefinition methodDefinition, Instruction instruction) + //{ + // /* + // For inline initialization of properties the compiler generates a field that is set in the constructor of the class. + // To skip this we search for compiler generated fields that are set in the constructor. + + // .field private string 'k__BackingField' + // .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + // 01 00 00 00 + // ) + + // .method public hidebysig specialname rtspecialname + // instance void .ctor () cil managed + // { + // IL_0000: ldarg.0 + // IL_0001: ldsfld string[System.Runtime] System.String::Empty + // IL_0006: stfld string TestRepro.ClassWithPropertyInit::'k__BackingField' + // ... + // } + // ... + // */ + // IEnumerable autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => + // x.CustomAttributes.Any(ca => ca.AttributeType.FullName.Equals(typeof(CompilerGeneratedAttribute).FullName)) && + // x.FullName.EndsWith("k__BackingField")); + + // return instruction.OpCode == OpCodes.Ldarg && + // instruction.Next?.Next?.OpCode == OpCodes.Stfld && + // instruction.Next?.Next?.Operand is FieldReference fr && + // 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) { diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 15386fd13..4defd6a6a 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -13,153 +13,147 @@ namespace Coverlet.CoreCoverage.Tests { public partial class CoverageTests { - //[Theory] - //[InlineData(true)] - //[InlineData(false)] - //public void SkipAutoPropsInRecords(bool skipAutoProps) - //{ - // string path = Path.GetTempFileName(); - // try - // { - // FunctionExecutor.RunInProcess(async (string[] parameters) => - // { - // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); - - // return 0; - // }, [path, skipAutoProps.ToString()]); - - // if (skipAutoProps) - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) - // .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) - // .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); - // } - // else - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) - // .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) - // .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); - // } - // } - // finally - // { - // File.Delete(path); - // } - //} - - //[Theory] - //[InlineData(true)] - //[InlineData(false)] - //public void SkipRecordWithProperties(bool skipAutoProps) - //{ - // string path = Path.GetTempFileName(); - // try - // { - // FunctionExecutor.RunInProcess(async (string[] parameters) => - // { - // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - // { - // return Task.CompletedTask; - // }, - // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - // return 0; - // }, [path, skipAutoProps.ToString()]); - - // if (skipAutoProps) - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) - // .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) - // .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); - // } - // else - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); - // } - // } - // finally - // { - // File.Delete(path); - // } - //} - - //[Theory] - //[InlineData(true)] - //[InlineData(false)] - //public void SkipInheritingRecordsWithProperties(bool skipAutoProps) - //{ - // string path = Path.GetTempFileName(); - // try - // { - // FunctionExecutor.RunInProcess(async (string[] parameters) => - // { - // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - // { - // return Task.CompletedTask; - // }, - // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - // return 0; - // }, [path, skipAutoProps.ToString()]); - - // if (skipAutoProps) - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) - // .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) - // .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); - - // } - // else - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); - // } - // } - // finally - // { - // File.Delete(path); - // } - //} + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipAutoPropsInRecords(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) + .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) + .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipRecordWithProperties(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) + .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipInheritingRecordsWithProperties(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) + .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); + + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); + } + } + finally + { + File.Delete(path); + } + } [Theory] [InlineData(true)] [InlineData(false)] - public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) + public void SkipRecordDeclarationWhenNoPrimaryConstructor(bool skipAutoProps) { string path = Path.GetTempFileName(); try { - FunctionExecutor.RunInProcess(async (string[] parameters) => + FunctionExecutor.Run(async (string[] parameters) => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { @@ -173,21 +167,21 @@ public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) if (skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) - //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) - .AssertLinesCovered(BuildConfiguration.Debug, (42, 1), (47, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 40, 40) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 40, 40) + .AssertLinesCovered(BuildConfiguration.Debug, (42, 1), (45, 1), (47, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (42, 1), (45, 1), (47, 1)); } else { TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show: true) .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (42, 1), (47, 1)); - //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 40, 40) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 40, 40) + .AssertLinesCovered(BuildConfiguration.Debug, (42, 1), (45, 1), (47, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (42, 1), (45, 1), (47, 1)); } } finally @@ -196,49 +190,44 @@ public void SkipInheritingRecordsWithPropertiesABC(bool skipAutoProps) } } - //[Theory] - //[InlineData(true)] - //[InlineData(false)] - //public void SkipInheritingRecordsWithPropertiesABCDEF(bool skipAutoProps) - //{ - // string path = Path.GetTempFileName(); - // try - // { - // FunctionExecutor.RunInProcess(async (string[] parameters) => - // { - // CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - // { - // return Task.CompletedTask; - // }, - // persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); - - // return 0; - // }, [path, skipAutoProps.ToString()]); - - // if (skipAutoProps) - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // //.AssertNonInstrumentedLines(BuildConfiguration.Debug, 39, 39) - // //.AssertNonInstrumentedLines(BuildConfiguration.Release, 39, 39) - // .AssertLinesCovered(BuildConfiguration.Debug, (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) - // .AssertLinesCovered(BuildConfiguration.Release, (45, 1)); - - // } - // else - // { - // TestInstrumentationHelper.GetCoverageResult(path) - // .GenerateReport(show: true) - // .Document("Instrumentation.Records.cs") - // .AssertLinesCovered(BuildConfiguration.Debug, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)); - // //.AssertLinesCovered(BuildConfiguration.Release, (39, 1), (41, 1), (45, 1)); - // } - // } - // finally - // { - // File.Delete(path); - // } - //} + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipAbstractRecordDeclarationWhenNoPrimaryConstructor(bool skipAutoProps) + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] parameters) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + + return 0; + }, [path, skipAutoProps.ToString()]); + + if (skipAutoProps) + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (59, 1), (66, 1), (69, 1), (78, 1), (86, 1)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (59, 1), (66, 1), (67, 1), (69, 1), (77, 1), (78, 1), (79, 1), (85, 1), (86, 1), (87, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (59, 1), (66, 1), (69, 1), (78, 1), (86, 1)); + } + } + finally + { + File.Delete(path); + } + } } } diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs index 17580f02d..47868411e 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -2,41 +2,41 @@ namespace Coverlet.Core.CoverageSamples.Tests { - //public record RecordWithPropertyInit - //{ - // private int _myRecordVal = 0; - // public RecordWithPropertyInit() - // { - // _myRecordVal = new Random().Next(); - // } - // public string RecordAutoPropsNonInit { get; set; } - // public string RecordAutoPropsInit { get; set; } = string.Empty; - //} - - //public class ClassWithRecordsAutoProperties - //{ - // record RecordWithPrimaryConstructor(string Prop1, string Prop2); - - // public ClassWithRecordsAutoProperties() - // { - // var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); - // } - //} - - //public class ClassWithInheritingRecordsAndAutoProperties - //{ - // record BaseRecord(int A); - - // record InheritedRecord(int A) : BaseRecord(A); - - // public ClassWithInheritingRecordsAndAutoProperties() - // { - // var record = new InheritedRecord(1); - // } - //} + public record RecordWithPropertyInit + { + private int _myRecordVal = 0; + public RecordWithPropertyInit() + { + _myRecordVal = new Random().Next(); + } + public string RecordAutoPropsNonInit { get; set; } + public string RecordAutoPropsInit { get; set; } = string.Empty; + } + + public class ClassWithRecordsAutoProperties + { + record RecordWithPrimaryConstructor(string Prop1, string Prop2); + + public ClassWithRecordsAutoProperties() + { + var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); + } + } + + public class ClassWithInheritingRecordsAndAutoProperties + { + record BaseRecord(int A); + + record InheritedRecord(int A) : BaseRecord(A); + + public ClassWithInheritingRecordsAndAutoProperties() + { + var record = new InheritedRecord(1); + } + } public class ClassWithRecordsEmptyPrimaryConstructor - { + { record First { public string Bar() => "baz"; @@ -52,45 +52,45 @@ public ClassWithRecordsEmptyPrimaryConstructor() new First().Bar(); new Second().Bar(); } - } - - // public class ClassWithAbstractRecords - // { - // public abstract record FirstAuditData() - // { - // public abstract string GetAuditType(); - // } - - // public abstract record SecondAuditData - // { - // private protected SecondAuditData() - // { - - // } - - // public abstract string GetAuditType(); - // } - - // public record ConcreteFirstAuditData : FirstAuditData - // { - // public override string GetAuditType() - // { - // return string.Empty; - // } - // } - - // public record ConcreteSecondAuditData : SecondAuditData - // { - // public override string GetAuditType() - // { - // return string.Empty; - // } - // } - - // public ClassWithAbstractRecords() - // { - // new ConcreteFirstAuditData().GetAuditType(); - // new ConcreteSecondAuditData().GetAuditType(); - // } - //} + } + + public class ClassWithAbstractRecords + { + public abstract record FirstAuditData() + { + public abstract string GetAuditType(); + } + + public abstract record SecondAuditData + { + private protected SecondAuditData() + { + + } + + public abstract string GetAuditType(); + } + + public record ConcreteFirstAuditData : FirstAuditData + { + public override string GetAuditType() + { + return string.Empty; + } + } + + public record ConcreteSecondAuditData : SecondAuditData + { + public override string GetAuditType() + { + return string.Empty; + } + } + + public ClassWithAbstractRecords() + { + new ConcreteFirstAuditData().GetAuditType(); + new ConcreteSecondAuditData().GetAuditType(); + } + } } From 98604d6fad22187e49e74f5519c23b2fa3f7c564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sat, 21 Jun 2025 23:42:36 +0200 Subject: [PATCH 11/16] fix --- .../Abstractions/ICecilSymbolHelper.cs | 1 - .../Instrumentation/Instrumenter.cs | 3 +- .../Symbols/CecilSymbolHelper.cs | 55 ------------------- .../Samples/Instrumentation.AutoProps.cs | 35 ------------ 4 files changed, 1 insertion(+), 93 deletions(-) diff --git a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs index b22463b5d..7941d96ba 100644 --- a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs +++ b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs @@ -12,6 +12,5 @@ internal interface ICecilSymbolHelper { IReadOnlyList GetBranchPoints(MethodDefinition methodDefinition); bool SkipNotCoverableInstruction(MethodDefinition methodDefinition, Instruction instruction); - bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction); } } diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 9fbd1150d..b36f93470 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -592,8 +592,7 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (/*_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, - currentInstruction) ||*/ IsInsideExcludedMethodSection(sequencePoint)) + if (IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index b0b6aca68..ec15e824d 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -1362,61 +1362,6 @@ private bool SkipExpressionBreakpointsSequences(MethodDefinition methodDefinitio return false; } - //public bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction) - //{ - // if (!skipAutoProps || !methodDefinition.IsConstructor) return false; - - // return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction) || - // SkipDefaultInitializationSystemObject(instruction); - //} - - //private static bool SkipGeneratedBackingFieldAssignment(MethodDefinition methodDefinition, Instruction instruction) - //{ - // /* - // For inline initialization of properties the compiler generates a field that is set in the constructor of the class. - // To skip this we search for compiler generated fields that are set in the constructor. - - // .field private string 'k__BackingField' - // .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - // 01 00 00 00 - // ) - - // .method public hidebysig specialname rtspecialname - // instance void .ctor () cil managed - // { - // IL_0000: ldarg.0 - // IL_0001: ldsfld string[System.Runtime] System.String::Empty - // IL_0006: stfld string TestRepro.ClassWithPropertyInit::'k__BackingField' - // ... - // } - // ... - // */ - // IEnumerable autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => - // x.CustomAttributes.Any(ca => ca.AttributeType.FullName.Equals(typeof(CompilerGeneratedAttribute).FullName)) && - // x.FullName.EndsWith("k__BackingField")); - - // return instruction.OpCode == OpCodes.Ldarg && - // instruction.Next?.Next?.OpCode == OpCodes.Stfld && - // instruction.Next?.Next?.Operand is FieldReference fr && - // 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) diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs index 3dd3b3da0..bbda25ee5 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs @@ -12,39 +12,4 @@ public AutoProps() public int AutoPropsNonInit { get; set; } public int AutoPropsInit { get; set; } = 10; } - - //public record RecordWithPropertyInit - //{ - // private int _myRecordVal = 0; - // public RecordWithPropertyInit() - // { - // _myRecordVal = new Random().Next(); - // } - // public string RecordAutoPropsNonInit { get; set; } - // public string RecordAutoPropsInit { get; set; } = string.Empty; - //} - - //public class ClassWithRecordsAutoProperties - //{ - // record RecordWithPrimaryConstructor(string Prop1, string Prop2); - - // public ClassWithRecordsAutoProperties() - // { - // var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); - // } - //} - - //public class ClassWithInheritingRecordsAndAutoProperties - //{ - // record BaseRecord(int A); - - // record InheritedRecord(int A) : BaseRecord(A); - - // public ClassWithInheritingRecordsAndAutoProperties() - // { - // var record = new InheritedRecord(1); - // } - //} - - } From 1829be13df1c5837a453f2162a0608ba9e3f33af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sun, 22 Jun 2025 00:35:34 +0200 Subject: [PATCH 12/16] fix --- .../Coverage/CoverageTests.Records.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 4defd6a6a..a8239959f 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -40,18 +40,17 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 13) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) - .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (9, 1), (10, 1), (11, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (10, 1)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, (12)) + .AssertNonInstrumentedLines(BuildConfiguration.Release, (12)) + .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (13, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (13, 1)); } else { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 10, 10) - .AssertLinesCoveredFromTo(BuildConfiguration.Release, 12, 13); + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (12, 1), (13, 1)); } } finally @@ -83,10 +82,8 @@ public void SkipRecordWithProperties(bool skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 18, 18) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 18, 18) - .AssertLinesCovered(BuildConfiguration.Debug, (21, 1), (22, 1), (23, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (22, 1)); + .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (22, 1)); } else { @@ -125,18 +122,16 @@ public void SkipInheritingRecordsWithProperties(bool skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 28, 28) - .AssertNonInstrumentedLines(BuildConfiguration.Release, 28, 28) - .AssertLinesCovered(BuildConfiguration.Debug, (30, 1), (33, 1), (34, 1), (35, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (34, 1)); + .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (32, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (32, 1), (34, 1)); } else { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (33, 1), (34, 1), (35, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (34, 1)); + .AssertLinesCovered(BuildConfiguration.Debug, (28, 1), (30, 1), (32, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (32, 1), (34, 1)); } } finally From b50306d9a4e137a79c80531f06ddfff14cfe4525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sun, 22 Jun 2025 22:57:48 +0200 Subject: [PATCH 13/16] add fix --- .../Abstractions/ICecilSymbolHelper.cs | 1 + .../Instrumentation/Instrumenter.cs | 3 +- .../Symbols/CecilSymbolHelper.cs | 38 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs index 7941d96ba..b22463b5d 100644 --- a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs +++ b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs @@ -12,5 +12,6 @@ internal interface ICecilSymbolHelper { IReadOnlyList GetBranchPoints(MethodDefinition methodDefinition); bool SkipNotCoverableInstruction(MethodDefinition methodDefinition, Instruction instruction); + bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction); } } diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index b36f93470..2878ff432 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -592,7 +592,8 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (IsInsideExcludedMethodSection(sequencePoint)) + if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, + currentInstruction) || IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index ec15e824d..c91968944 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -1362,6 +1362,44 @@ private bool SkipExpressionBreakpointsSequences(MethodDefinition methodDefinitio return false; } + public bool SkipInlineAssignedAutoProperty(bool skipAutoProps, MethodDefinition methodDefinition, Instruction instruction) + { + if (!skipAutoProps) return false; + + return SkipGeneratedBackingFieldAssignment(methodDefinition, instruction); + } + + private static bool SkipGeneratedBackingFieldAssignment(MethodDefinition methodDefinition, Instruction instruction) + { + /* + For inline initialization of properties the compiler generates a field that is set in the constructor of the class. + To skip this we search for compiler generated fields that are set in the constructor. + + .field private string 'k__BackingField' + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldarg.0 + IL_0001: ldsfld string[System.Runtime] System.String::Empty + IL_0006: stfld string TestRepro.ClassWithPropertyInit::'k__BackingField' + ... + } + ... + */ + IEnumerable autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => + x.CustomAttributes.Any(ca => ca.AttributeType.FullName.Equals(typeof(CompilerGeneratedAttribute).FullName)) && + x.FullName.EndsWith("k__BackingField")); + + return instruction.OpCode == OpCodes.Ldarg && + instruction.Next?.Next?.OpCode == OpCodes.Stfld && + instruction.Next?.Next?.Operand is FieldReference fr && + autogeneratedBackingFields.Select(x => x.FullName).Contains(fr.FullName); + } + private static bool SkipBranchGeneratedExceptionFilter(Instruction branchInstruction, MethodDefinition methodDefinition) { if (!methodDefinition.Body.HasExceptionHandlers) From 215729563e21dfa6d487071a4dece8cc57c0ed6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sun, 22 Jun 2025 23:18:06 +0200 Subject: [PATCH 14/16] fix --- .../Coverage/CoverageTests.Records.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index a8239959f..980e52abe 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -40,10 +40,10 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) { TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, (12)) - .AssertNonInstrumentedLines(BuildConfiguration.Release, (12)) - .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (13, 1)) - .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (13, 1)); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 12,13) + .AssertNonInstrumentedLines(BuildConfiguration.Release, 12, 13) + .AssertLinesCovered(BuildConfiguration.Debug, (7, 1), (8, 1), (9, 1), (10, 1), (11, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1)); } else { From ea9508e43c18eddd70c7e5ea554e88bc02769f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sun, 22 Jun 2025 23:33:48 +0200 Subject: [PATCH 15/16] test --- .../Coverage/InstrumenterHelper.Assertions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs b/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs index c7afeb814..1da26abe3 100644 --- a/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs +++ b/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs @@ -32,7 +32,7 @@ public static CoverageResult GenerateReport(this CoverageResult coverageResult, { Process.Start("cmd", "/C " + Path.GetFullPath(Path.Combine(directory, "index.htm"))); } - Process.Start("cmd", "/C " + Path.GetFullPath(Path.Combine(directory, "index.htm"))); + return coverageResult; } From 820e25594ddc1b004c3eee43a452f4b201d7779f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Sun, 22 Jun 2025 23:51:52 +0200 Subject: [PATCH 16/16] nit --- .../Coverage/CoverageTests.Records.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs index 980e52abe..9afd61da4 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -50,7 +50,7 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.Records.cs") .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) - .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (12, 1), (13, 1)); + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (12, 2), (13, 3)); } } finally