File: ExpectedExceptionTests.4.1.0.cs
Web Access
Project: src\src\System.Private.ServiceModel\tests\Scenarios\Client\ExpectedExceptions\Client.ExpectedExceptions.IntegrationTests.csproj (Client.ExpectedExceptions.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using Infrastructure.Common;
using System;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
 
public partial class ExpectedExceptionTests : ConditionalWcfTest
{
    [WcfFact]
    [OuterLoop]
    public static void NotExistentHost_Throws_EndpointNotFoundException()
    {
        string nonExistentHost = "http://nonexisthost/WcfService/WindowsCommunicationFoundation";
        ChannelFactory<IWcfService> factory = null;
        EndpointAddress endpointAddress = null;
        BasicHttpBinding binding = null;
        IWcfService serviceProxy = null;
 
 
        // *** VALIDATE *** \\
        EndpointNotFoundException exception = Assert.Throws<EndpointNotFoundException>(() =>
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding();
            binding.SendTimeout = TimeSpan.FromMilliseconds(40000);
            endpointAddress = new EndpointAddress(nonExistentHost);
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            try
            {
                serviceProxy.Echo("Hello");
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
        });
 
        // *** ADDITIONAL VALIDATION FOR NET NATIVE *** \\
        // On .Net Native retail, exception message is stripped to include only parameter
        Assert.True(exception.Message.Contains(nonExistentHost), string.Format("Expected exception message to contain: '{0}'\nThe exception message was: {1}", nonExistentHost, exception.Message));
    }
 
    [WcfFact]
    [OuterLoop]
    public static void ServiceRestart_Throws_CommunicationException()
    {
        // This test validates that if the Service were to shut-down, re-start or otherwise die in the
        // middle of an operation the client will not just hang. It should instead receive a CommunicationException.
        string restartServiceAddress = "";
        ChannelFactory<IWcfService> setupHostFactory = null;
        IWcfService setupHostServiceProxy = null;
        ChannelFactory<IWcfRestartService> factory = null;
        IWcfRestartService serviceProxy = null;
        BasicHttpBinding binding = new BasicHttpBinding();
 
        // *** Step 1 *** \\
        // We need the Service to create and open a ServiceHost and then give us the endpoint address for it.
        try
        {
            setupHostFactory = new ChannelFactory<IWcfService>(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic_Text));
            setupHostServiceProxy = setupHostFactory.CreateChannel();
            restartServiceAddress = setupHostServiceProxy.GetRestartServiceEndpoint();
 
            // *** CLEANUP *** \\
            ((ICommunicationObject)setupHostServiceProxy).Close();
            setupHostFactory.Close();
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)setupHostServiceProxy, setupHostFactory);
        }
 
        // *** Additional Setup *** \\
        // The restartServiceAddress we got from the Service used localhost as the host name.
        // We need the actual host name for the client call to work.
        // To make it easier to parse, localhost was replaced with '[HOST]'.
 
        // Use Endpoints.HttpBaseAddress_Basic only for the purpose of extracting the Service host name.
        // Then update 'restartServiceAddress' with it.
        string hostName = new Uri(Endpoints.HttpBaseAddress_Basic_Text).Host;
        restartServiceAddress = restartServiceAddress.Replace("[HOST]", hostName);
 
        // Get the last portion of the restart service url which is a Guid and convert it back to a Guid
        // This is needed by the RestartService operation as a Dictionary key to get the ServiceHost
        string uniqueIdentifier = restartServiceAddress.Substring(restartServiceAddress.LastIndexOf("/") + 1);
        Guid guid = new Guid(uniqueIdentifier);
 
        // *** Step 2 *** \\
        // Simple echo call to make sure the newly created endpoint is working.
        try
        {
            factory = new ChannelFactory<IWcfRestartService>(binding, new EndpointAddress(restartServiceAddress));
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            string result = serviceProxy.NonRestartService(guid);
 
            Assert.True(result == "Success!", string.Format("Test Case failed, expected the returned string to be: {0}, instead it was: {1}", "Success!", result));
        }
        catch (Exception ex)
        {
            string exceptionMessage = ex.Message;
            string innerExceptionMessage = ex.InnerException?.Message;
            string testExceptionMessage = $"The ping to validate the newly created endpoint failed.\nThe endpoint pinged was: {restartServiceAddress}\nThe GUID used to extract the ServiceHost from the server side dictionary was: {guid}";
            string fullExceptionMessage = $"testExceptionMessage: {testExceptionMessage}\nexceptionMessage: {exceptionMessage}\ninnerExceptionMessage: {innerExceptionMessage}";
            
            Assert.Fail(fullExceptionMessage);
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
 
        // *** Step 3 *** \\
        // The actual part of the test where the host is killed in the middle of the operation.
        // We expect the test should not hang and should receive a CommunicationException.
        CommunicationException exception = Assert.Throws<CommunicationException>(() =>
        {
            factory = new ChannelFactory<IWcfRestartService>(binding, new EndpointAddress(restartServiceAddress));
            serviceProxy = factory.CreateChannel();
 
            try
            {
                // *** EXECUTE *** \\
                serviceProxy.RestartService(guid);
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
        });
    }
 
    [WcfFact]
    [OuterLoop]
    public static void UnknownUrl_Throws_EndpointNotFoundException()
    {
        // We need a running service host at the other end but mangle the endpoint suffix
        string notFoundUrl = Endpoints.HttpBaseAddress_Basic_Text + "not-an-endpoint";
        BasicHttpBinding binding = null;
        ChannelFactory<IWcfService> factory = null;
        EndpointAddress endpointAddress = null;
        IWcfService serviceProxy = null;
 
        // *** VALIDATE *** \\
        EndpointNotFoundException exception = Assert.Throws<EndpointNotFoundException>(() =>
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding();
            binding.SendTimeout = TimeSpan.FromMilliseconds(10000);
            endpointAddress = new EndpointAddress(notFoundUrl);
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            try
            {
                serviceProxy.Echo("Hello");
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
 
        });
 
        // *** ADDITIONAL VALIDATION FOR NET NATIVE *** \\
        // On .Net Native retail, exception message is stripped to include only parameter
        Assert.True(exception.Message.Contains(notFoundUrl), string.Format("Expected exception message to contain: '{0}'\nThe exception message was:{1}", notFoundUrl, exception.Message));
    }
 
    [WcfFact]
    [OuterLoop]
    public static void UnknownUrl_Throws_ProtocolException()
    {
        string protocolExceptionUri = Endpoints.HttpProtocolError_Address;
        BasicHttpBinding binding = null;
        EndpointAddress endpointAddress = null;
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        // *** VALIDATE *** \\
        ProtocolException exception = Assert.Throws<ProtocolException>(() =>
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding();
            binding.SendTimeout = TimeSpan.FromMilliseconds(10000);
            endpointAddress = new EndpointAddress(protocolExceptionUri);
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            try
            {
                serviceProxy.Echo("Hello");
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
 
        });
 
        // *** ADDITIONAL VALIDATION FOR NET NATIVE *** \\
        // On .Net Native retail, exception message is stripped to include only parameter
        Assert.True(exception.Message.Contains(protocolExceptionUri), string.Format("Expected exception message to contain: '{0}'\nThe exception was: '{1}'", protocolExceptionUri, exception.Message));
    }
 
    [WcfFact]
    [OuterLoop]
    public static void DuplexCallback_Throws_FaultException_DirectThrow()
    {
        DuplexChannelFactory<IWcfDuplexTaskReturnService> factory = null;
        Guid guid = Guid.NewGuid();
        EndpointAddress endpointAddress = null;
        NetTcpBinding binding = null;
        DuplexTaskReturnServiceCallback callbackService = null;
        InstanceContext context = null;
        IWcfDuplexTaskReturnService serviceProxy = null;
 
        // *** VALIDATE *** \\
        FaultException<FaultDetail> exception = Assert.Throws<FaultException<FaultDetail>>(() =>
        {
            // *** SETUP *** \\
            binding = new NetTcpBinding();
            binding.Security.Mode = SecurityMode.None;
            callbackService = new DuplexTaskReturnServiceCallback(true);
            context = new InstanceContext(callbackService);
            endpointAddress = new EndpointAddress(Endpoints.Tcp_NoSecurity_TaskReturn_Address);
            factory = new DuplexChannelFactory<IWcfDuplexTaskReturnService>(context, binding, endpointAddress);
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            try
            {
                Task<Guid> task = serviceProxy.FaultPing(guid);
                if ((task as IAsyncResult).AsyncWaitHandle.WaitOne(ScenarioTestHelpers.TestTimeout))
                {
                    Guid returnedGuid = task.GetAwaiter().GetResult();
                }
                else
                {
                    throw new TimeoutException(String.Format("The call to the Service did not complete within the alloted time of: {0}", ScenarioTestHelpers.TestTimeout));
                }
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
        });
 
        // *** ADDITIONAL VALIDATION *** \\
        string exceptionCodeName = "ServicePingFaultCallback";
        string exceptionReason = "Reason: Testing FaultException returned from Duplex Callback";
 
        Assert.True(String.Equals(exceptionCodeName, exception.Code.Name), String.Format("Expected exception code name: {0}\nActual exception code name: {1}", exceptionCodeName, exception.Code.Name));
        Assert.True(String.Equals(exceptionReason, exception.Reason.GetMatchingTranslation().Text), String.Format("Expected exception reason: {0}\nActual exception reason: {1}", exceptionReason, exception.Reason.GetMatchingTranslation().Text));
    }
 
    [WcfFact]
    [OuterLoop]
    public static void DuplexCallback_Throws_FaultException_ReturnsFaultedTask()
    {
        DuplexChannelFactory<IWcfDuplexTaskReturnService> factory = null;
        Guid guid = Guid.NewGuid();
        NetTcpBinding binding = null;
        DuplexTaskReturnServiceCallback callbackService = null;
        InstanceContext context = null;
        EndpointAddress endpointAddress = null;
        IWcfDuplexTaskReturnService serviceProxy = null;
 
        // *** VALIDATE *** \\
        FaultException<FaultDetail> exception = Assert.Throws<FaultException<FaultDetail>>(() =>
        {
            // *** SETUP *** \\
            binding = new NetTcpBinding();
            binding.Security.Mode = SecurityMode.None;
            endpointAddress = new EndpointAddress(Endpoints.Tcp_NoSecurity_TaskReturn_Address);
            callbackService = new DuplexTaskReturnServiceCallback();
            context = new InstanceContext(callbackService);
            factory = new DuplexChannelFactory<IWcfDuplexTaskReturnService>(context, binding, endpointAddress);
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            try
            {
                Task<Guid> task = serviceProxy.FaultPing(guid);
                if ((task as IAsyncResult).AsyncWaitHandle.WaitOne(ScenarioTestHelpers.TestTimeout))
                {
                    Guid returnedGuid = task.GetAwaiter().GetResult();
                }
                else
                {
                    throw new TimeoutException(String.Format("The call to the Service did not complete within the alloted time of: {0}", ScenarioTestHelpers.TestTimeout));
                }
            }
            finally
            {
                // *** ENSURE CLEANUP *** \\
                ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
            }
 
        });
 
        // *** ADDITIONAL VALIDATION *** \\
        string exceptionCodeName = "ServicePingFaultCallback";
        string exceptionReason = "Reason: Testing FaultException returned from Duplex Callback";
 
        Assert.True(String.Equals(exceptionCodeName, exception.Code.Name), String.Format("Expected exception code name: {0}\nActual exception code name: {1}", exceptionCodeName, exception.Code.Name));
        Assert.True(String.Equals(exceptionReason, exception.Reason.GetMatchingTranslation().Text), String.Format("Expected exception reason: {0}\nActual exception reason: {1}", exceptionReason, exception.Reason.GetMatchingTranslation().Text));
    }
 
    [WcfFact]
    [OuterLoop]
    // Verify product throws MessageSecurityException when the Dns identity from the server does not match the expectation
    public static void TCP_ServiceCertExpired_Throw_MessageSecurityException()
    {
        string testString = "Hello";
        NetTcpBinding binding = null;
        EndpointAddress endpointAddress = null;
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
 
        // *** SETUP *** \\
        binding = new NetTcpBinding();
        binding.Security.Mode = SecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
        endpointAddress = new EndpointAddress(Endpoints.Tcp_ExpiredServerCertResource_Address);
        factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
        serviceProxy = factory.CreateChannel();
 
        // *** EXECUTE *** \\
        try
        {
            serviceProxy.Echo(testString);
            Assert.Fail("Expected: SecurityNegotiationException, Actual: no exception");
        }
        catch (CommunicationException exception)
        {
            // *** VALIDATE *** \\
            // Cannot explicitly catch a SecurityNegotiationException as it is not in the public contract.
            string exceptionType = exception.GetType().Name;
            if (exceptionType != "SecurityNegotiationException")
            {
                Assert.Fail(string.Format("Expected type SecurityNegotiationException, Actual: {0}", exceptionType));
            }
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
 
    [WcfFact]
    [OuterLoop]
    // Verify product throws SecurityNegotiationException when the service cert is revoked
    public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException()
    {
        string testString = "Hello";
        NetTcpBinding binding = null;
        EndpointAddress endpointAddress = null;
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        // *** SETUP *** \\
        binding = new NetTcpBinding();
        binding.Security.Mode = SecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
        endpointAddress = new EndpointAddress(new Uri(Endpoints.Tcp_RevokedServerCertResource_Address), new DnsEndpointIdentity(Endpoints.Tcp_RevokedServerCertResource_HostName));
        factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
        serviceProxy = factory.CreateChannel();
 
        // *** EXECUTE *** \\
        try
        {
            serviceProxy.Echo(testString);
            Assert.Fail("Expected: SecurityNegotiationException, Actual: no exception");
        }
        catch (CommunicationException exception)
        {
            // *** VALIDATION *** \\
            // Cannot explicitly catch a SecurityNegotiationException as it is not in the public contract.
            string exceptionType = exception.GetType().Name;
            if (exceptionType != "SecurityNegotiationException")
            {
                Assert.Fail(string.Format("Expected type SecurityNegotiationException, Actual: {0}", exceptionType));
            }
 
            Assert.True(exception.Message.Contains(Endpoints.Tcp_RevokedServerCertResource_HostName),
                                                    string.Format("Expected message contains {0}, actual message: {1}",
                                                    Endpoints.Tcp_RevokedServerCertResource_HostName,
                                                    exception.ToString()));
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
 
    [WcfFact]
    [OuterLoop]
    // Verify product throws SecurityNegotiationException when the service cert only has the ClientAuth usage
    public static void TCP_ServiceCertInvalidEKU_Throw_SecurityNegotiationException()
    {
        string testString = "Hello";
        NetTcpBinding binding = null;
        EndpointAddress endpointAddress = null;
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        // *** SETUP *** \\
        binding = new NetTcpBinding();
        binding.Security.Mode = SecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
        endpointAddress = new EndpointAddress(new Uri(Endpoints.Tcp_InvalidEkuServerCertResource_Address), new DnsEndpointIdentity(Endpoints.Tcp_InvalidEkuServerCertResource_HostName));
        factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
        serviceProxy = factory.CreateChannel();
 
        // *** EXECUTE *** \\
        try
        {
            serviceProxy.Echo(testString);
            Assert.Fail("Expected: SecurityNegotiationException, Actual: no exception");
        }
        catch (CommunicationException exception)
        {
            // *** VALIDATION *** \\
            // Cannot explicitly catch a SecurityNegotiationException as it is not in the public contract.
            string exceptionType = exception.GetType().Name;
            if (exceptionType != "SecurityNegotiationException")
            {
                Assert.Fail(string.Format("Expected type SecurityNegotiationException, Actual: {0}", exceptionType));
            }
 
            Assert.True(exception.Message.Contains(Endpoints.Tcp_RevokedServerCertResource_HostName),
                                                    string.Format("Expected message contains {0}, actual message: {1}",
                                                    Endpoints.Tcp_RevokedServerCertResource_HostName,
                                                    exception.ToString()));
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
 
    [WcfFact]
    [OuterLoop]
    public static void Abort_During_Implicit_Open_Closes_Sync_Waiters()
    {
        // This test is a regression test of an issue with CallOnceManager.
        // When a single proxy is used to make several service calls without
        // explicitly opening it, the CallOnceManager queues up all the requests
        // that happen while it is opening the channel (or handling previously
        // queued service calls.  If the channel was closed or faulted during
        // the handling of any queued requests, it caused a pathological worst
        // case where every queued request waited for its complete SendTimeout
        // before failing.
        //
        // This test operates by making multiple concurrent synchronous service
        // calls, but stalls the Opening event to allow them to be queued before
        // any of them are allowed to proceed.  It then aborts the channel when
        // the first service operation is allowed to proceed.  This causes the
        // CallOnce manager to deal with all its queued operations and cause
        // them to complete other than by timing out.
 
        BasicHttpBinding binding = null;
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
        int timeoutMs = 20000;
        long operationsQueued = 0;
        int operationCount = 5;
        Task<string>[] tasks = new Task<string>[operationCount];
        Exception[] exceptions = new Exception[operationCount];
        string[] results = new string[operationCount];
        bool isClosed = false;
        DateTime endOfOpeningStall = DateTime.Now;
        int serverDelayMs = 100;
        TimeSpan serverDelayTimeSpan = TimeSpan.FromMilliseconds(serverDelayMs);
        string testMessage = "testMessage";
 
        try
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
            binding.TransferMode = TransferMode.Streamed;
            // SendTimeout is the timeout used for implicit opens
            binding.SendTimeout = TimeSpan.FromMilliseconds(timeoutMs);
            factory = new ChannelFactory<IWcfService>(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic_Text));
            serviceProxy = factory.CreateChannel();
 
            // Force the implicit open to stall until we have multiple concurrent calls pending.
            // This forces the CallOnceManager to have a queue of waiters it will need to notify.
            ((ICommunicationObject)serviceProxy).Opening += (s, e) =>
            {
                // Wait until we see sync calls have been queued
                DateTime startOfOpeningStall = DateTime.Now;
                while (true)
                {
                    endOfOpeningStall = DateTime.Now;
 
                    // Don't wait forever -- if we stall longer than the SendTimeout, it means something
                    // is wrong other than what we are testing, so just fail early.
                    if ((endOfOpeningStall - startOfOpeningStall).TotalMilliseconds > timeoutMs)
                    {
                        Assert.Fail("The Opening event timed out waiting for operations to queue, which was not expected for this test.");
                    }
 
                    // As soon as we have all our Tasks at least running, wait a little
                    // longer to allow them finish queuing up their waiters, then stop stalling the Opening
                    if (Interlocked.Read(ref operationsQueued) >= operationCount)
                    {
                        Task.Delay(500).Wait();
                        endOfOpeningStall = DateTime.Now;
                        return;
                    }
 
                    Task.Delay(100).Wait();
                }
            };
 
            // Each task will make a synchronous service call, which will cause all but the
            // first to be queued for the implicit open.  The first call to complete then closes
            // the channel so that it is forced to deal with queued waiters.
            Func<string> callFunc = () =>
            {
                // We increment the # ops queued before making the actual sync call, which is
                // technically a short race condition in the test.  But reversing the order would
                // timeout the implicit open and fault the channel. 
                Interlocked.Increment(ref operationsQueued);
 
                // The call of the operation is what creates the entry in the CallOnceManager queue.
                // So as each Task below starts, it increments the count and adds a waiter to the
                // queue.  We ask for a small delay on the server side just to introduce a small
                // stall after the sync request has been made before it can complete.  Otherwise
                // fast machines can finish all the requests before the first one finishes the Close().
                string result = serviceProxy.EchoWithTimeout("test", serverDelayTimeSpan);
                lock (tasks)
                {
                    if (!isClosed)
                    {
                        try
                        {
                            isClosed = true;
                            ((ICommunicationObject)serviceProxy).Abort();
                        }
                        catch { }
                    }
                }
                return result;
            };
 
            // *** EXECUTE *** \\
 
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < operationCount; ++i)
            {
                tasks[i] = Task.Run(callFunc);
            }
 
            for (int i = 0; i < operationCount; ++i)
            {
                try
                {
                    results[i] = tasks[i].GetAwaiter().GetResult();
                }
                catch (Exception ex)
                {
                    exceptions[i] = ex;
                }
            }
 
            // *** VALIDATE *** \\
            double elapsedMs = (DateTime.Now - endOfOpeningStall).TotalMilliseconds;
 
            // Before validating that the issue was fixed, first validate that we received the exceptions or the
            // results we expected. This is to verify the fix did not introduce a behavioral change other than the
            // elimination of the long unnecessary timeouts after the channel was closed.
            int nFailures = 0;
            for (int i = 0; i < operationCount; ++i)
            {
                if (exceptions[i] == null)
                {
                    Assert.True((String.Equals("test", results[i])),
                                    String.Format("Expected operation #{0} to return '{1}' but actual was '{2}'",
                                                    i, testMessage, results[i]));
                }
                else
                {
                    ++nFailures;
 
                    TimeoutException toe = exceptions[i] as TimeoutException;
                    Assert.True(toe == null, String.Format("Task [{0}] should not have failed with TimeoutException", i));
                }
            }
 
            Assert.True(nFailures > 0,
                String.Format("Expected at least one operation to throw an exception, but none did. Elapsed time = {0} ms.",
                    elapsedMs));
 
            Assert.True(nFailures < operationCount,
                String.Format("Expected at least one operation to succeed but none did. Elapsed time = {0} ms.",
                    elapsedMs));
 
            // The original issue was that sync waiters in the CallOnceManager were not notified when
            // the channel became unusable and therefore continued to time out for the full amount.
            // Additionally, because they were executed sequentially, it was also possible for each one
            // to time out for the full amount.  Given that we closed the channel, we expect all the queued
            // waiters to have been immediately waked up and detected failure.
            int expectedElapsedMs = (operationCount * serverDelayMs) + timeoutMs / 2;
            Assert.True(elapsedMs < expectedElapsedMs,
                        String.Format("The {0} operations took {1} ms to complete which exceeds the expected {2} ms",
                                      operationCount, elapsedMs, expectedElapsedMs));
 
            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy).Close();
            factory.Close();
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
}