summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/CoordinatorBase.cs2
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs91
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs113
3 files changed, 159 insertions, 47 deletions
diff --git a/src/DotNetOpenAuth.Test/CoordinatorBase.cs b/src/DotNetOpenAuth.Test/CoordinatorBase.cs
index 8efd46d..fdb2991 100644
--- a/src/DotNetOpenAuth.Test/CoordinatorBase.cs
+++ b/src/DotNetOpenAuth.Test/CoordinatorBase.cs
@@ -8,6 +8,8 @@ namespace DotNetOpenAuth.Test {
using System;
using System.Threading;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Test.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
internal abstract class CoordinatorBase<T1, T2> {
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
index 6fe5248..ff3f5a8 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
@@ -13,12 +13,64 @@ namespace DotNetOpenAuth.Test.Mocks {
using DotNetOpenAuth.Messaging;
internal class CoordinatingChannel : Channel {
+ /// <summary>
+ /// A lock to use when checking and setting the <see cref="waitingForMessage"/>
+ /// or the <see cref="simulationCompleted"/> fields.
+ /// </summary>
+ /// <remarks>
+ /// This is a static member so that all coordinating channels share a lock
+ /// since they peak at each others fields.
+ /// </remarks>
+ private static readonly object waitingForMessageCoordinationLock = new object();
+
+ /// <summary>
+ /// The original product channel whose behavior is being modified to work
+ /// better in automated testing.
+ /// </summary>
private Channel wrappedChannel;
+
+ /// <summary>
+ /// A flag set to true when this party in a two-party test has completed
+ /// its part of the testing.
+ /// </summary>
+ private bool simulationCompleted;
+
+ /// <summary>
+ /// A thread-coordinating signal that is set when another thread has a
+ /// message ready for this channel to receive.
+ /// </summary>
private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false);
+
+ /// <summary>
+ /// A flag used to indicate when this channel is waiting for a message
+ /// to arrive.
+ /// </summary>
+ private bool waitingForMessage;
+
+ /// <summary>
+ /// An incoming message that has been posted by a remote channel and
+ /// is waiting for receipt by this channel.
+ /// </summary>
private IProtocolMessage incomingMessage;
+
+ /// <summary>
+ /// A delegate that gets a chance to peak at and fiddle with all
+ /// incoming messages.
+ /// </summary>
private Action<IProtocolMessage> incomingMessageFilter;
+
+ /// <summary>
+ /// A delegate that gets a chance to peak at and fiddle with all
+ /// outgoing messages.
+ /// </summary>
private Action<IProtocolMessage> outgoingMessageFilter;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CoordinatingChannel"/> class.
+ /// </summary>
+ /// <param name="wrappedChannel">The wrapped channel. Must not be null.</param>
+ /// <param name="incomingMessageFilter">The incoming message filter. May be null.</param>
+ /// <param name="outgoingMessageFilter">The outgoing message filter. May be null.</param>
internal CoordinatingChannel(Channel wrappedChannel, Action<IProtocolMessage> incomingMessageFilter, Action<IProtocolMessage> outgoingMessageFilter)
: base(GetMessageFactory(wrappedChannel), wrappedChannel.BindingElements.ToArray()) {
ErrorUtilities.VerifyArgumentNotNull(wrappedChannel, "wrappedChannel");
@@ -37,6 +89,21 @@ namespace DotNetOpenAuth.Test.Mocks {
internal CoordinatingChannel RemoteChannel { get; set; }
/// <summary>
+ /// Indicates that the simulation that uses this channel has completed work.
+ /// </summary>
+ /// <remarks>
+ /// Calling this method is not strictly necessary, but it gives the channel
+ /// coordination a chance to recognize when another channel is left dangling
+ /// waiting for a message from another channel that may never come.
+ /// </remarks>
+ internal void Close() {
+ lock (waitingForMessageCoordinationLock) {
+ this.simulationCompleted = true;
+ ErrorUtilities.VerifyInternal(!this.RemoteChannel.waitingForMessage || this.RemoteChannel.incomingMessage != null, "This channel is shutting down, yet the remote channel is expecting a message to arrive from us that won't be coming!");
+ }
+ }
+
+ /// <summary>
/// Replays the specified message as if it were received again.
/// </summary>
/// <param name="message">The message to replay.</param>
@@ -142,7 +209,31 @@ namespace DotNetOpenAuth.Test.Mocks {
}
private IProtocolMessage AwaitIncomingMessage() {
+ // Special care should be taken so that we don't indefinitely
+ // wait for a message that may never come due to a bug in the product
+ // or the test.
+ // There are two scenarios that we need to watch out for:
+ // 1. Two channels are waiting to receive messages from each other.
+ // 2. One channel is waiting for a message that will never come because
+ // the remote party has already finished executing.
+ lock (waitingForMessageCoordinationLock) {
+ // It's possible that a message was just barely transmitted either to this
+ // or the remote channel. So it's ok for the remote channel to be waiting
+ // if either it or we are already about to receive a message.
+ ErrorUtilities.VerifyInternal(!this.RemoteChannel.waitingForMessage || this.RemoteChannel.incomingMessage != null || this.incomingMessage != null, "This channel is expecting an incoming message from another channel that is also blocked waiting for an incoming message from us!");
+
+ // It's permissible that the remote channel has already closed if it left a message
+ // for us already.
+ ErrorUtilities.VerifyInternal(!this.RemoteChannel.simulationCompleted || this.incomingMessage != null, "This channel is expecting an incoming message from another channel that has already been closed.");
+ this.waitingForMessage = true;
+ }
+
this.incomingMessageSignal.WaitOne();
+
+ lock (waitingForMessageCoordinationLock) {
+ this.waitingForMessage = false;
+ }
+
IProtocolMessage response = this.incomingMessage;
this.incomingMessage = null;
return response;
diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs
index 3b925a8..cdc648a 100644
--- a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs
@@ -1,47 +1,66 @@
-//-----------------------------------------------------------------------
-// <copyright file="OpenIdCoordinator.cs" company="Andrew Arnott">
-// Copyright (c) Andrew Arnott. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Test.OpenId {
- using System;
- using DotNetOpenAuth.Messaging.Bindings;
- using DotNetOpenAuth.OpenId;
- using DotNetOpenAuth.OpenId.Provider;
- using DotNetOpenAuth.OpenId.RelyingParty;
- using DotNetOpenAuth.Test.Mocks;
-
- internal class OpenIdCoordinator : CoordinatorBase<OpenIdRelyingParty, OpenIdProvider> {
- internal OpenIdCoordinator(Action<OpenIdRelyingParty> rpAction, Action<OpenIdProvider> opAction)
- : base(rpAction, opAction) {
- }
-
- internal OpenIdProvider Provider { get; set; }
-
- internal OpenIdRelyingParty RelyingParty { get; set; }
-
- internal override void Run() {
- this.EnsurePartiesAreInitialized();
- var rpCoordinatingChannel = new CoordinatingChannel(this.RelyingParty.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
- var opCoordinatingChannel = new CoordinatingChannel(this.Provider.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
- rpCoordinatingChannel.RemoteChannel = opCoordinatingChannel;
- opCoordinatingChannel.RemoteChannel = rpCoordinatingChannel;
-
- this.RelyingParty.Channel = rpCoordinatingChannel;
- this.Provider.Channel = opCoordinatingChannel;
-
- RunCore(this.RelyingParty, this.Provider);
- }
-
- private void EnsurePartiesAreInitialized() {
- if (this.RelyingParty == null) {
- this.RelyingParty = new OpenIdRelyingParty(new AssociationMemoryStore<Uri>(), new NonceMemoryStore(TimeSpan.FromHours(3)), new PrivateSecretMemoryStore());
- }
-
- if (this.Provider == null) {
- this.Provider = new OpenIdProvider(new AssociationMemoryStore<AssociationRelyingPartyType>(), new NonceMemoryStore(TimeSpan.FromHours(3)));
- }
- }
- }
-}
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdCoordinator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.OpenId {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Provider;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Test.Mocks;
+
+ internal class OpenIdCoordinator : CoordinatorBase<OpenIdRelyingParty, OpenIdProvider> {
+ internal OpenIdCoordinator(Action<OpenIdRelyingParty> rpAction, Action<OpenIdProvider> opAction)
+ : base(WrapAction(rpAction), WrapAction(opAction)) {
+ }
+
+ internal OpenIdProvider Provider { get; set; }
+
+ internal OpenIdRelyingParty RelyingParty { get; set; }
+
+ internal override void Run() {
+ this.EnsurePartiesAreInitialized();
+ var rpCoordinatingChannel = new CoordinatingChannel(this.RelyingParty.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
+ var opCoordinatingChannel = new CoordinatingChannel(this.Provider.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter);
+ rpCoordinatingChannel.RemoteChannel = opCoordinatingChannel;
+ opCoordinatingChannel.RemoteChannel = rpCoordinatingChannel;
+
+ this.RelyingParty.Channel = rpCoordinatingChannel;
+ this.Provider.Channel = opCoordinatingChannel;
+
+ RunCore(this.RelyingParty, this.Provider);
+ }
+
+ private static Action<OpenIdRelyingParty> WrapAction(Action<OpenIdRelyingParty> action) {
+ ErrorUtilities.VerifyArgumentNotNull(action, "action");
+
+ return rp => {
+ action(rp);
+ ((CoordinatingChannel)rp.Channel).Close();
+ };
+ }
+
+ private static Action<OpenIdProvider> WrapAction(Action<OpenIdProvider> action) {
+ ErrorUtilities.VerifyArgumentNotNull(action, "action");
+
+ return op => {
+ action(op);
+ ((CoordinatingChannel)op.Channel).Close();
+ };
+ }
+
+ private void EnsurePartiesAreInitialized() {
+ if (this.RelyingParty == null) {
+ this.RelyingParty = new OpenIdRelyingParty(new AssociationMemoryStore<Uri>(), new NonceMemoryStore(TimeSpan.FromHours(3)), new PrivateSecretMemoryStore());
+ }
+
+ if (this.Provider == null) {
+ this.Provider = new OpenIdProvider(new AssociationMemoryStore<AssociationRelyingPartyType>(), new NonceMemoryStore(TimeSpan.FromHours(3)));
+ }
+ }
+ }
+}