Skip to content

Commit

Permalink
Fingers crossed.
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyhallett committed May 10, 2024
1 parent e150811 commit 6d9a114
Show file tree
Hide file tree
Showing 19 changed files with 621 additions and 423 deletions.
719 changes: 358 additions & 361 deletions FineCodeCoverageTests/Editor/DynamicCoverage/BufferLineCoverage_Tests.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using AutoMoq;
using System;
using AutoMoq;
using FineCodeCoverage.Core.Utilities;
using FineCodeCoverage.Editor.DynamicCoverage;
using FineCodeCoverage.Editor.DynamicCoverage.Utilities;
using FineCodeCoverage.Engine;
using FineCodeCoverage.Engine.Model;
using FineCodeCoverage.Impl;
using FineCodeCoverageTests.Test_helpers;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
Expand Down Expand Up @@ -51,14 +54,20 @@ public void Manage_Should_Create_Singleton_IBufferLineCoverage()
public void Manage_Should_Create_Singleton_IBufferLineCoverage_With_Last_Coverage_And_Dependencies(bool hasLastCoverage)
{
var autoMocker = new AutoMoqer();

var now = new DateTime();
autoMocker.GetMock<IDateTimeService>().Setup(dateTimeService => dateTimeService.Now).Returns(now);

var eventAggregator = autoMocker.GetMock<IEventAggregator>().Object;
var trackedLinesFactory = autoMocker.GetMock<ITrackedLinesFactory>().Object;
var dynamicCoverageManager = autoMocker.Create<DynamicCoverageManager>();
IFileLineCoverage lastCoverage = null;
LastCoverage lastCoverage = null;
if (hasLastCoverage)
{
lastCoverage = new Mock<IFileLineCoverage>().Object;
dynamicCoverageManager.Handle(new NewCoverageLinesMessage { CoverageLines = lastCoverage});
var fileLineCoverage = new Mock<IFileLineCoverage>().Object;
lastCoverage = new LastCoverage(fileLineCoverage, now);
(dynamicCoverageManager as IListener<TestExecutionStartingMessage>).Handle(new TestExecutionStartingMessage());
dynamicCoverageManager.Handle(new NewCoverageLinesMessage { CoverageLines = fileLineCoverage});
}

var mockTextInfo = new Mock<ITextInfo>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AutoMoq;
using FineCodeCoverage.Core.Utilities;
using FineCodeCoverage.Editor.DynamicCoverage;
using FineCodeCoverage.Editor.DynamicCoverage.Utilities;
using FineCodeCoverage.Engine;
using FineCodeCoverage.Options;
using Microsoft.VisualStudio.Settings;
Expand Down Expand Up @@ -43,14 +44,20 @@ public void Should_Delete_WritableUserSettingsStore_Collection_When_NewCoverageL
public void Should_SaveSerializedCoverage_To_The_Store_Creating_Collection_If_Does_Not_Exist(bool collectionExists)
{
var autoMoqer = new AutoMoqer();
var mockDateTimeService = autoMoqer.GetMock<IDateTimeService>();
var now = DateTime.Now;
mockDateTimeService.SetupGet(dateTimeService => dateTimeService.Now).Returns(now);
var mockJsonConvertService = autoMoqer.GetMock<IJsonConvertService>();
mockJsonConvertService.Setup(jsonConvertService => jsonConvertService.SerializeObject(
new SerializedCoverageWhen { Serialized = "serialized coverage", When = now })).Returns("serialized");
var mockWritableSettingsStore = new Mock<WritableSettingsStore>();
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.CollectionExists("FCC.DynamicCoverageStore")).Returns(collectionExists);
autoMoqer.Setup<IWritableUserSettingsStoreProvider, WritableSettingsStore>(
writableUserSettingsStoreProvider => writableUserSettingsStoreProvider.Provide()).Returns(mockWritableSettingsStore.Object);

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

dynamicCoverageStore.SaveSerializedCoverage("filePath", "serialized");
dynamicCoverageStore.SaveSerializedCoverage("filePath", "serialized coverage");

mockWritableSettingsStore.Verify(writableSettingsStore => writableSettingsStore.CreateCollection("FCC.DynamicCoverageStore"), Times.Exactly(collectionExists ? 0 : 1));
mockWritableSettingsStore.Verify(writableSettingsStore => writableSettingsStore.SetString("FCC.DynamicCoverageStore", "filePath", "serialized"), Times.Once);
Expand All @@ -76,18 +83,32 @@ public void Should_Return_Null_For_GetSerializedCoverage_When_Collection_Does_No
public void Should_Return_From_Collection_When_Property_Exists(bool propertyExists)
{
var autoMoqer = new AutoMoqer();

var mockWritableSettingsStore = new Mock<WritableSettingsStore>();
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.CollectionExists("FCC.DynamicCoverageStore")).Returns(true);
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.PropertyExists("FCC.DynamicCoverageStore", "filePath")).Returns(propertyExists);
mockWritableSettingsStore.Setup(writableSettingsStore => writableSettingsStore.GetString("FCC.DynamicCoverageStore", "filePath")).Returns("serialized");
autoMoqer.Setup<IWritableUserSettingsStoreProvider, WritableSettingsStore>(
writableUserSettingsStoreProvider => writableUserSettingsStoreProvider.Provide()).Returns(mockWritableSettingsStore.Object);

