-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Track last call and spec in thread-safe manner #264
Rename ICallStack to ICallCollection. That is because the Pop() method is not required more (and was deleted), and Delete() method was added instead. That is much more collection that stack. IPendingSpecification now holds both information about previous call and pending specification. That is encapsulated to the PendingSpecificationInfo. The reasons why I added one more responsibility to this class are following: - We always either use pending specification or information about previous call. This data is mutually exclusive (we delete other one when store current one). Therefore, I decided that information about previous call is also a specification to some degree. - It's simpler to track sources of specifications because now we have a single place only. In my previous implementation we needed to clear pending specification and information about last call - more chances to fail somewhere. - Data is stored in SubstitutionContext, so we have single field instead of two (one for pending specification, second for last call information). PendingSpecificationInfo stores thread-local on SubstitutionContext (i.e. per substitute). GetCallSpec now uses IPendingSpecification only to resolve specification. Therefore, I renamed the FromLastCall method to the FromPendingSpecification because it better reflects the method implementation. TrackLastCallHandler introduced to set `IPendingSpecification`.
- Loading branch information
Showing
39 changed files
with
677 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,4 +15,5 @@ _ReSharper* | |
.DS_Store | ||
project.lock.json | ||
.vs/ | ||
.fake/ | ||
.fake/ | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
Source/NSubstitute.Acceptance.Specs/ConcurrencyTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System; | ||
using System.Threading; | ||
using NSubstitute.Acceptance.Specs.Infrastructure; | ||
using NUnit.Framework; | ||
|
||
namespace NSubstitute.Acceptance.Specs | ||
{ | ||
public class ConcurrencyTests | ||
{ | ||
[Test] | ||
public void Call_between_invocation_and_received_doesnt_cause_issue() | ||
{ | ||
//arrange | ||
var subs = Substitute.For<ISomething>(); | ||
|
||
var backgroundReady = new AutoResetEvent(false); | ||
|
||
//act | ||
var dummy = subs.Say("ping"); | ||
|
||
RunInOtherThread(() => | ||
{ | ||
subs.Echo(42); | ||
backgroundReady.Set(); | ||
}); | ||
|
||
backgroundReady.WaitOne(); | ||
|
||
dummy.Returns("pong"); | ||
|
||
//assert | ||
var actualResult = subs.Say("ping"); | ||
|
||
Assert.That(actualResult, Is.EqualTo("pong")); | ||
} | ||
|
||
[Test] | ||
public void Background_invocation_doesnt_delete_specification() | ||
{ | ||
//arrange | ||
var subs = Substitute.For<ISomething>(); | ||
|
||
var backgroundReady = new AutoResetEvent(false); | ||
|
||
//act | ||
var dummy = subs.Say(Arg.Any<string>()); | ||
|
||
RunInOtherThread(() => | ||
{ | ||
subs.Say("hello"); | ||
backgroundReady.Set(); | ||
}); | ||
|
||
backgroundReady.WaitOne(); | ||
dummy.Returns("42"); | ||
|
||
//assert | ||
Assert.That(subs.Say("Alex"), Is.EqualTo("42")); | ||
} | ||
|
||
[Test] | ||
public void Both_threads_can_configure_returns_concurrently() | ||
{ | ||
//arrange | ||
var subs = Substitute.For<ISomething>(); | ||
|
||
var foregroundReady = new AutoResetEvent(false); | ||
var backgroundReady = new AutoResetEvent(false); | ||
|
||
//act | ||
//1 | ||
var dummy = subs.Say("ping"); | ||
|
||
RunInOtherThread(() => | ||
{ | ||
//2 | ||
var d = subs.Echo(42); | ||
SignalAndWait(backgroundReady, foregroundReady); | ||
//4 | ||
d.Returns("42"); | ||
backgroundReady.Set(); | ||
}); | ||
|
||
backgroundReady.WaitOne(); | ||
|
||
//3 | ||
dummy.Returns("pong"); | ||
SignalAndWait(foregroundReady, backgroundReady); | ||
|
||
//assert | ||
Assert.That(subs.Say("ping"), Is.EqualTo("pong")); | ||
Assert.That(subs.Echo(42), Is.EqualTo("42")); | ||
} | ||
|
||
#if (NET45 || NET4 || NETSTANDARD1_5) | ||
[Test] | ||
public void Configuration_works_fine_for_async_methods() | ||
{ | ||
//arrange | ||
var subs = Substitute.For<ISomething>(); | ||
|
||
//act | ||
subs.EchoAsync(42).Returns("42"); | ||
|
||
//assert | ||
var result = subs.EchoAsync(42).Result; | ||
Assert.That(result, Is.EqualTo("42")); | ||
} | ||
#endif | ||
|
||
private static void RunInOtherThread(Action action) | ||
{ | ||
new Thread(action.Invoke) {IsBackground = true}.Start(); | ||
} | ||
|
||
private static void SignalAndWait(EventWaitHandle toSignal, EventWaitHandle toWait) | ||
{ | ||
toSignal.Set(); | ||
toWait.WaitOne(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using System; | ||
using NSubstitute.Core; | ||
using NSubstitute.Specs.Infrastructure; | ||
using NUnit.Framework; | ||
|
||
namespace NSubstitute.Specs | ||
{ | ||
public class CallCollectionSpecs : ConcernFor<CallCollection> | ||
{ | ||
public override CallCollection CreateSubjectUnderTest() => new CallCollection(); | ||
|
||
[Test] | ||
public void Should_add_call() | ||
{ | ||
//arrange | ||
var call = mock<ICall>(); | ||
|
||
//act | ||
sut.Add(call); | ||
|
||
//assert | ||
CollectionAssert.Contains(sut.AllCalls(), call); | ||
} | ||
|
||
[Test] | ||
public void Should_delete_call_when_deleted() | ||
{ | ||
//arrange | ||
var call = mock<ICall>(); | ||
|
||
//act | ||
sut.Add(call); | ||
sut.Delete(call); | ||
|
||
//assert | ||
CollectionAssert.DoesNotContain(sut.AllCalls(), call); | ||
} | ||
|
||
[Test] | ||
public void Should_fail_when_delete_nonexisting_call() | ||
{ | ||
//arrange | ||
var call = mock<ICall>(); | ||
|
||
//act/assert | ||
var exception = Assert.Throws<InvalidOperationException>(() => sut.Delete(call)); | ||
Assert.That(exception.Message, Is.StringContaining("Collection doesn't contain the call.")); | ||
} | ||
|
||
[Test] | ||
public void Should_delete_all_calls_on_clear() | ||
{ | ||
//arrange | ||
var call1 = mock<ICall>(); | ||
var call2 = mock<ICall>(); | ||
|
||
//act | ||
sut.Add(call1); | ||
sut.Add(call2); | ||
|
||
sut.Clear(); | ||
|
||
//assert | ||
CollectionAssert.IsEmpty(sut.AllCalls()); | ||
} | ||
|
||
[Test] | ||
public void Should_return_all_calls_in_the_order_they_were_received() | ||
{ | ||
//arrange | ||
var firstCall = mock<ICall>(); | ||
var secondCall = mock<ICall>(); | ||
|
||
//act | ||
sut.Add(firstCall); | ||
sut.Add(secondCall); | ||
|
||
//assert | ||
CollectionAssert.AreEqual(sut.AllCalls(), new[] { firstCall, secondCall }); | ||
} | ||
} | ||
} |
Oops, something went wrong.