summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Test
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2011-06-09 17:14:03 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2011-06-09 17:14:03 -0700
commita921328045f5711fa4136a1f57d5db4745117905 (patch)
treefe2d7cfa663bdd22d4a85f3b19f9a66636610763 /src/DotNetOpenAuth.Test
parent2751e08721af51437ae5738e2da23dd460df6cc8 (diff)
parentd916633668329aa07f0b6f2ee952268a5dff8069 (diff)
downloadDotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.zip
DotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.tar.gz
DotNetOpenAuth-a921328045f5711fa4136a1f57d5db4745117905.tar.bz2
Merge branch 'v3.4' into sample2legged
Diffstat (limited to 'src/DotNetOpenAuth.Test')
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj6
-rw-r--r--src/DotNetOpenAuth.Test/Logging.config17
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs20
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs14
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs53
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs6
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs83
-rw-r--r--src/DotNetOpenAuth.Test/Performance/CodeTimers.cs401
-rw-r--r--src/DotNetOpenAuth.Test/Performance/HighPerformance.cs178
-rw-r--r--src/DotNetOpenAuth.Test/Performance/PerformanceTestUtilities.cs56
-rw-r--r--src/DotNetOpenAuth.Test/TestBase.cs40
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
}
}