Skip to content

Commit d70cb64

Browse files
Fix skip [ExcludefromCoverage] for generated async state machine (#849)
Fix skip [ExcludefromCoverage] for generated async state machine
1 parent 8a1026d commit d70cb64

File tree

4 files changed

+353
-1
lines changed

4 files changed

+353
-1
lines changed

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,37 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
372372
Debug.Assert(_customTrackerClassConstructorIl != null);
373373
}
374374

375+
private bool IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded(MethodDefinition method)
376+
{
377+
// Type compiler generated, the async state machine
378+
TypeDefinition typeDefinition = method.DeclaringType;
379+
if (typeDefinition.DeclaringType is null)
380+
{
381+
return false;
382+
}
383+
384+
// Search in type that contains async state machine, compiler generates async state machine in private nested class
385+
foreach (MethodDefinition typeMethod in typeDefinition.DeclaringType.Methods)
386+
{
387+
// If we find the async state machine attribute on method
388+
CustomAttribute attribute;
389+
if ((attribute = typeMethod.CustomAttributes.SingleOrDefault(a => a.AttributeType.FullName == typeof(AsyncStateMachineAttribute).FullName)) != null)
390+
{
391+
// If the async state machine generated by compiler is "associated" to this method we check for exclusions
392+
// The associated type is specified on attribute constructor
393+
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.asyncstatemachineattribute.-ctor?view=netcore-3.1
394+
if (attribute.ConstructorArguments[0].Value == method.DeclaringType)
395+
{
396+
if (typeMethod.CustomAttributes.Any(IsExcludeAttribute))
397+
{
398+
return true;
399+
}
400+
}
401+
}
402+
}
403+
return false;
404+
}
405+
375406
private void InstrumentType(TypeDefinition type)
376407
{
377408
var methods = type.GetMethods();
@@ -394,6 +425,12 @@ private void InstrumentType(TypeDefinition type)
394425
}
395426

396427
ordinal++;
428+
429+
if (IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded(method))
430+
{
431+
continue;
432+
}
433+
397434
if (IsSynthesizedMemberToBeExcluded(method))
398435
{
399436
continue;
@@ -413,7 +450,9 @@ private void InstrumentType(TypeDefinition type)
413450
foreach (var ctor in ctors)
414451
{
415452
if (!ctor.CustomAttributes.Any(IsExcludeAttribute))
453+
{
416454
InstrumentMethod(ctor);
455+
}
417456
}
418457
}
419458

