-
Notifications
You must be signed in to change notification settings - Fork 269
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Store information about last call in thread local storage
NSubstitute relies on information about last call to set return value. In order to better support concurrent usage of substitution during its configuration implementation was switched to use thread local storage for last call info. The following changes were made: - PendingSpecification is now stored in ThreadLocal context. - PendingSpecification stores both pending specification and information about the last call. - CallStack was reworked to be a CallCollection. - Add new kind of exception to indicate that last router is known, but information about last call is missing.
- 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.