diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/DotNetOpenAuth.Test/CoordinatorBase.cs | 2 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs | 91 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs | 113 |
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))); + } + } + } +} |