test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,41 @@ public void ExcludeFromCodeCoverageNextedTypes()
169169
File.Delete(path);
170170
}
171171
}
172+
173+
[Fact]
174+
public void ExcludeFromCodeCoverage_Issue809()
175+
{
176+
string path = Path.GetTempFileName();
177+
try
178+
{
179+
FunctionExecutor.Run(async (string[] pathSerialize) =>
180+
{
181+
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<TaskRepo_Issue809>(instance =>
182+
{
183+
Assert.True(((Task<bool>)instance.EditTask(null, 10)).GetAwaiter().GetResult());
184+
return Task.CompletedTask;
185+
}, persistPrepareResultToFile: pathSerialize[0]);
186+
187+
return 0;
188+
}, new string[] { path });
189+
190+
TestInstrumentationHelper.GetCoverageResult(path)
191+
.Document("Instrumentation.ExcludeFromCoverage.Issue809.cs")
192+
193+
// public async Task<bool> EditTask(Tasks_Issue809 tasks, int val)
194+
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 153, 162)
195+
// .AssertNonInstrumentedLines(BuildConfiguration.Debug, 167, 170) -> Shoud be not covered, issue with lambda
196+
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 167, 197)
197+
198+
// public List<Tasks_Issue809> GetAllTasks()
199+
// .AssertNonInstrumentedLines(BuildConfiguration.Debug, 263, 266) -> Shoud be not covered, issue with lambda
200+
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 263, 264);
201+
// .AssertNonInstrumentedLines(BuildConfiguration.Debug, 269, 275) -> Shoud be not covered, issue with lambda
202+
}
203+
finally
204+
{
205+
File.Delete(path);
206+
}
207+
}
172208
}
173209
}
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Remember to use full name because adding new using directives change line numbers
2+
using System.Linq;
3+
4+
namespace Coverlet.Core.Samples.Tests
5+
{
6+
7+
public class ParentTask_Issue809
8+
{
9+
public int Parent_ID { get; set; }
10+
public int Parent_Task { get; set; }
11+
public string ParentTaskDescription { get; set; }
12+
public System.Collections.Generic.List<Tasks_Issue809> Tasks { get; set; }
13+
}
14+
15+
public class Tasks_Issue809
16+
{
17+
public int TaskId { get; set; }
18+
public string TaskDeatails { get; set; }
19+
public System.DateTime StartDate { get; set; }
20+
public System.DateTime EndDate { get; set; }
21+
public int ParentTaskId { get; set; }
22+
public int Priortiy { get; set; }
23+
public int Status { get; set; }
24+
25+
public ParentTask_Issue809 ParentTask { get; set; }
26+
}
27+
28+
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
29+
public class TaskContext_Issue809
30+
{
31+
public virtual System.Collections.Generic.List<ParentTask_Issue809> ParentTasks { get; set; }
32+
public virtual System.Collections.Generic.List<Tasks_Issue809> Tasks { get; set; }
33+
34+
internal System.Threading.Tasks.Task<int> SaveChangesAsync()
35+
{
36+
throw new System.NotImplementedException();
37+
}
38+
39+
internal void Update<T>(T tasks)
40+
{
41+
throw new System.NotImplementedException();
42+
}
43+
}
44+
45+
public class SearchMsg_Issue809
46+
{
47+
public int TaskId { get; set; } = -1;
48+
public string TaskDescription { get; set; }
49+
public int ParentTaskId { get; set; } = -1;
50+
public int PriorityFrom { get; set; } = -1;
51+
public int PriorityTo { get; set; } = -1;
52+
public System.DateTime FromDate { get; set; }
53+
public System.DateTime ToDate { get; set; }
54+
}
55+
56+
public class TaskRepo_Issue809
57+
{
58+
private readonly TaskContext_Issue809 taskContext = new TaskContext_Issue809();
59+
60+
public System.Collections.Generic.List<Tasks_Issue809> GetTaskForAllCriteria(SearchMsg_Issue809 searchMsg)
61+
{
62+
var criteriaPredicate = LinqKit.PredicateBuilder.New<Tasks_Issue809>(true);
63+
if (searchMsg.TaskId > 0)
64+
criteriaPredicate = criteriaPredicate.And(tsk => tsk.TaskId == searchMsg.TaskId);
65+
if (searchMsg.ParentTaskId > 0)
66+
{
67+
var parentTask = taskContext.ParentTasks.FirstOrDefault(
68+
partask => partask.Parent_Task == searchMsg.ParentTaskId);
69+
var parentId = (parentTask != default) ? parentTask.Parent_ID : 0;
70+
71+
criteriaPredicate = criteriaPredicate.And(tsk => tsk.ParentTaskId == parentId);
72+
}
73+
74+
if (searchMsg.PriorityFrom > 0)
75+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.Priortiy >= searchMsg.PriorityFrom);
76+
if (searchMsg.PriorityTo > 0)
77+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.Priortiy <= searchMsg.PriorityTo);
78+
if (searchMsg.FromDate > System.DateTime.MinValue)
79+
criteriaPredicate = criteriaPredicate.And(tsk => tsk.StartDate == searchMsg.FromDate);
80+
if (searchMsg.ToDate > System.DateTime.MinValue)
81+
criteriaPredicate = criteriaPredicate.And(tsk => tsk.EndDate == searchMsg.ToDate);
82+
if (!string.IsNullOrWhiteSpace(searchMsg.TaskDescription))
83+
criteriaPredicate = criteriaPredicate.And(tsk =>
84+
tsk.TaskDeatails.CompareTo(searchMsg.TaskDescription) == 0);
85+
86+
var anyTaskQuery = from taskEntity in taskContext.Tasks.Where(criteriaPredicate.Compile())
87+
select taskEntity;
88+
89+
var tasks = anyTaskQuery.ToList();
90+
tasks.ForEach(task =>
91+
{
92+
if (task.ParentTaskId > 0)
93+
{
94+
task.ParentTask = taskContext.ParentTasks.FirstOrDefault();
95+
96+
}
97+
});
98+
99+
return tasks;
100+
101+
}
102+
103+
public System.Collections.Generic.List<Tasks_Issue809> GetTaskForAnyCriteria(SearchMsg_Issue809 searchMsg)
104+
{
105+
var criteriaPredicate = LinqKit.PredicateBuilder.New<Tasks_Issue809>(false);
106+
if (searchMsg.TaskId > 0)
107+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.TaskId == searchMsg.TaskId);
108+
if (!string.IsNullOrWhiteSpace(searchMsg.TaskDescription))
109+
criteriaPredicate = criteriaPredicate.Or(tsk =>
110+
tsk.TaskDeatails.CompareTo(searchMsg.TaskDescription) == 0);
111+
if (searchMsg.ParentTaskId > 0)
112+
{
113+
var parentTask = taskContext.ParentTasks.FirstOrDefault(
114+
partask => partask.Parent_Task == searchMsg.ParentTaskId);
115+
var parentId = (parentTask != default) ? parentTask.Parent_ID : 0;
116+
117+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.ParentTaskId == parentId);
118+
}
119+
120+
if (searchMsg.PriorityFrom > 0)
121+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.Priortiy >= searchMsg.PriorityFrom);
122+
if (searchMsg.PriorityTo > 0)
123+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.Priortiy <= searchMsg.PriorityTo);
124+
if (searchMsg.FromDate > System.DateTime.MinValue)
125+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.StartDate == searchMsg.FromDate);
126+
if (searchMsg.ToDate > System.DateTime.MinValue)
127+
criteriaPredicate = criteriaPredicate.Or(tsk => tsk.EndDate == searchMsg.ToDate);
128+
var anyTaskQuery = from taskEntity in taskContext.Tasks.Where(criteriaPredicate.Compile())
129+
select taskEntity;
130+
131+
var tasks = anyTaskQuery.ToList();
132+
tasks.ForEach(task =>
133+
{
134+
if (task.ParentTaskId > 0)
135+
{
136+
task.ParentTask = taskContext.ParentTasks.FirstOrDefault();
137+
}
138+
});
139+
140+
return tasks;
141+
}
142+
public async System.Threading.Tasks.Task<bool> AddTask(Tasks_Issue809 tasks)
143+
{
144+
_ = await manageParentTask(tasks);
145+
taskContext.Tasks.Add(tasks);
146+
var rowsAffected = await taskContext.SaveChangesAsync();
147+
return (rowsAffected > 0) ? true : false;
148+
149+
}
150+
151+
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
152+
public async System.Threading.Tasks.Task<bool> EditTask(Tasks_Issue809 tasks, int val)
153+
{
154+
if (val == 10)
155+
{
156+
return true;
157+
}
158+
else
159+
{
160+
if ((tasks.ParentTask != default) && (tasks.ParentTask.Parent_Task == 0))
161+
tasks.ParentTask = null;
162+
163+
var oldTaskQuery = from taskEntity in taskContext.Tasks.Where(tsk => tsk.TaskId == tasks.TaskId)
164+
from parTaskEntity in taskContext.ParentTasks.Where(partask =>
165+
partask.Parent_ID == taskEntity.ParentTaskId).DefaultIfEmpty()
166+
select new { taskEntity, parTaskEntity };
167+
var oldTaskValueObj = oldTaskQuery.FirstOrDefault();
168+
var oldTask = oldTaskValueObj.taskEntity;
169+
if (oldTaskValueObj.parTaskEntity != default)
170+
{
171+
oldTask.ParentTask = new ParentTask_Issue809
172+
{
173+
Parent_ID = oldTaskValueObj.parTaskEntity.Parent_ID,
174+
ParentTaskDescription = oldTaskValueObj.parTaskEntity.ParentTaskDescription,
175+
Parent_Task = oldTaskValueObj.parTaskEntity.Parent_Task
176+
};
177+
}
178+
179+
180+
if (oldTask == default)
181+
throw new System.ApplicationException("Task not found");
182+
if (((oldTask.ParentTask != null) && (oldTask.ParentTask.Parent_ID != tasks.ParentTaskId)) ||
183+
((oldTask.ParentTask == default) && (tasks.ParentTask != default) && (tasks.ParentTask.Parent_Task > 0)))
184+
_ = await manageParentTask(tasks);
185+
186+
187+
taskContext.Update<Tasks_Issue809>(tasks);
188+
var rowsAffected = await taskContext.SaveChangesAsync();
189+
190+
bool combinedResult = (rowsAffected > 0) ? true : false;
191+
bool parentUpdateResult = await UpdateParentTakDetails(tasks);
192+
if ((combinedResult) && (parentUpdateResult))
193+
return true;
194+
else
195+
return false;
196+
}
197+
}
198+
199+
private async System.Threading.Tasks.Task<bool> UpdateParentTakDetails(Tasks_Issue809 task)
200+
{
201+
var parentTask = taskContext.ParentTasks.FirstOrDefault(parTsk =>
202+
parTsk.Parent_Task == task.ParentTaskId);
203+
if ((parentTask != default) &&
204+
(parentTask.ParentTaskDescription.CompareTo(task.TaskDeatails) != 0))
205+
{
206+
parentTask.ParentTaskDescription = task.TaskDeatails;
207+
taskContext.Update(parentTask);
208+
var recordsAffected = await taskContext.SaveChangesAsync();
209+
return (recordsAffected > 0) ? true : false;
210+
}
211+
return true;
212+
213+
}
214+
215+
private async System.Threading.Tasks.Task<Tasks_Issue809> manageParentTask(Tasks_Issue809 task)
216+
{
217+
218+
if ((task.ParentTask != null) && (task.ParentTask.Parent_Task > 0))
219+
{
220+
ParentTask_Issue809 parentTask = taskContext.ParentTasks.FirstOrDefault(parTsk =>
221+
parTsk.Parent_Task == task.ParentTaskId);
222+
if (parentTask == default)
223+
{
224+
var parTaskFromTaskEntity = taskContext.Tasks
225+
.FirstOrDefault(tsk => tsk.TaskId == task.ParentTaskId);
226+
parentTask = new ParentTask_Issue809
227+
{
228+
Parent_Task = parTaskFromTaskEntity.TaskId,
229+
ParentTaskDescription = parTaskFromTaskEntity.TaskDeatails
230+
};
231+
taskContext.ParentTasks.Add(parentTask);
232+
await taskContext.SaveChangesAsync();
233+
234+
}
235+
else
236+
{
237+
taskContext.Update(parentTask);
238+
await taskContext.SaveChangesAsync();
239+
240+
}
241+
242+
task.ParentTaskId = parentTask.Parent_ID;
243+
task.ParentTask = parentTask;
244+
}
245+
else
246+
task.ParentTask = null;
247+
248+
return task;
249+
}
250+
251+
public System.Threading.Tasks.Task<System.Collections.Generic.List<ParentTask_Issue809>> GetAllParentTasks()
252+
{
253+
return System.Threading.Tasks.Task.FromResult(taskContext.ParentTasks.ToList());
254+
}
255+
256+
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
257+
public System.Collections.Generic.List<Tasks_Issue809> GetAllTasks()
258+
{
259+
var taskQuery = from taskEntity in taskContext.Tasks.Where(tsk => tsk.Status >= 0)
260+
from parTaskEntity in taskContext.ParentTasks.Where(partask =>
261+
partask.Parent_ID == taskEntity.ParentTaskId).DefaultIfEmpty()
262+
select new { taskEntity, parTaskEntity };
263+
var taskValueObj = taskQuery.ToList();
264+
var tasks = taskValueObj.Select(valueObj =>
265+
{
266+
if (valueObj.parTaskEntity != null)
267+
{
268+
valueObj.taskEntity.ParentTask = valueObj.parTaskEntity;
269+
}
270+
return valueObj.taskEntity;
271+
}).ToList();
272+
return tasks;
273+
}
274+
}
275+
}

test/coverlet.core.tests/coverlet.core.tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.xunit.extensions\coverlet.tests.xunit.extensions.csproj" />
2929
</ItemGroup>
3030

31-
<!--For test TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationContext-->
3231
<ItemGroup>
32+
<!--For test TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationContext-->
3333
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
34+
<!--For test issue 809 https://github.com/coverlet-coverage/coverlet/issues/809-->
35+
<PackageReference Include="LinqKit.Microsoft.EntityFrameworkCore" Version="2.0.0" />
3436
</ItemGroup>
3537

3638
<ItemGroup>

0 commit comments

Comments
 (0)