Skip to content

Commit 6d9a114

Browse files
committed
Fingers crossed.
1 parent e150811 commit 6d9a114

19 files changed

+621
-423
lines changed

FineCodeCoverageTests/Editor/DynamicCoverage/BufferLineCoverage_Tests.cs

Lines changed: 358 additions & 361 deletions
Large diffs are not rendered by default.

FineCodeCoverageTests/Editor/DynamicCoverage/DynamicCoverageManager_Tests.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
using AutoMoq;
1+
using System;
2+
using AutoMoq;
23
using FineCodeCoverage.Core.Utilities;
34
using FineCodeCoverage.Editor.DynamicCoverage;
5+
using FineCodeCoverage.Editor.DynamicCoverage.Utilities;
46
using FineCodeCoverage.Engine;
57
using FineCodeCoverage.Engine.Model;
8+
using FineCodeCoverage.Impl;
69
using FineCodeCoverageTests.Test_helpers;
710
using Microsoft.VisualStudio.Text;
811
using Microsoft.VisualStudio.Text.Editor;
@@ -51,14 +54,20 @@ public void Manage_Should_Create_Singleton_IBufferLineCoverage()
5154
public void Manage_Should_Create_Singleton_IBufferLineCoverage_With_Last_Coverage_And_Dependencies(bool hasLastCoverage)
5255
{
5356
var autoMocker = new AutoMoqer();
57+
58+
var now = new DateTime();
59+
autoMocker.GetMock<IDateTimeService>().Setup(dateTimeService => dateTimeService.Now).Returns(now);
60+
5461
var eventAggregator = autoMocker.GetMock<IEventAggregator>().Object;
5562
var trackedLinesFactory = autoMocker.GetMock<ITrackedLinesFactory>().Object;
5663
var dynamicCoverageManager = autoMocker.Create<DynamicCoverageManager>();
57-
IFileLineCoverage lastCoverage = null;
64+
LastCoverage lastCoverage = null;
5865
if (hasLastCoverage)
5966
{
60-
lastCoverage = new Mock<IFileLineCoverage>().Object;
61-
dynamicCoverageManager.Handle(new NewCoverageLinesMessage { CoverageLines = lastCoverage});
67+
var fileLineCoverage = new Mock<IFileLineCoverage>().Object;
68+
lastCoverage = new LastCoverage(fileLineCoverage, now);
69+
(dynamicCoverageManager as IListener<TestExecutionStartingMessage>).Handle(new TestExecutionStartingMessage());
70+
dynamicCoverageManager.Handle(new NewCoverageLinesMessage { CoverageLines = fileLineCoverage});
6271
}
6372

6473
var mockTextInfo = new Mock<ITextInfo>();

FineCodeCoverageTests/Editor/DynamicCoverage/DynamicCoverageStore_Tests.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AutoMoq;
22
using FineCodeCoverage.Core.Utilities;
33
using FineCodeCoverage.Editor.DynamicCoverage;
4+
using FineCodeCoverage.Editor.DynamicCoverage.Utilities;
45
using FineCodeCoverage.Engine;
56
using FineCodeCoverage.Options;
67
using Microsoft.VisualStudio.Settings;
@@ -43,14 +44,20 @@ public void Should_Delete_WritableUserSettingsStore_Collection_When_NewCoverageL
4344
public void Should_SaveSerializedCoverage_To_The_Store_Creating_Collection_If_Does_Not_Exist(bool collectionExists)
4445
{
4546
var autoMoqer = new AutoMoqer();
47+
var mockDateTimeService = autoMoqer.GetMock<IDateTimeService>();
48+
var now = DateTime.Now;
49+
mockDateTimeService.SetupGet(dateTimeService => dateTimeService.Now).Returns(now);
50+
var mockJsonConvertService = autoMoqer.GetMock<IJsonConvertService>();
51+
mockJsonConvertService.Setup(jsonConvertService => jsonConvertService.SerializeObject(
52+
new SerializedCoverageWhen { Serialized = "serialized coverage", When = now })).Returns("serialized");
4653
var mockWritableSettingsStore = new Mock<WritableSettingsStore>();
4754
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.CollectionExists("FCC.DynamicCoverageStore")).Returns(collectionExists);
4855
autoMoqer.Setup<IWritableUserSettingsStoreProvider, WritableSettingsStore>(
4956
writableUserSettingsStoreProvider => writableUserSettingsStoreProvider.Provide()).Returns(mockWritableSettingsStore.Object);
5057

5158
var dynamicCoverageStore = autoMoqer.Create<DynamicCoverageStore>();
5259

53-
dynamicCoverageStore.SaveSerializedCoverage("filePath", "serialized");
60+
dynamicCoverageStore.SaveSerializedCoverage("filePath", "serialized coverage");
5461

5562
mockWritableSettingsStore.Verify(writableSettingsStore => writableSettingsStore.CreateCollection("FCC.DynamicCoverageStore"), Times.Exactly(collectionExists ? 0 : 1));
5663
mockWritableSettingsStore.Verify(writableSettingsStore => writableSettingsStore.SetString("FCC.DynamicCoverageStore", "filePath", "serialized"), Times.Once);
@@ -76,18 +83,32 @@ public void Should_Return_Null_For_GetSerializedCoverage_When_Collection_Does_No
7683
public void Should_Return_From_Collection_When_Property_Exists(bool propertyExists)
7784
{
7885
var autoMoqer = new AutoMoqer();
86+
7987
var mockWritableSettingsStore = new Mock<WritableSettingsStore>();
8088
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.CollectionExists("FCC.DynamicCoverageStore")).Returns(true);
8189
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.PropertyExists("FCC.DynamicCoverageStore", "filePath")).Returns(propertyExists);
8290
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.GetString("FCC.DynamicCoverageStore", "filePath")).Returns("serialized");
8391
autoMoqer.Setup<IWritableUserSettingsStoreProvider, WritableSettingsStore>(
8492
writableUserSettingsStoreProvider => writableUserSettingsStoreProvider.Provide()).Returns(mockWritableSettingsStore.Object);
8593

