diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index a278c5da3..c91968944 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -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) @@ -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) diff --git a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs index 7deabe6f0..59bb3cde2 100644 --- a/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.AutoProps.cs @@ -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(instance => + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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])); @@ -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)); @@ -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 @@ -64,22 +65,18 @@ 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(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(instance => + { + return Task.CompletedTask; + }, + persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); return 0; }, [path, skipAutoProps.ToString()]); @@ -87,19 +84,20 @@ public void SkipAutoPropsInRecords(bool skipAutoProps) 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 @@ -111,18 +109,23 @@ 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(instance => - { - return Task.CompletedTask; - }, - persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1])); + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(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()]); @@ -130,18 +133,20 @@ public void SkipRecordWithProperties(bool skipAutoProps) 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 @@ -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(instance => + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { return Task.CompletedTask; }, @@ -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(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 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..b48ee4db2 --- /dev/null +++ b/test/coverlet.core.coverage.tests/Coverage/CoverageTests.Records.cs @@ -0,0 +1,228 @@ +// 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.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), (8, 1), (9, 1), (10, 1), (11, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1)); + } + else + { + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.Records.cs") + .AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13) + .AssertLinesCovered(BuildConfiguration.Release, (8, 1), (10, 1), (11, 1), (12, 2), (13, 3)); + } + } + 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") + .AssertLinesCovered(BuildConfiguration.Debug, (18, 1), (20, 1), (21, 1), (22, 1), (23, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (18, 1), (20, 1), (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") + .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), (32, 1), (33, 1), (34, 1), (35, 1)) + .AssertLinesCovered(BuildConfiguration.Release, (28, 1), (30, 1), (32, 1), (34, 1)); + } + } + finally + { + File.Delete(path); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SkipRecordDeclarationWhenNoPrimaryConstructor(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, 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) + .Document("Instrumentation.Records.cs") + .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 + { + 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/Coverage/InstrumenterHelper.Assertions.cs b/test/coverlet.core.coverage.tests/Coverage/InstrumenterHelper.Assertions.cs index 582be3993..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"))); } - + return coverageResult; } diff --git a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs index d649d94a8..12922edac 100644 --- a/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.AutoProps.cs @@ -2,49 +2,62 @@ namespace Coverlet.Core.CoverageSamples.Tests { - public class AutoProps + public class ClassWithAutoProps { private int _myVal = 0; - public AutoProps() + public ClassWithAutoProps() { _myVal = new Random().Next(); } public int AutoPropsNonInit { get; set; } public int AutoPropsInit { get; set; } = 10; + public int AutoPropsInitKeyword { get; init; } } - public record RecordWithPropertyInit - { - private int _myRecordVal = 0; - public RecordWithPropertyInit() + public class ClassWithAutoPropsPrimaryConstructor { - _myRecordVal = new Random().Next(); + public ClassWithAutoPropsPrimaryConstructor() + { + var instance = new InnerClassWithAutoProps(1) + { + AutoPropsNonInit = 20, + AutoPropsInit = 30, + AutoPropsInitKeyword = 33 + }; + } + private class InnerClassWithAutoProps(int myVal = 10) + { + public int AutoPropsNonInit { get; set; } + public int AutoPropsInit { get; set; } = myVal; + public int AutoPropsInitKeyword { get; init; } + } } - public string RecordAutoPropsNonInit { get; set; } - public string RecordAutoPropsInit { get; set; } = string.Empty; - } - public class ClassWithRecordsAutoProperties + public record RecordWithAutoProps { - record RecordWithPrimaryConstructor(string Prop1, string Prop2); - - public ClassWithRecordsAutoProperties() - { - var record = new RecordWithPrimaryConstructor(string.Empty, string.Empty); - } + private int _myVal = 0; + public RecordWithAutoProps() + { + _myVal = new Random().Next(); + } + public int AutoPropsNonInit { get; set; } + public int AutoPropsInit { get; set; } = 10; + public int AutoPropsInitKeyword { get; init; } } - public class ClassWithInheritingRecordsAndAutoProperties - { - record BaseRecord(int A); - - record InheritedRecord(int A) : BaseRecord(A); - - public ClassWithInheritingRecordsAndAutoProperties() + public class RecordsWithPrimaryConstructor { - var record = new InheritedRecord(1); + public record RecordWithAutoPropsPrimaryConstructor(int AutoPropsNonInit, int AutoPropsInit = 10); + + public record RecordWithAutoPropsPrimaryConstructorMultiline( + int AutoPropsNonInit, + int AutoPropsInit = 10 + ); + + public RecordsWithPrimaryConstructor() + { + var primaryConstructor = new RecordWithAutoPropsPrimaryConstructor(20, 30); + var primaryConstructorMultiline = new RecordWithAutoPropsPrimaryConstructorMultiline(20); + } } - } - - } 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..286be8a29 --- /dev/null +++ b/test/coverlet.core.coverage.tests/Samples/Instrumentation.Records.cs @@ -0,0 +1,96 @@ +using System; + +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 ClassWithInheritingRecordsAndPrimaryConstructor + { + record BaseRecord(int A); + + record InheritedRecord(int A) : BaseRecord(A); + + public ClassWithInheritingRecordsAndPrimaryConstructor() + { + var record = new InheritedRecord(1); + } + } + + public class ClassWithRecordsEmptyPrimaryConstructor + { + record First + { + public string Bar() => "baz"; + } + + record Second() + { + public string Bar() => "baz"; + } + + 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(); + } + } +}