var deserializedCoverageWhen = new SerializedCoverageWhen { When = DateTime.Now, Serialized = "serializedCoverage coverage" };
var mockJsonConvertService = autoMoqer.GetMock<IJsonConvertService>();
mockJsonConvertService.Setup(jsonConvertService => jsonConvertService.DeserializeObject<SerializedCoverageWhen>("serialized"))
.Returns(deserializedCoverageWhen);

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

var serializedCoverage = dynamicCoverageStore.GetSerializedCoverage("filePath");
var serializedCoverageWhen = dynamicCoverageStore.GetSerializedCoverage("filePath");

Assert.AreEqual(propertyExists ? "serialized" : null, serializedCoverage);
if (propertyExists)
{
Assert.AreSame(deserializedCoverageWhen, serializedCoverageWhen);
}
else
{
Assert.Null(serializedCoverageWhen);

}
}

private void FileRename(
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ If you switch to one of the EditorCoverageColouringMode options then you will ne
For Blazor components with @code blocks coverage lines can be generated outside these regions.
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.

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.
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..

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

Expand Down
123 changes: 93 additions & 30 deletions SharedProject/Editor/DynamicCoverage/Management/BufferLineCoverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@ internal class BufferLineCoverage :
private readonly IAppOptionsProvider appOptionsProvider;
private readonly ILogger logger;
private readonly ITextBuffer2 textBuffer;
private ITrackedLines trackedLines;
private bool? editorCoverageModeOff;
private IFileLineCoverage fileLineCoverage;
private Nullable<DateTime> lastChanged;
private DateTime lastTestExecutionStarting;
private DateTime lastTestExecutionStarting;

public ITrackedLines TrackedLines { get; set; }

internal enum SerializedCoverageState
{
NotSerialized, OutOfDate, Ok
}

public BufferLineCoverage(
IFileLineCoverage fileLineCoverage,
ILastCoverage lastCoverage,
ITextInfo textInfo,
IEventAggregator eventAggregator,
ITrackedLinesFactory trackedLinesFactory,
Expand All @@ -36,7 +43,12 @@ public BufferLineCoverage(
ILogger logger
)
{
this.fileLineCoverage = fileLineCoverage;
if (lastCoverage != null)
{
this.fileLineCoverage = lastCoverage.FileLineCoverage;
this.lastTestExecutionStarting = lastCoverage.TestExecutionStartingDate;
}

this.textBuffer = textInfo.TextBuffer;
this.textInfo = textInfo;
this.eventAggregator = eventAggregator;
Expand All @@ -47,17 +59,16 @@ ILogger logger
void AppOptionsChanged(IAppOptions appOptions)
{
bool newEditorCoverageModeOff = appOptions.EditorCoverageColouringMode == EditorCoverageColouringMode.Off;
if (this.trackedLines != null && newEditorCoverageModeOff && this.editorCoverageModeOff != newEditorCoverageModeOff)
this.editorCoverageModeOff = newEditorCoverageModeOff;
if (this.TrackedLines != null && newEditorCoverageModeOff)
{
this.trackedLines = null;
this.TrackedLines = null;
this.SendCoverageChangedMessage();
}

this.editorCoverageModeOff = newEditorCoverageModeOff;
}

appOptionsProvider.OptionsChanged += AppOptionsChanged;
if (fileLineCoverage != null)
if (this.fileLineCoverage != null)
{
this.CreateTrackedLinesIfRequired(true);
}
Expand All @@ -66,7 +77,7 @@ void AppOptionsChanged(IAppOptions appOptions)
this.textBuffer.ChangedOnBackground += this.TextBuffer_ChangedOnBackground;
void textViewClosedHandler(object s, EventArgs e)
{
this.UpdateDynamicCoverageStore((s as ITextView).TextSnapshot.GetText());
this.UpdateDynamicCoverageStore((s as ITextView).TextSnapshot);
this.textBuffer.Changed -= this.TextBuffer_ChangedOnBackground;
textInfo.TextView.Closed -= textViewClosedHandler;
appOptionsProvider.OptionsChanged -= AppOptionsChanged;
Expand All @@ -76,26 +87,40 @@ void textViewClosedHandler(object s, EventArgs e)
textInfo.TextView.Closed += textViewClosedHandler;
}

private void UpdateDynamicCoverageStore(string text)
private void UpdateDynamicCoverageStore(ITextSnapshot textSnapshot)
{
if (this.trackedLines != null)
if (this.TrackedLines != null)
{
this.dynamicCoverageStore.SaveSerializedCoverage(
this.textInfo.FilePath,
this.trackedLinesFactory.Serialize(this.trackedLines, text)
);
string snapshotText = textSnapshot.GetText();
if (this.FileSystemReflectsTrackedLines(snapshotText))
{
// this only applies to the last coverage run.
// the DynamicCoverageStore ensures this is removed when next coverage is run
this.dynamicCoverageStore.SaveSerializedCoverage(
this.textInfo.FilePath,
this.trackedLinesFactory.Serialize(this.TrackedLines, snapshotText)
);
}
else
{
this.dynamicCoverageStore.RemoveSerializedCoverage(this.textInfo.FilePath);
}
}
else
{
this.dynamicCoverageStore.RemoveSerializedCoverage(this.textInfo.FilePath);
}
}

//todo - behaviour if exception reading text
private bool FileSystemReflectsTrackedLines(string snapshotText)
=> this.textInfo.GetFileText() == snapshotText;

private void CreateTrackedLinesIfRequired(bool initial)
{
if (this.EditorCoverageColouringModeOff())
{
this.trackedLines = null;
this.TrackedLines = null;
}
else
{
Expand All @@ -117,52 +142,90 @@ private void TryCreateTrackedLines(bool initial)

private void CreateTrackedLinesIfRequiredWithMessage()
{
bool hadTrackedLines = this.trackedLines != null;
bool hadTrackedLines = this.TrackedLines != null;
if (!this.lastChanged.HasValue || this.lastChanged < this.lastTestExecutionStarting)
{
this.CreateTrackedLinesIfRequired(false);
}
else
{
this.logger.Log($"Not creating editor marks for {this.textInfo.FilePath} as it was changed after test execution started");
this.trackedLines = null;
this.TrackedLines = null;
}

bool hasTrackedLines = this.trackedLines != null;
bool hasTrackedLines = this.TrackedLines != null;
if (hadTrackedLines || hasTrackedLines)
{
this.SendCoverageChangedMessage();
}
}

private (SerializedCoverageState,string) GetSerializedCoverageInfo(SerializedCoverageWhen serializedCoverageWhen)
{
DateTime lastWriteTime = this.textInfo.GetLastWriteTime();


if (serializedCoverageWhen == null)
{
SerializedCoverageState state = lastWriteTime > this.lastTestExecutionStarting ?
SerializedCoverageState.OutOfDate :
SerializedCoverageState.NotSerialized;
return (state, null);
}

/*
If there is a When then it applies to the current coverage run ( as DynamicCoverageStore removes )
as When is written when the text view is closed it is always - LastWriteTime < When
*/
return serializedCoverageWhen.When < lastWriteTime
? ((SerializedCoverageState, string))(SerializedCoverageState.OutOfDate, null)
: (SerializedCoverageState.Ok, serializedCoverageWhen.Serialized);
}

private void CreateTrackedLines(bool initial)
{
string filePath = this.textInfo.FilePath;
ITextSnapshot currentSnapshot = this.textBuffer.CurrentSnapshot;
if (initial)
{
string serializedCoverage = this.dynamicCoverageStore.GetSerializedCoverage(filePath);
if (serializedCoverage != null)
SerializedCoverageWhen serializedCoverageWhen = this.dynamicCoverageStore.GetSerializedCoverage(
filePath
);
(SerializedCoverageState state, string serializedCoverage) = this.GetSerializedCoverageInfo(serializedCoverageWhen);
switch (state)
{
this.trackedLines = this.trackedLinesFactory.Create(serializedCoverage, currentSnapshot, filePath);
return;
case SerializedCoverageState.NotSerialized:
break;
case SerializedCoverageState.Ok:
this.TrackedLines = this.trackedLinesFactory.Create(
serializedCoverage, currentSnapshot, filePath);
return;
default: // Out of date
this.logger.Log($"Not creating editor marks for {this.textInfo.FilePath} as coverage is out of date");
return;
}
}

var lines = this.fileLineCoverage.GetLines(this.textInfo.FilePath).ToList();
this.trackedLines = this.trackedLinesFactory.Create(lines, currentSnapshot, filePath);
this.TrackedLines = this.trackedLinesFactory.Create(lines, currentSnapshot, filePath);
}

private bool EditorCoverageColouringModeOff()
{
// as handling the event do not need to check the value again
if (this.editorCoverageModeOff.HasValue)
{
return this.editorCoverageModeOff.Value;
}

this.editorCoverageModeOff = this.appOptionsProvider.Get().EditorCoverageColouringMode == EditorCoverageColouringMode.Off;
return this.editorCoverageModeOff.Value;
}

private void TextBuffer_ChangedOnBackground(object sender, TextContentChangedEventArgs textContentChangedEventArgs)
{
this.lastChanged = DateTime.Now;
if (this.trackedLines != null)
if (this.TrackedLines != null)
{
this.TryUpdateTrackedLines(textContentChangedEventArgs);
}
Expand All @@ -182,7 +245,7 @@ private void TryUpdateTrackedLines(TextContentChangedEventArgs textContentChange

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

public IEnumerable<IDynamicLine> GetLines(int startLineNumber, int endLineNumber)
=> this.trackedLines == null ? Enumerable.Empty<IDynamicLine>() : this.trackedLines.GetLines(startLineNumber, endLineNumber);
=> this.TrackedLines == null ? Enumerable.Empty<IDynamicLine>() : this.TrackedLines.GetLines(startLineNumber, endLineNumber);

public void Handle(NewCoverageLinesMessage message)
{
this.fileLineCoverage = message.CoverageLines;

bool hadTrackedLines = this.trackedLines != null;
bool hadTrackedLines = this.TrackedLines != null;
if (this.fileLineCoverage == null)
{
this.trackedLines = null;
this.TrackedLines = null;
if (hadTrackedLines)
{
this.SendCoverageChangedMessage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using FineCodeCoverage.Core.Utilities;
using FineCodeCoverage.Engine.Model;
using FineCodeCoverage.Impl;
using FineCodeCoverage.Options;

namespace FineCodeCoverage.Editor.DynamicCoverage
Expand All @@ -27,12 +28,12 @@ ILogger logger
}

public IBufferLineCoverage Create(
IFileLineCoverage fileLineCoverage,
LastCoverage lastCoverage,
ITextInfo textInfo,
IEventAggregator eventAggregator,
ITrackedLinesFactory trackedLinesFactory
) => new BufferLineCoverage(
fileLineCoverage,
lastCoverage,
textInfo,
eventAggregator,
trackedLinesFactory,
Expand Down
Loading

0 comments on commit 6d9a114

Please sign in to comment.