94+
var deserializedCoverageWhen = new SerializedCoverageWhen { When = DateTime.Now, Serialized = "serializedCoverage coverage" };
95+
var mockJsonConvertService = autoMoqer.GetMock<IJsonConvertService>();
96+
mockJsonConvertService.Setup(jsonConvertService => jsonConvertService.DeserializeObject<SerializedCoverageWhen>("serialized"))
97+
.Returns(deserializedCoverageWhen);
98+
8699
var dynamicCoverageStore = autoMoqer.Create<DynamicCoverageStore>();
87100

88-
var serializedCoverage = dynamicCoverageStore.GetSerializedCoverage("filePath");
101+
var serializedCoverageWhen = dynamicCoverageStore.GetSerializedCoverage("filePath");
89102

90-
Assert.AreEqual(propertyExists ? "serialized" : null, serializedCoverage);
103+
if (propertyExists)
104+
{
105+
Assert.AreSame(deserializedCoverageWhen, serializedCoverageWhen);
106+
}
107+
else
108+
{
109+
Assert.Null(serializedCoverageWhen);
110+
111+
}
91112
}
92113

93114
private void FileRename(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ If you switch to one of the EditorCoverageColouringMode options then you will ne
8484
For Blazor components with @code blocks coverage lines can be generated outside these regions.
8585
When the Roslyn syntax tree is available to FCC you can set the option BlazorCoverageLinesFromGeneratedSource to true to limit coverage lines in .razor file to those in generated source.
8686

87-
FCC tracks the visual studio editor and saves this information when a file is closed. If upon re-opening a file the text has changed there will be no coverage marks for this file.
87+
FCC tracks the visual studio editor and saves this information when a file is closed. If upon re-opening a file the text has changed outside of a document window there will be no coverage marks for this file as the coverage lines are no longer expected to be correct..
8888

8989
There will also be no editor marks if you edit a file whilst FCC is collecting coverage.
9090

SharedProject/Editor/DynamicCoverage/Management/BufferLineCoverage.cs

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ internal class BufferLineCoverage :
2121
private readonly IAppOptionsProvider appOptionsProvider;
2222
private readonly ILogger logger;
2323
private readonly ITextBuffer2 textBuffer;
24-
private ITrackedLines trackedLines;
2524
private bool? editorCoverageModeOff;
2625
private IFileLineCoverage fileLineCoverage;
2726
private Nullable<DateTime> lastChanged;
28-
private DateTime lastTestExecutionStarting;
27+
private DateTime lastTestExecutionStarting;
28+
29+
public ITrackedLines TrackedLines { get; set; }
30+
31+
internal enum SerializedCoverageState
32+
{
33+
NotSerialized, OutOfDate, Ok
34+
}
35+
2936
public BufferLineCoverage(
30-
IFileLineCoverage fileLineCoverage,
37+
ILastCoverage lastCoverage,
3138
ITextInfo textInfo,
3239
IEventAggregator eventAggregator,
3340
ITrackedLinesFactory trackedLinesFactory,
@@ -36,7 +43,12 @@ public BufferLineCoverage(
3643
ILogger logger
3744
)
3845
{
39-
this.fileLineCoverage = fileLineCoverage;
46+
if (lastCoverage != null)
47+
{
48+
this.fileLineCoverage = lastCoverage.FileLineCoverage;
49+
this.lastTestExecutionStarting = lastCoverage.TestExecutionStartingDate;
50+
}
51+
4052
this.textBuffer = textInfo.TextBuffer;
4153
this.textInfo = textInfo;
4254
this.eventAggregator = eventAggregator;
@@ -47,17 +59,16 @@ ILogger logger
4759
void AppOptionsChanged(IAppOptions appOptions)
4860
{
4961
bool newEditorCoverageModeOff = appOptions.EditorCoverageColouringMode == EditorCoverageColouringMode.Off;
50-
if (this.trackedLines != null && newEditorCoverageModeOff && this.editorCoverageModeOff != newEditorCoverageModeOff)
62+
this.editorCoverageModeOff = newEditorCoverageModeOff;
63+
if (this.TrackedLines != null && newEditorCoverageModeOff)
5164
{
52-
this.trackedLines = null;
65+
this.TrackedLines = null;
5366
this.SendCoverageChangedMessage();
5467
}
55-
56-
this.editorCoverageModeOff = newEditorCoverageModeOff;
5768
}
5869

5970
appOptionsProvider.OptionsChanged += AppOptionsChanged;
60-
if (fileLineCoverage != null)
71+
if (this.fileLineCoverage != null)
6172
{
6273
this.CreateTrackedLinesIfRequired(true);
6374
}
@@ -66,7 +77,7 @@ void AppOptionsChanged(IAppOptions appOptions)
6677
this.textBuffer.ChangedOnBackground += this.TextBuffer_ChangedOnBackground;
6778
void textViewClosedHandler(object s, EventArgs e)
6879
{
69-
this.UpdateDynamicCoverageStore((s as ITextView).TextSnapshot.GetText());
80+
this.UpdateDynamicCoverageStore((s as ITextView).TextSnapshot);
7081
this.textBuffer.Changed -= this.TextBuffer_ChangedOnBackground;
7182
textInfo.TextView.Closed -= textViewClosedHandler;
7283
appOptionsProvider.OptionsChanged -= AppOptionsChanged;
@@ -76,26 +87,40 @@ void textViewClosedHandler(object s, EventArgs e)
7687
textInfo.TextView.Closed += textViewClosedHandler;
7788
}
7889

79-
private void UpdateDynamicCoverageStore(string text)
90+
private void UpdateDynamicCoverageStore(ITextSnapshot textSnapshot)
8091
{
81-
if (this.trackedLines != null)
92+
if (this.TrackedLines != null)
8293
{
83-
this.dynamicCoverageStore.SaveSerializedCoverage(
84-
this.textInfo.FilePath,
85-
this.trackedLinesFactory.Serialize(this.trackedLines, text)
86-
);
94+
string snapshotText = textSnapshot.GetText();
95+
if (this.FileSystemReflectsTrackedLines(snapshotText))
96+
{
97+
// this only applies to the last coverage run.
98+
// the DynamicCoverageStore ensures this is removed when next coverage is run
99+
this.dynamicCoverageStore.SaveSerializedCoverage(
100+
this.textInfo.FilePath,
101+
this.trackedLinesFactory.Serialize(this.TrackedLines, snapshotText)
102+
);
103+
}
104+
else
105+
{
106+
this.dynamicCoverageStore.RemoveSerializedCoverage(this.textInfo.FilePath);
107+
}
87108
}
88109
else
89110
{
90111
this.dynamicCoverageStore.RemoveSerializedCoverage(this.textInfo.FilePath);
91112
}
92113
}
93114

115+
//todo - behaviour if exception reading text
116+
private bool FileSystemReflectsTrackedLines(string snapshotText)
117+
=> this.textInfo.GetFileText() == snapshotText;
118+
94119
private void CreateTrackedLinesIfRequired(bool initial)
95120
{
96121
if (this.EditorCoverageColouringModeOff())
97122
{
98-
this.trackedLines = null;
123+
this.TrackedLines = null;
99124
}
100125
else
101126
{
@@ -117,52 +142,90 @@ private void TryCreateTrackedLines(bool initial)
117142

118143
private void CreateTrackedLinesIfRequiredWithMessage()
119144
{
120-
bool hadTrackedLines = this.trackedLines != null;
145+
bool hadTrackedLines = this.TrackedLines != null;
121146
if (!this.lastChanged.HasValue || this.lastChanged < this.lastTestExecutionStarting)
122147
{
123148
this.CreateTrackedLinesIfRequired(false);
124149
}
125150
else
126151
{
127152
this.logger.Log($"Not creating editor marks for {this.textInfo.FilePath} as it was changed after test execution started");
128-
this.trackedLines = null;
153+
this.TrackedLines = null;
129154
}
130155

131-
bool hasTrackedLines = this.trackedLines != null;
156+
bool hasTrackedLines = this.TrackedLines != null;
132157
if (hadTrackedLines || hasTrackedLines)
133158
{
134159
this.SendCoverageChangedMessage();
135160
}
136161
}
137162

163+
private (SerializedCoverageState,string) GetSerializedCoverageInfo(SerializedCoverageWhen serializedCoverageWhen)
164+
{
165+
DateTime lastWriteTime = this.textInfo.GetLastWriteTime();
166+
167+
168+
if (serializedCoverageWhen == null)
169+
{
170+
SerializedCoverageState state = lastWriteTime > this.lastTestExecutionStarting ?
171+
SerializedCoverageState.OutOfDate :
172+
SerializedCoverageState.NotSerialized;
173+
return (state, null);
174+
}
175+
176+
/*
177+
If there is a When then it applies to the current coverage run ( as DynamicCoverageStore removes )
178+
as When is written when the text view is closed it is always - LastWriteTime < When
179+
*/
180+
return serializedCoverageWhen.When < lastWriteTime
181+
? ((SerializedCoverageState, string))(SerializedCoverageState.OutOfDate, null)
182+
: (SerializedCoverageState.Ok, serializedCoverageWhen.Serialized);
183+
}
184+
138185
private void CreateTrackedLines(bool initial)
139186
{
140187
string filePath = this.textInfo.FilePath;
141188
ITextSnapshot currentSnapshot = this.textBuffer.CurrentSnapshot;
142189
if (initial)
143190
{
144-
string serializedCoverage = this.dynamicCoverageStore.GetSerializedCoverage(filePath);
145-
if (serializedCoverage != null)
191+
SerializedCoverageWhen serializedCoverageWhen = this.dynamicCoverageStore.GetSerializedCoverage(
192+
filePath
193+
);
194+
(SerializedCoverageState state, string serializedCoverage) = this.GetSerializedCoverageInfo(serializedCoverageWhen);
195+
switch (state)
146196
{
147-
this.trackedLines = this.trackedLinesFactory.Create(serializedCoverage, currentSnapshot, filePath);
148-
return;
197+
case SerializedCoverageState.NotSerialized:
198+
break;
199+
case SerializedCoverageState.Ok:
200+
this.TrackedLines = this.trackedLinesFactory.Create(
201+
serializedCoverage, currentSnapshot, filePath);
202+
return;
203+
default: // Out of date
204+
this.logger.Log($"Not creating editor marks for {this.textInfo.FilePath} as coverage is out of date");
205+
return;
149206
}
150207
}
151208

152209
var lines = this.fileLineCoverage.GetLines(this.textInfo.FilePath).ToList();
153-
this.trackedLines = this.trackedLinesFactory.Create(lines, currentSnapshot, filePath);
210+
this.TrackedLines = this.trackedLinesFactory.Create(lines, currentSnapshot, filePath);
154211
}
155212

156213
private bool EditorCoverageColouringModeOff()
157214
{
215+
// as handling the event do not need to check the value again
216+
if (this.editorCoverageModeOff.HasValue)
217+
{
218+
return this.editorCoverageModeOff.Value;
219+
}
220+
158221
this.editorCoverageModeOff = this.appOptionsProvider.Get().EditorCoverageColouringMode == EditorCoverageColouringMode.Off;
159222
return this.editorCoverageModeOff.Value;
160223
}
161224

162225
private void TextBuffer_ChangedOnBackground(object sender, TextContentChangedEventArgs textContentChangedEventArgs)
163226
{
164227
this.lastChanged = DateTime.Now;
165-
if (this.trackedLines != null)
228+
if (this.TrackedLines != null)
166229
{
167230
this.TryUpdateTrackedLines(textContentChangedEventArgs);
168231
}
@@ -182,7 +245,7 @@ private void TryUpdateTrackedLines(TextContentChangedEventArgs textContentChange
182245

183246
private void UpdateTrackedLines(TextContentChangedEventArgs textContentChangedEventArgs)
184247
{
185-
IEnumerable<int> changedLineNumbers = this.trackedLines.GetChangedLineNumbers(textContentChangedEventArgs.After, textContentChangedEventArgs.Changes.Select(change => change.NewSpan).ToList())
248+
IEnumerable<int> changedLineNumbers = this.TrackedLines.GetChangedLineNumbers(textContentChangedEventArgs.After, textContentChangedEventArgs.Changes.Select(change => change.NewSpan).ToList())
186249
.Where(changedLine => changedLine >= 0 && changedLine < textContentChangedEventArgs.After.LineCount);
187250
this.SendCoverageChangedMessageIfChanged(changedLineNumbers);
188251
}
@@ -199,16 +262,16 @@ private void SendCoverageChangedMessage(IEnumerable<int> changedLineNumbers = nu
199262
=> this.eventAggregator.SendMessage(new CoverageChangedMessage(this, this.textInfo.FilePath, changedLineNumbers));
200263

201264
public IEnumerable<IDynamicLine> GetLines(int startLineNumber, int endLineNumber)
202-
=> this.trackedLines == null ? Enumerable.Empty<IDynamicLine>() : this.trackedLines.GetLines(startLineNumber, endLineNumber);
265+
=> this.TrackedLines == null ? Enumerable.Empty<IDynamicLine>() : this.TrackedLines.GetLines(startLineNumber, endLineNumber);
203266

204267
public void Handle(NewCoverageLinesMessage message)
205268
{
206269
this.fileLineCoverage = message.CoverageLines;
207270

208-
bool hadTrackedLines = this.trackedLines != null;
271+
bool hadTrackedLines = this.TrackedLines != null;
209272
if (this.fileLineCoverage == null)
210273
{
211-
this.trackedLines = null;
274+
this.TrackedLines = null;
212275
if (hadTrackedLines)
213276
{
214277
this.SendCoverageChangedMessage();

SharedProject/Editor/DynamicCoverage/Management/BufferLineCoverageFactory.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Diagnostics.CodeAnalysis;
33
using FineCodeCoverage.Core.Utilities;
44
using FineCodeCoverage.Engine.Model;
5+
using FineCodeCoverage.Impl;
56
using FineCodeCoverage.Options;
67

78
namespace FineCodeCoverage.Editor.DynamicCoverage
@@ -27,12 +28,12 @@ ILogger logger
2728
}
2829

2930
public IBufferLineCoverage Create(
30-
IFileLineCoverage fileLineCoverage,
31+
LastCoverage lastCoverage,
3132
ITextInfo textInfo,
3233
IEventAggregator eventAggregator,
3334
ITrackedLinesFactory trackedLinesFactory
3435
) => new BufferLineCoverage(
35-
fileLineCoverage,
36+
lastCoverage,
3637
textInfo,
3738
eventAggregator,
3839
trackedLinesFactory,

0 commit comments

Comments
 (0)