diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2011-06-09 17:14:03 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2011-06-09 17:14:03 -0700 |
commit | a921328045f5711fa4136a1f57d5db4745117905 (patch) | |
tree | fe2d7cfa663bdd22d4a85f3b19f9a66636610763 /src/DotNetOpenAuth.Test | |
parent | 2751e08721af51437ae5738e2da23dd460df6cc8 (diff) | |
parent | d916633668329aa07f0b6f2ee952268a5dff8069 (diff) | |
download | DotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.zip DotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.tar.gz DotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.tar.bz2 |
Merge branch 'v3.4' into sample2legged
Diffstat (limited to 'src/DotNetOpenAuth.Test')
11 files changed, 791 insertions, 83 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 5e80f2c..2067ede 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -74,6 +74,7 @@ <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs> <CodeContractsExtraRewriteOptions /> <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -111,6 +112,7 @@ <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs> <CodeContractsExtraRewriteOptions /> <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -150,6 +152,7 @@ <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs> <CodeContractsExtraRewriteOptions /> <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <ItemGroup> <Reference Include="log4net" /> @@ -301,6 +304,9 @@ <Compile Include="OpenId\RelyingParty\IdentifierDiscoveryResultTests.cs" /> <Compile Include="OpenId\UriIdentifierTests.cs" /> <Compile Include="OpenId\XriIdentifierTests.cs" /> + <Compile Include="Performance\CodeTimers.cs" /> + <Compile Include="Performance\HighPerformance.cs" /> + <Compile Include="Performance\PerformanceTestUtilities.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Messaging\ResponseTests.cs" /> <Compile Include="OAuth\AppendixScenarios.cs" /> diff --git a/src/DotNetOpenAuth.Test/Logging.config b/src/DotNetOpenAuth.Test/Logging.config index 87da027..a32badd 100644 --- a/src/DotNetOpenAuth.Test/Logging.config +++ b/src/DotNetOpenAuth.Test/Logging.config @@ -17,6 +17,23 @@ <conversionPattern value="[%thread] %-5level - %message%newline" /> </layout> </appender> + <appender name="StdOutAppender" type="log4net.Appender.ConsoleAppender"> + <immediateFlush value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> + </layout> + </appender> + <appender name="StdErrAppender" type="log4net.Appender.ConsoleAppender"> + <immediateFlush value="true" /> + <target value="Console.Error" /> + <filter type="log4net.Filter.LevelRangeFilter"> + <param name="LevelMin" value="ERROR"/> + <param name="LevelMax" value="FATAL"/> + </filter> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> + </layout> + </appender> <!-- Setup the root category, add the appenders and set the default level --> <root> <level value="Info" /> diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs index e862ca6..74e23bd 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs @@ -24,29 +24,29 @@ namespace DotNetOpenAuth.Test.Mocks { /// <summary> /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers. /// </summary> - /// <param name="signingBindingElement"> - /// The signing element for the Consumer to use. Null for the Service Provider. - /// </param> + /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> /// <param name="tokenManager">The token manager to use.</param> - internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IConsumerTokenManager tokenManager) + /// <param name="securitySettings">The security settings.</param> + internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IConsumerTokenManager tokenManager, DotNetOpenAuth.OAuth.ConsumerSecuritySettings securitySettings) : base( signingBindingElement, new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), - tokenManager) { + tokenManager, + securitySettings) { } /// <summary> /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers. /// </summary> - /// <param name="signingBindingElement"> - /// The signing element for the Consumer to use. Null for the Service Provider. - /// </param> + /// <param name="signingBindingElement">The signing element for the Consumer to use. Null for the Service Provider.</param> /// <param name="tokenManager">The token manager to use.</param> - internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IServiceProviderTokenManager tokenManager) + /// <param name="securitySettings">The security settings.</param> + internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IServiceProviderTokenManager tokenManager, DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings securitySettings) : base( signingBindingElement, new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), - tokenManager) { + tokenManager, + securitySettings) { } /// <summary> diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index 479375a..34cc3a4 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -27,6 +27,8 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { private TestWebRequestHandler webRequestHandler; private SigningBindingElementBase signingElement; private INonceStore nonceStore; + private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); [SetUp] public override void SetUp() { @@ -35,33 +37,33 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { this.webRequestHandler = new TestWebRequestHandler(); this.signingElement = new RsaSha1SigningBindingElement(new InMemoryTokenManager()); this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); - this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), new TestMessageFactory()); + this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), this.serviceProviderSecuritySettings, new TestMessageFactory()); this.channel.WebRequestHandler = this.webRequestHandler; } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullSigner() { - new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), new TestMessageFactory()); + new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullStore() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), null, new InMemoryTokenManager(), new TestMessageFactory()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), null, new InMemoryTokenManager(), this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase, ExpectedException(typeof(ArgumentNullException))] public void CtorNullTokenManager() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, null, new TestMessageFactory()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, null, this.consumerSecuritySettings, new TestMessageFactory()); } [TestCase] public void CtorSimpleConsumer() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager(), this.consumerSecuritySettings); } [TestCase] public void CtorSimpleServiceProvider() { - new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager(), this.serviceProviderSecuritySettings); } [TestCase] diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs index 2ef7e9a..83d5feb 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OAuth.ChannelElements { + using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OAuth; @@ -52,6 +54,15 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", SigningBindingElementBase.ConstructSignatureBaseString(message, this.MessageDescriptions.GetAccessor(message))); + // Test for when oauth_version isn't explicitly included in the message by the consumer. + message = CreateTestRequestTokenMessageNoOAuthVersion( + this.MessageDescriptions, + new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/", HttpDeliveryMethods.GetRequest)); + message.ExtraData.Remove("scope"); // remove it from ExtraData since we put it in the URL + Assert.AreEqual( + "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", + SigningBindingElementBase.ConstructSignatureBaseString(message, this.MessageDescriptions.GetAccessor(message))); + // This is a simulation of receiving the message, where the query string is still in the URL, // but has been read into ExtraData, so parameters in the query string appear twice. message = CreateTestRequestTokenMessage( @@ -62,6 +73,42 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { SigningBindingElementBase.ConstructSignatureBaseString(message, this.MessageDescriptions.GetAccessor(message))); } + [TestCase] + public void BaseSignatureStringResourceRequests() { + var message = this.CreateResourceRequest(new MessageReceivingEndpoint("http://tom.test.wishpot.com/restapi/List/Search?List.LastName=ciccotosto", HttpDeliveryMethods.GetRequest)); + message.ConsumerKey = "public"; + message.AccessToken = "tokenpublic"; + + var signedMessage = (ITamperResistantOAuthMessage)message; + signedMessage.HttpMethod = "GET"; + signedMessage.SignatureMethod = "HMAC-SHA1"; + + MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(message); + dictionary["oauth_timestamp"] = "1302716502"; + dictionary["oauth_nonce"] = "2U5YsZvL"; + + Assert.AreEqual( + "GET&http%3A%2F%2Ftom.test.wishpot.com%2Frestapi%2FList%2FSearch&List.LastName%3Dciccotosto%26oauth_consumer_key%3Dpublic%26oauth_nonce%3D2U5YsZvL%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1302716502%26oauth_token%3Dtokenpublic%26oauth_version%3D1.0", + SigningBindingElementBase.ConstructSignatureBaseString(message, this.MessageDescriptions.GetAccessor(message))); + } + + internal static UnauthorizedTokenRequest CreateTestRequestTokenMessageNoOAuthVersion(MessageDescriptionCollection messageDescriptions, MessageReceivingEndpoint endpoint) { + endpoint = endpoint ?? new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); + var parts = new Dictionary<string, string>(); + parts["oauth_consumer_key"] = "nerdbank.org"; + parts["oauth_timestamp"] = "1222665749"; + parts["oauth_nonce"] = "fe4045a3f0efdd1e019fa8f8ae3f5c38"; + parts["scope"] = "http://www.google.com/m8/feeds/"; + parts["oauth_signature_method"] = "HMAC-SHA1"; + parts["oauth_signature"] = "anything non-empty"; + + UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint, Protocol.V10.Version); + MessageDictionary dictionary = messageDescriptions.GetAccessor(message); + MessageSerializer.Get(typeof(UnauthorizedTokenRequest)).Deserialize(parts, dictionary); + + return message; + } + internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage(MessageDescriptionCollection messageDescriptions, MessageReceivingEndpoint endpoint) { endpoint = endpoint ?? new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint, Protocol.V10.Version); @@ -76,5 +123,11 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements { dictionary["scope"] = "http://www.google.com/m8/feeds/"; return message; } + + internal AccessProtectedResourceRequest CreateResourceRequest(MessageReceivingEndpoint endpoint) { + Contract.Requires(endpoint != null); + var message = new AccessProtectedResourceRequest(endpoint, Protocol.V10.Version); + return message; + } } } diff --git a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs index 972dd2a..6bcc583 100644 --- a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs @@ -19,6 +19,8 @@ namespace DotNetOpenAuth.Test.OAuth { internal class OAuthCoordinator : CoordinatorBase<WebConsumer, ServiceProvider> { private ConsumerDescription consumerDescription; private ServiceProviderDescription serviceDescription; + private DotNetOpenAuth.OAuth.ServiceProviderSecuritySettings serviceProviderSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + private DotNetOpenAuth.OAuth.ConsumerSecuritySettings consumerSecuritySettings = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); /// <summary>Initializes a new instance of the <see cref="OAuthCoordinator"/> class.</summary> /// <param name="consumerDescription">The description of the consumer.</param> @@ -50,8 +52,8 @@ namespace DotNetOpenAuth.Test.OAuth { serviceTokenManager.AddConsumer(this.consumerDescription); // Prepare channels that will pass messages directly back and forth. - CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(consumerSigningElement, (IConsumerTokenManager)consumerTokenManager); - CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(spSigningElement, (IServiceProviderTokenManager)serviceTokenManager); + CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(consumerSigningElement, (IConsumerTokenManager)consumerTokenManager, this.consumerSecuritySettings); + CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(spSigningElement, (IServiceProviderTokenManager)serviceTokenManager, this.serviceProviderSecuritySettings); consumerChannel.RemoteChannel = serviceProviderChannel; serviceProviderChannel.RemoteChannel = consumerChannel; diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs index 7984b58..4530982 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs @@ -18,6 +18,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.Test.Performance; using NUnit.Framework; [TestFixture, Category("Performance")] @@ -29,73 +30,50 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { [SetUp] public override void SetUp() { base.SetUp(); - SuspendLogging(); this.provider = CreateProvider(); } - [TearDown] - public override void Cleanup() { - ResumeLogging(); - base.Cleanup(); - } - [TestCase] public void AssociateDH() { var associateRequest = this.CreateAssociateRequest(OPUri); - Stopwatch timer = new Stopwatch(); - timer.Start(); - int iterations; - for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { - IRequest request = this.provider.GetRequest(associateRequest); - var response = this.provider.PrepareResponse(request); - Assert.IsInstanceOf<AssociateSuccessfulResponse>(response.OriginalMessage); - } - timer.Stop(); - double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); - TestUtilities.TestLogger.InfoFormat("Created {0} associations in {1}, or {2} per second.", iterations, timer.Elapsed, executionsPerSecond); - Assert.IsTrue(executionsPerSecond >= 2, "Too slow ({0} >= 2 executions per second required.)", executionsPerSecond); + MeasurePerformance( + () => { + IRequest request = this.provider.GetRequest(associateRequest); + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOf<AssociateSuccessfulResponse>(response.OriginalMessage); + }, + maximumAllowedUnitTime: 3.5e6f, + iterations: 1); } [TestCase] public void AssociateClearText() { var associateRequest = this.CreateAssociateRequest(OPUriSsl); // SSL will cause a plaintext association - Stopwatch timer = new Stopwatch(); - timer.Start(); - int iterations; - for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { - IRequest request = this.provider.GetRequest(associateRequest); - var response = this.provider.PrepareResponse(request); - Assert.IsInstanceOf<AssociateSuccessfulResponse>(response.OriginalMessage); - } - timer.Stop(); - double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); - TestUtilities.TestLogger.InfoFormat("Created {0} associations in {1}, or {2} per second.", iterations, timer.Elapsed, executionsPerSecond); - Assert.IsTrue(executionsPerSecond > 1000, "Too slow ({0} > 1000 executions per second required.)", executionsPerSecond); + MeasurePerformance( + () => { + IRequest request = this.provider.GetRequest(associateRequest); + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOf<AssociateSuccessfulResponse>(response.OriginalMessage); + }, + maximumAllowedUnitTime: 1.5e4f, + iterations: 1000); } [TestCase] public void CheckIdSharedHmacSha1Association() { Protocol protocol = Protocol.Default; string assocType = protocol.Args.SignatureAlgorithm.HMAC_SHA1; - double executionsPerSecond = this.ParameterizedCheckIdTest(protocol, assocType); - TestUtilities.TestLogger.InfoFormat("{0} executions per second.", executionsPerSecond); - Assert.IsTrue(executionsPerSecond > 500, "Too slow ({0} > 500 executions per second required.)", executionsPerSecond); + this.ParameterizedCheckIdTest(protocol, assocType); } [TestCase] public void CheckIdSharedHmacSha256Association() { Protocol protocol = Protocol.Default; string assocType = protocol.Args.SignatureAlgorithm.HMAC_SHA256; - double executionsPerSecond = this.ParameterizedCheckIdTest(protocol, assocType); - TestUtilities.TestLogger.InfoFormat("{0} executions per second.", executionsPerSecond); - Assert.IsTrue(executionsPerSecond > 400, "Too slow ({0} > 400 executions per second required.)", executionsPerSecond); + this.ParameterizedCheckIdTest(protocol, assocType); } - private static double GetExecutionsPerSecond(int iterations, Stopwatch timer) { - return (double)iterations / (timer.ElapsedMilliseconds / 1000); - } - - private double ParameterizedCheckIdTest(Protocol protocol, string assocType) { + private void ParameterizedCheckIdTest(Protocol protocol, string assocType) { Association assoc = HmacShaAssociation.Create( protocol, assocType, @@ -103,19 +81,14 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { this.provider.SecuritySettings); this.provider.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, assoc); var checkidRequest = this.CreateCheckIdRequest(true); - Stopwatch timer = new Stopwatch(); - timer.Start(); - int iterations; - for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { - var request = (IAuthenticationRequest)this.provider.GetRequest(checkidRequest); - request.IsAuthenticated = true; - var response = this.provider.PrepareResponse(request); - Assert.IsInstanceOf<PositiveAssertionResponse>(response.OriginalMessage); - } - timer.Stop(); - double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); - TestUtilities.TestLogger.InfoFormat("Responded to {0} checkid messages in {1}; or {2} authentications per second.", iterations, timer.Elapsed, executionsPerSecond); - return executionsPerSecond; + MeasurePerformance( + () => { + var request = (IAuthenticationRequest)this.provider.GetRequest(checkidRequest); + request.IsAuthenticated = true; + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOf<PositiveAssertionResponse>(response.OriginalMessage); + }, + maximumAllowedUnitTime: 6.8e4f); } private HttpRequestInfo CreateAssociateRequest(Uri opEndpoint) { diff --git a/src/DotNetOpenAuth.Test/Performance/CodeTimers.cs b/src/DotNetOpenAuth.Test/Performance/CodeTimers.cs new file mode 100644 index 0000000..85fa83d --- /dev/null +++ b/src/DotNetOpenAuth.Test/Performance/CodeTimers.cs @@ -0,0 +1,401 @@ +// <auto-generated/> // well, imported. But this gets StyleCop off our back +//----------------------------------------------------------------------- +// <copyright file="CodeTimers.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. All rights reserved. +// </copyright> +// <author>Vance Morrison</author> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Performance { + using System; + using System.Collections.Generic; + using System.Diagnostics; + + /// <summary> + /// Stats represents a list of samples (floating point values) This class can calculate the standard + /// statistics on this list (Mean, Median, StandardDeviation ...) + /// </summary> + internal class Stats : IEnumerable<float> { + private List<float> data; + private float minimum; + private float maximum; + private float median; + private float mean; + private float standardDeviation; + private bool statsComputed; + + public Stats() { data = new List<float>(); } + + public void Add(float dataItem) { + statsComputed = false; + data.Add(dataItem); + } + public void RemoveRange(int index, int count) { + data.RemoveRange(index, count); + statsComputed = false; + } + internal void Adjust(float delta) { + statsComputed = false; + for (int i = 0; i < data.Count; i++) + data[i] += delta; + } + internal void AdjustForScale(float scale) { + statsComputed = false; + for (int i = 0; i < data.Count; i++) + data[i] /= scale; + } + public int Count { get { return data.Count; } } + public float this[int idx] { get { return data[idx]; } } + public IEnumerator<float> GetEnumerator() { return data.GetEnumerator(); } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return data.GetEnumerator(); } + + public float Minimum { + get { + if (!statsComputed) { + this.ComputeStats(); + } + + return minimum; + } + } + public float Maximum { + get { + if (!statsComputed) { + this.ComputeStats(); + } + + return maximum; + } + } + public float Median { + get { + if (!statsComputed) { + this.ComputeStats(); + } + + return median; + } + } + + public float Mean { + get { + if (!statsComputed) { + this.ComputeStats(); + } + + return mean; + } + } + + public float StandardDeviation { + get { + if (!statsComputed) { + this.ComputeStats(); + } + + return standardDeviation; + } + } + + /// <summary> + /// Returns a <see cref="System.String"/> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String"/> that represents this instance. + /// </returns> + public override string ToString() { + if (!statsComputed) { + ComputeStats(); + } + + return "mean=" + mean.ToString("f3") + " median=" + median.ToString("f3") + + " min=" + minimum.ToString("f3") + " max=" + maximum.ToString("f3") + + " sdtdev=" + standardDeviation.ToString("f3") + " samples=" + Count.ToString(); + } + + private void ComputeStats() { + minimum = float.MaxValue; + maximum = float.MinValue; + median = 0.0F; + mean = 0.0F; + standardDeviation = 0.0F; + + double total = 0; + foreach (float dataPoint in this) { + if (dataPoint < minimum) + minimum = dataPoint; + if (dataPoint > maximum) + maximum = dataPoint; + total += dataPoint; + } + + if (Count > 0) { + data.Sort(); + if (Count % 2 == 1) + median = this[Count / 2]; + else + median = (this[(Count / 2) - 1] + this[Count / 2]) / 2; + mean = (float)(total / Count); + + double squares = 0.0; + foreach (float dataPoint in this) { + double diffFromMean = dataPoint - mean; + squares += diffFromMean * diffFromMean; + } + standardDeviation = (float)Math.Sqrt(squares / Count); + } + + statsComputed = true; + } + }; + + /// <summary> + /// The CodeTimer class only times one invocation of the code. Often, you want to collect many samples so + /// that you can determine how noisy the resulting data is. This is what MultiSampleCodeTimer does. + /// </summary> + internal class MultiSampleCodeTimer { + public MultiSampleCodeTimer() : this(1) { } + public MultiSampleCodeTimer(int sampleCount) : this(sampleCount, 1) { } + public MultiSampleCodeTimer(int sampleCount, int iterationCount) { + SampleCount = sampleCount; + timer = new CodeTimer(iterationCount); + timer.Prime = false; // We will do the priming (or not). + Prime = true; + } + public MultiSampleCodeTimer(MultiSampleCodeTimer template) + : this(template.SampleCount, template.IterationCount) { + OnMeasure = template.OnMeasure; + } + + /// <summary> + /// If true (the default), the benchmark is run once before the actual measurement to + /// insure that any 'first time' initialization is complete. + /// </summary> + public bool Prime; + /// <summary> + /// The number of times the benchmark is run in a loop for a single measument. + /// </summary> + public int IterationCount { get { return timer.IterationCount; } set { timer.IterationCount = value; } } + /// <summary> + /// The number of measurments to make for a single benchmark. + /// </summary> + public int SampleCount; + /// <summary> + /// The smallest time (in microseconds) that can be resolved by the timer). + /// </summary> + public static float ResolutionUsec { get { return 1000000.0F / Stopwatch.Frequency; } } + + public delegate void MeasureCallback(string name, int iterationCount, float scale, Stats sample); + /// <summary> + /// OnMeasure is signaled every time a Measure() is called. + /// </summary> + public event MeasureCallback OnMeasure; + + public Stats Measure(string name, Action action) { + return Measure(name, 1, action, null); + } + /// <summary> + /// The main measurment routine. Calling this will cause code:OnMeasure event to be + /// raised. + /// </summary> + /// <param name="name">name of the benchmark</param> + /// <param name="scale">The number of times the benchmark is cloned in 'action' (typically 1)</param> + /// <param name="action">The actual code to measure.</param> + /// <returns>A Stats object representing the measurements (in usec)</returns> + public Stats Measure(string name, float scale, Action action) { + return Measure(name, scale, action, null); + } + /// <summary> + /// The main measurment routine. Calling this will cause code:OnMeasure event to be + /// raised. + /// </summary> + /// <param name="name">name of the benchmark</param> + /// <param name="scale">The number of times the benchmark is cloned in 'action' (typically 1)</param> + /// <param name="action">The actual code to measure.</param> + /// <param name="reset">Code that will be called before 'action' to reset the state of the benchmark.</param> + /// <returns>A Stats object representing the measurements (in usec)</returns> + public Stats Measure(string name, float scale, Action action, Action reset) { + if (reset != null && IterationCount != 1) + throw new ApplicationException("Reset can only be used on timers with an iteration count of 1"); + Stats statsUSec = new Stats(); + if (Prime) { + if (reset != null) + reset(); + action(); + } + for (int i = 0; i < SampleCount; i++) { + if (reset != null) + reset(); + statsUSec.Add(timer.Measure(name, scale, action)); + } + + if (OnMeasure != null) + OnMeasure(name, IterationCount, scale, statsUSec); + return statsUSec; + } + + /// <summary> + /// Prints the mean, median, min, max, and stdDev and count of the samples to the Console + /// Useful as a target for OnMeasure + /// </summary> + public static MeasureCallback PrintStats = delegate(string name, int iterationCount, float scale, Stats sample) { + Console.WriteLine(name + ": " + sample.ToString()); + }; + /// <summary> + /// Prints the mean with a error bound (2 standard deviations, which imply a you have + /// 95% confidence that a sampleUsec will be with the bounds (for a normal distribution). + /// This is a good default target for OnMeasure. + /// </summary> + public static MeasureCallback Print = delegate(string name, int iterationCount, float scale, Stats sample) { + // +- two standard deviations covers 95% of all samples in a normal distribution + float errorPercent = (sample.StandardDeviation * 2 * 100) / Math.Abs(sample.Mean); + string errorString = ">400%"; + if (errorPercent < 400) + errorString = (errorPercent.ToString("f0") + "%").PadRight(5); + string countString = ""; + if (iterationCount != 1) + countString = "count: " + iterationCount.ToString() + " "; + Console.WriteLine(name + ": " + countString + sample.Mean.ToString("f3").PadLeft(8) + " +- " + errorString + " msec"); + }; + + #region privates + CodeTimer timer; + #endregion + }; + + /// <summary> + /// CodeTimer is a simple wrapper that uses System.Diagnostics.StopWatch + /// to time the body of some code (given by a delegate), to high precision. + /// </summary> + public class CodeTimer { + public CodeTimer() : this(1) { } + public CodeTimer(int iterationCount) { + this.iterationCount = iterationCount; + Prime = true; + + // Spin the CPU for a while. This should help insure that the CPU gets out of any low power + // mode so so that we get more stable results. + // TODO: see if this is true, and if there is a better way of doing it. + Stopwatch sw = Stopwatch.StartNew(); + while (sw.ElapsedMilliseconds < 32) { + } + } + /// <summary> + /// The number of times the benchmark is run in a loop for a single measument. + /// </summary> + public int IterationCount { + get { return iterationCount; } + set { + iterationCount = value; + overheadValid = false; + } + } + /// <summary> + /// By default CodeTimer will run the action once before doing a + /// measurement run. This insures one-time actions like JIT + /// compilation are not being measured. However if the benchmark is + /// not idempotent, this can be a problem. Setting Prime=false + /// insures that this Priming does not happen. + /// </summary> + public bool Prime; + public delegate void MeasureCallback(string name, int iterationCount, float sample); + /// <summary> + /// OnMeasure is signaled every time a Measure() is called. + /// </summary> + public event MeasureCallback OnMeasure; + /// <summary> + /// The smallest time (in microseconds) that can be resolved by the timer). + /// </summary> + public static float ResolutionUsec { get { return 1000000.0F / Stopwatch.Frequency; } } + /// <summary> + /// Returns the number of microsecond it took to run 'action', 'count' times. + /// </summary> + public float Measure(string name, Action action) { + return Measure(name, 1, action); + } + /// <summary> + /// Returns the number of microseconds it to to run action 'count' times divided by 'scale'. + /// Scaling is useful if you want to normalize to a single iteration for example. + /// </summary> + public float Measure(string name, float scale, Action action) { + Stopwatch sw = new Stopwatch(); + + // Run the action once to do any JITTing that might happen. + if (Prime) + action(); + float overheadUsec = GetOverheadUsec(action); + + sw.Reset(); + sw.Start(); + for (int j = 0; j < iterationCount; j++) + action(); + sw.Stop(); + + float sampleUsec = (float)((sw.Elapsed.TotalMilliseconds * 1000.0F - overheadUsec) / scale / iterationCount); + if (!computingOverhead && OnMeasure != null) + OnMeasure(name, iterationCount, sampleUsec); + return sampleUsec; + } + /// <summary> + /// Prints the result of a CodeTimer to standard output. + /// This is a good default target for OnMeasure. + /// </summary> + public static MeasureCallback Print = delegate(string name, int iterationCount, float sample) { + Console.WriteLine("{0}: count={1} time={2:f3} msec ", name, iterationCount, sample); + }; + #region privates + + /// <summary> + /// Time the overheadUsec of the harness that does nothing so we can subtract it out. + /// + /// Because calling delegates on static methods is more expensive than caling delegates on + /// instance methods we need the action to determine the overheadUsec. + /// </summary> + /// <returns></returns> + float GetOverheadUsec(Action action) { + if (!overheadValid) { + if (computingOverhead) + return 0.0F; + computingOverhead = true; + + // Compute the overheads of calling differnet types of delegates. + Action emptyInstanceAction = new Action(this.emptyMethod); + // Prime the actions (JIT them) + Measure(null, emptyInstanceAction); + // Compute the min over 5 runs (figuring better not to go negative) + instanceOverheadUsec = float.MaxValue; + for (int i = 0; i < 5; i++) { + // We multiply by iteration count because we don't want this scaled by the + // count but 'Measure' does it by whether we want it or not. + instanceOverheadUsec = Math.Min(Measure(null, emptyInstanceAction) * IterationCount, instanceOverheadUsec); + } + + Action emptyStaticAction = new Action(emptyStaticMethod); + Measure(null, emptyStaticAction); + staticOverheadUsec = float.MaxValue; + for (int i = 0; i < 5; i++) + staticOverheadUsec = Math.Min(Measure(null, emptyStaticAction) * IterationCount, staticOverheadUsec); + + computingOverhead = false; + overheadValid = true; + } + + if (action.Target == null) + return staticOverheadUsec; + else + return instanceOverheadUsec; + } + + static private void emptyStaticMethod() { } + private void emptyMethod() { } + + bool overheadValid; + bool computingOverhead; + int iterationCount; + float staticOverheadUsec; + float instanceOverheadUsec; + + #endregion + }; +} + diff --git a/src/DotNetOpenAuth.Test/Performance/HighPerformance.cs b/src/DotNetOpenAuth.Test/Performance/HighPerformance.cs new file mode 100644 index 0000000..695aeda --- /dev/null +++ b/src/DotNetOpenAuth.Test/Performance/HighPerformance.cs @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------- +// <copyright file="HighPerformance.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Performance { + using System; + using System.ComponentModel; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Threading; + using log4net; + using NUnit.Framework; + + /// <summary> + /// Suppresses logging and forces the CPU into a high performance mode. + /// </summary> + internal class HighPerformance : IDisposable { + private readonly log4net.Core.Level originalLoggerThreshold; + private readonly PowerManagment.PowerSetting powerSetting; + private readonly ProcessPriorityClass originalProcessPriority; + +#pragma warning disable 0618 + /// <summary> + /// Initializes a new instance of the <see cref="HighPerformance"/> class. + /// </summary> + internal HighPerformance() { + ////if (!WaitForQuietCpu()) { + //// Assert.Inconclusive("Timed out waiting for a quiet CPU in which to perform perf tests."); + ////} + + this.originalLoggerThreshold = LogManager.GetLoggerRepository().Threshold; + LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["OFF"]; + this.powerSetting = new PowerManagment.PowerSetting(PowerManagment.PowerProfiles.HighPerformance); + this.originalProcessPriority = Process.GetCurrentProcess().PriorityClass; + Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; + Thread.CurrentThread.Priority = ThreadPriority.Highest; + SpinCpu(); + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + Thread.CurrentThread.Priority = ThreadPriority.Normal; + Process.GetCurrentProcess().PriorityClass = this.originalProcessPriority; + this.powerSetting.Dispose(); // restores original power setting. + LogManager.GetLoggerRepository().Threshold = this.originalLoggerThreshold; + } +#pragma warning restore 0618 + + /// <summary> + /// Runs the CPU in a tight loop to get it out of any low power state. + /// </summary> + private static void SpinCpu() { + int dummy; + new MultiSampleCodeTimer(10, 1000).Measure( + "Loop 1K times", + 1, + delegate { + int k = 0; + while (k < 1000) { + k++; // still in danger of being optimized. + } + + dummy = k; // avoid optimization. + }); + } + + private static bool WaitForQuietCpu(float maxCpuSpike = 25, int minSecondsOfQuiet = 2, int maxSecondsBeforeGiveUp = 30) { + using (var pc = new System.Diagnostics.PerformanceCounter()) { + pc.CategoryName = "Processor"; + pc.CounterName = "% Processor Time"; + pc.InstanceName = "_Total"; + + TimeSpan samplingInterval = TimeSpan.FromMilliseconds(1000); + TimeSpan minimumQuietTime = TimeSpan.FromSeconds(minSecondsOfQuiet); + TimeSpan maximumTimeBeforeBail = TimeSpan.FromSeconds(maxSecondsBeforeGiveUp); + DateTime startTryingStamp = DateTime.Now; + int hitsRequired = (int)(minimumQuietTime.TotalMilliseconds / samplingInterval.TotalMilliseconds); + while (DateTime.Now - startTryingStamp < maximumTimeBeforeBail) { + int hits; + for (hits = 0; hits < hitsRequired; hits++) { + float currentCpuUtilization = pc.NextValue(); + if (currentCpuUtilization > maxCpuSpike) { + ////Console.WriteLine("Miss: CPU at {0}% utilization", currentCpuUtilization); + break; + } + + ////Console.WriteLine("Hit: CPU at {0}% utilization", currentCpuUtilization); + Thread.Sleep(samplingInterval); + } + + if (hits == hitsRequired) { + return true; + } + + Thread.Sleep(samplingInterval); + } + + return false; + } + } + + /// <summary> + /// PowerManagement allows you to access the funtionality of the Control Panel -> Power Options + /// dialog in windows. (Currently we only use VISTA APIs). + /// </summary> + private static class PowerManagment { + internal static unsafe Guid CurrentPolicy { + get { + Guid* retPolicy = null; + Guid ret = Guid.Empty; + try { + int callRet = PowerGetActiveScheme(IntPtr.Zero, ref retPolicy); + if (callRet == 0) { + ret = *retPolicy; + Marshal.FreeHGlobal((IntPtr)retPolicy); + } + } catch (Exception) { + } + return ret; + } + + set { + Guid newPolicy = value; + int result = PowerSetActiveScheme(IntPtr.Zero, ref newPolicy); + if (result != 0) { + TestUtilities.TestLogger.ErrorFormat("Unable to set power management policy. Error code: {0}", result); + ////throw new Win32Exception(result); + } + } + } + + [DllImport("powrprof.dll")] + private static unsafe extern int PowerGetActiveScheme(IntPtr reservedZero, ref Guid* policyGuidRet); + + [DllImport("powrprof.dll")] + private static extern int PowerSetActiveScheme(IntPtr reservedZero, ref Guid policyGuid); + + internal static class PowerProfiles { + internal static Guid HighPerformance = new Guid(0x8c5e7fda, 0xe8bf, 0x4a96, 0x9a, 0x85, 0xa6, 0xe2, 0x3a, 0x8c, 0x63, 0x5c); + + internal static Guid Balanced = new Guid(0x381b4222, 0xf694, 0x41f0, 0x96, 0x85, 0xff, 0x5b, 0xb2, 0x60, 0xdf, 0x2e); + + internal static Guid PowerSaver = new Guid(0xa1841308, 0x3541, 0x4fab, 0xbc, 0x81, 0xf7, 0x15, 0x56, 0xf2, 0x0b, 0x4a); + } + + internal class PowerSetting : IDisposable { + /// <summary> + /// The power policy in effect when this instance was constructed. + /// </summary> + private Guid previousPolicy; + + /// <summary> + /// Initializes a new instance of the <see cref="PowerSetting"/> class. + /// </summary> + /// <param name="powerProfile">The power profile.</param> + internal PowerSetting(Guid powerProfile) { + this.previousPolicy = PowerManagment.CurrentPolicy; + if (this.previousPolicy != powerProfile) { + PowerManagment.CurrentPolicy = powerProfile; + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + if (this.previousPolicy != PowerManagment.CurrentPolicy) { + PowerManagment.CurrentPolicy = this.previousPolicy; + } + } + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Performance/PerformanceTestUtilities.cs b/src/DotNetOpenAuth.Test/Performance/PerformanceTestUtilities.cs new file mode 100644 index 0000000..5e28732 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Performance/PerformanceTestUtilities.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="PerformanceTestUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Performance { + using System; + using System.Diagnostics; + using System.Reflection; + using System.Threading; + using DotNetOpenAuth.OpenId.RelyingParty; + using NUnit.Framework; + + internal static class PerformanceTestUtilities { + internal static Stats Baseline; + + static PerformanceTestUtilities() { + Baseline = CollectBaseline(); + TestUtilities.TestLogger.InfoFormat( + "Scaled where EmptyStaticFunction = 1.0 ({0:f1} nsec = 1.0 units)", + Baseline.Median * 1000); + } + + internal static bool IsOptimized(Assembly assembly) { + DebuggableAttribute debugAttribute = (DebuggableAttribute)System.Attribute.GetCustomAttribute(assembly, typeof(System.Diagnostics.DebuggableAttribute)); + return debugAttribute == null || !debugAttribute.IsJITOptimizerDisabled; + } + + private static Stats CollectBaseline() { + using (new HighPerformance()) { + return new MultiSampleCodeTimer(10, 1000).Measure( + "MethodCalls: EmptyStaticFunction()", + 10, + delegate { + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + Class.EmptyStaticFunction(); + }); + } + } + + private class Class { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + public static void EmptyStaticFunction() { + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/TestBase.cs b/src/DotNetOpenAuth.Test/TestBase.cs index 8aeca2d..4a6eaca 100644 --- a/src/DotNetOpenAuth.Test/TestBase.cs +++ b/src/DotNetOpenAuth.Test/TestBase.cs @@ -11,6 +11,8 @@ namespace DotNetOpenAuth.Test { using System.Web; using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Test.Performance; using log4net; using NUnit.Framework; @@ -65,6 +67,34 @@ namespace DotNetOpenAuth.Test { log4net.LogManager.Shutdown(); } + internal static Stats MeasurePerformance(Action action, float maximumAllowedUnitTime, int samples = 10, int iterations = 100, string name = null) { + if (!PerformanceTestUtilities.IsOptimized(typeof(OpenIdRelyingParty).Assembly)) { + Assert.Inconclusive("Unoptimized code."); + } + + var timer = new MultiSampleCodeTimer(samples, iterations); + Stats stats; + using (new HighPerformance()) { + stats = timer.Measure(name ?? TestContext.CurrentContext.Test.FullName, action); + } + + stats.AdjustForScale(PerformanceTestUtilities.Baseline.Median); + + TestUtilities.TestLogger.InfoFormat( + "Performance counters: median {0}, mean {1}, min {2}, max {3}, stddev {4} ({5}%).", + stats.Median, + stats.Mean, + stats.Minimum, + stats.Maximum, + stats.StandardDeviation, + stats.StandardDeviation / stats.Median * 100); + + Assert.IsTrue(stats.Mean < maximumAllowedUnitTime, "The mean time of {0} exceeded the maximum allowable of {1}.", stats.Mean, maximumAllowedUnitTime); + TestUtilities.TestLogger.InfoFormat("Within {0}% of the maximum allowed time of {1}.", Math.Round((maximumAllowedUnitTime - stats.Mean) / maximumAllowedUnitTime * 100, 1), maximumAllowedUnitTime); + + return stats; + } + /// <summary> /// Sets HttpContext.Current to some empty (but non-null!) value. /// </summary> @@ -73,15 +103,5 @@ namespace DotNetOpenAuth.Test { new HttpRequest("mock", "http://mock", "mock"), new HttpResponse(new StringWriter())); } - -#pragma warning disable 0618 - protected internal static void SuspendLogging() { - LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["OFF"]; - } - - protected internal static void ResumeLogging() { - LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["ALL"]; - } -#pragma warning restore 0618 } } |