Skip to content

Commit 599243f

Browse files
authored
Merge pull request #5273 from MDoerner/FixesAroundDeadlocks
Fixes around deadlocks
2 parents 2ccff5a + d53c9a6 commit 599243f

File tree

4 files changed

+59
-13
lines changed

4 files changed

+59
-13
lines changed

Rubberduck.CodeAnalysis/Inspections/Results/IdentifierReferenceInspectionResult.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public IdentifierReferenceInspectionResult(IInspection inspection, string descri
3434

3535
public override bool ChangesInvalidateResult(ICollection<QualifiedModuleName> modifiedModules)
3636
{
37-
return modifiedModules.Contains(Target.QualifiedModuleName)
38-
|| base.ChangesInvalidateResult(modifiedModules);
37+
return Target != null && modifiedModules.Contains(Target.QualifiedModuleName)
38+
|| base.ChangesInvalidateResult(modifiedModules);
3939
}
4040
}
4141
}

Rubberduck.Core/UI/Command/RunSelectedTestMethodCommand.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ private Declaration FindDeclarationFromSelection()
5050

5151
private bool IsTestMethod(Declaration member)
5252
{
53-
return member.DeclarationType == DeclarationType.Procedure
53+
return member != null
54+
&& member.DeclarationType == DeclarationType.Procedure
5455
&& member.Annotations.Any(parseTreeAnnotation =>
5556
parseTreeAnnotation.Annotation is TestMethodAnnotation);
5657
}

Rubberduck.UnitTesting/UnitTesting/TestEngine.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Runtime.InteropServices;
6+
using System.Threading.Tasks;
67
using NLog;
78
using Rubberduck.Parsing.Annotations;
89
using Rubberduck.Parsing.Symbols;
@@ -18,10 +19,8 @@ namespace Rubberduck.UnitTesting
1819
// FIXME litter logging around here
1920
internal class TestEngine : ITestEngine
2021
{
21-
private static readonly ParserState[] AllowedRunStates =
22+
protected static readonly ParserState[] AllowedRunStates =
2223
{
23-
ParserState.ResolvedDeclarations,
24-
ParserState.ResolvingReferences,
2524
ParserState.Ready
2625
};
2726

@@ -172,13 +171,14 @@ public void RequestCancellation()
172171
CancellationRequested = true;
173172
}
174173

175-
private void RunInternal(IEnumerable<TestMethod> tests)
174+
protected virtual void RunInternal(IEnumerable<TestMethod> tests)
176175
{
177176
if (!CanRun)
178177
{
179178
return;
180179
}
181-
_state.OnSuspendParser(this, AllowedRunStates, () => RunWhileSuspended(tests));
180+
//We push the suspension to a background thread to avoid potential deadlocks if a parse is still running.
181+
Task.Run(() => _state.OnSuspendParser(this, AllowedRunStates, () => RunWhileSuspended(tests)));
182182
}
183183

184184
private void EnsureRubberduckIsReferencedForEarlyBoundTests()
@@ -199,7 +199,15 @@ private void EnsureRubberduckIsReferencedForEarlyBoundTests()
199199
}
200200
}
201201

202-
private void RunWhileSuspended(IEnumerable<TestMethod> tests)
202+
protected void RunWhileSuspended(IEnumerable<TestMethod> tests)
203+
{
204+
//Running the tests has to be done on the UI thread, so we push the task to it from within suspension of the parser.
205+
//We have to wait for the completion to make sure that the suspension only ends after tests have been completed.
206+
var testTask = _uiDispatcher.StartTask(() => RunWhileSuspendedOnUiThread(tests));
207+
testTask.Wait();
208+
}
209+
210+
private void RunWhileSuspendedOnUiThread(IEnumerable<TestMethod> tests)
203211
{
204212
var testMethods = tests as IList<TestMethod> ?? tests.ToList();
205213
if (!testMethods.Any())

RubberduckTests/UnitTesting/MockedTestEngine.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
44
using System.Linq;
5+
using System.Threading.Tasks;
56
using Moq;
67
using NUnit.Framework;
78
using Rubberduck.Parsing.UIContext;
@@ -47,8 +48,17 @@ internal class MockedTestEngine : IDisposable
4748

4849
private MockedTestEngine()
4950
{
50-
Dispatcher.Setup(d => d.InvokeAsync(It.IsAny<Action>())).Callback((Action action) => action.Invoke()).Verifiable();
51-
51+
Dispatcher.Setup(d => d.InvokeAsync(It.IsAny<Action>()))
52+
.Callback((Action action) => action.Invoke())
53+
.Verifiable();
54+
Dispatcher.Setup(d => d.StartTask(It.IsAny<Action>(), It.IsAny<TaskCreationOptions>()))
55+
.Returns((Action action, TaskCreationOptions options) =>
56+
{
57+
action.Invoke();
58+
return Task.CompletedTask;
59+
})
60+
.Verifiable();
61+
5262
TypeLib.Setup(tlm => tlm.Dispose()).Verifiable();
5363
WrapperProvider.Setup(p => p.TypeLibWrapperFromProject(It.IsAny<string>())).Returns(TypeLib.Object).Verifiable();
5464

@@ -64,7 +74,7 @@ public MockedTestEngine(string testModuleCode) : this()
6474

6575
Vbe = builder.Build();
6676
ParserState = MockParser.Create(Vbe.Object).State;
67-
TestEngine = new TestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
77+
TestEngine = new SynchronouslySuspendingTestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
6878
}
6979

7080
public MockedTestEngine(IReadOnlyList<string> moduleNames, IReadOnlyList<int> methodCounts) : this()
@@ -87,7 +97,7 @@ public MockedTestEngine(IReadOnlyList<string> moduleNames, IReadOnlyList<int> me
8797
project.AddProjectToVbeBuilder();
8898
Vbe = builder.Build();
8999
ParserState = MockParser.Create(Vbe.Object).State;
90-
TestEngine = new TestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
100+
TestEngine = new SynchronouslySuspendingTestEngine(ParserState, _fakesFactory.Object, VbeInteraction.Object, WrapperProvider.Object, Dispatcher.Object, Vbe.Object);
91101
}
92102

93103
public MockedTestEngine(int testMethodCount)
@@ -246,5 +256,32 @@ Public Sub TestCleanup()
246256
'this method runs after every test in the module.
247257
End Sub
248258
";
259+
260+
private class SynchronouslySuspendingTestEngine : TestEngine
261+
{
262+
private readonly RubberduckParserState _state;
263+
264+
public SynchronouslySuspendingTestEngine(
265+
RubberduckParserState state,
266+
IFakesFactory fakesFactory,
267+
IVBEInteraction declarationRunner,
268+
ITypeLibWrapperProvider wrapperProvider,
269+
IUiDispatcher uiDispatcher,
270+
IVBE vbe)
271+
: base(state, fakesFactory, declarationRunner, wrapperProvider, uiDispatcher, vbe)
272+
{
273+
_state = state;
274+
}
275+
276+
protected override void RunInternal(IEnumerable<TestMethod> tests)
277+
{
278+
if (!CanRun)
279+
{
280+
return;
281+
}
282+
//We have to do this on the same thread here to guarantee that the actions runs before the assert in the unit tests is called.
283+
_state.OnSuspendParser(this, AllowedRunStates, () => RunWhileSuspended(tests));
284+
}
285+
}
249286
}
250287
}

0 commit comments

Comments
 (0)