From ac7bee4a51edca48e9f80b0ac7999a67851a452f Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 12:24:25 -0700 Subject: [PATCH] Fix format (#2231) --- .../ActivityExtensions.cs | 318 ++--- .../ActivityHelper.cs | 324 ++--- .../AspNetTelemetryCorrelationEventSource.cs | 220 ++-- .../AssemblyInfo.cs | 68 +- .../Internal/BaseHeaderParser.cs | 166 +-- .../Internal/GenericHeaderParser.cs | 87 +- .../Internal/HeaderUtilities.cs | 118 +- .../Internal/HttpHeaderParser.cs | 79 +- .../Internal/HttpParseResult.cs | 74 +- .../Internal/HttpRuleParser.cs | 566 ++++---- .../Internal/NameValueHeaderValue.cs | 260 ++-- ...crosoft.AspNet.TelemetryCorrelation.csproj | 42 +- .../README.md | 27 +- .../TelemetryCorrelationHttpModule.cs | 344 ++--- .../ActivityExtensionsTest.cs | 644 +++++----- .../ActivityHelperTest.cs | 1138 ++++++++--------- .../HttpContextHelper.cs | 206 +-- ...t.AspNet.TelemetryCorrelation.Tests.csproj | 66 +- .../PropertyExtensions.cs | 56 +- .../TestDiagnosticListener.cs | 88 +- .../WebConfigTransformTest.cs | 822 ++++++------ .../WebConfigWithLocationTagTransformTest.cs | 882 ++++++------- 22 files changed, 3299 insertions(+), 3296 deletions(-) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index b7a18a0461b..8492e7327df 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,159 +1,159 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Extensions of Activity class. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ActivityExtensions - { - /// - /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. - /// - internal const string RequestIdHeaderName = "Request-Id"; - - /// - /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. - /// - internal const string TraceparentHeaderName = "traceparent"; - - /// - /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. - /// - internal const string TracestateHeaderName = "tracestate"; - - /// - /// Http header name to carry the correlation context. - /// - internal const string CorrelationContextHeaderName = "Correlation-Context"; - - /// - /// Maximum length of Correlation-Context header value. - /// - internal const int MaxCorrelationContextLength = 1024; - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - public static bool Extract(this Activity activity, NameValueCollection requestHeaders) - { - if (activity == null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); - return false; - } - - if (activity.ParentId != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); - return false; - } - - if (activity.Id != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); - return false; - } - - var parents = requestHeaders.GetValues(TraceparentHeaderName); - if (parents == null || parents.Length == 0) - { - parents = requestHeaders.GetValues(RequestIdHeaderName); - } - - if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) - { - // there may be several Request-Id or traceparent headers, but we only read the first one - activity.SetParentId(parents[0]); - - var tracestates = requestHeaders.GetValues(TracestateHeaderName); - if (tracestates != null && tracestates.Length > 0) - { - if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) - { - activity.TraceStateString = tracestates[0]; - } - else - { - activity.TraceStateString = string.Join(",", tracestates); - } - } - - // Header format - Correlation-Context: key1=value1, key2=value2 - var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); - if (baggages != null) - { - int correlationContextLength = -1; - - // there may be several Correlation-Context header - foreach (var item in baggages) - { - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - foreach (var pair in item.Split(',')) - { - correlationContextLength += pair.Length + 1; // pair and comma - - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) - { - activity.AddBaggage(baggageItem.Name, baggageItem.Value); - } - else - { - AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); - } - } - } - } - - return true; - } - - return false; - } - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - [Obsolete("Method is obsolete, use Extract method instead", true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) - { - return Extract(activity, requestHeaders); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Extensions of Activity class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ActivityExtensions + { + /// + /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. + /// + internal const string RequestIdHeaderName = "Request-Id"; + + /// + /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. + /// + internal const string TraceparentHeaderName = "traceparent"; + + /// + /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. + /// + internal const string TracestateHeaderName = "tracestate"; + + /// + /// Http header name to carry the correlation context. + /// + internal const string CorrelationContextHeaderName = "Correlation-Context"; + + /// + /// Maximum length of Correlation-Context header value. + /// + internal const int MaxCorrelationContextLength = 1024; + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + public static bool Extract(this Activity activity, NameValueCollection requestHeaders) + { + if (activity == null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + return false; + } + + if (activity.ParentId != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + return false; + } + + if (activity.Id != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + return false; + } + + var parents = requestHeaders.GetValues(TraceparentHeaderName); + if (parents == null || parents.Length == 0) + { + parents = requestHeaders.GetValues(RequestIdHeaderName); + } + + if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) + { + // there may be several Request-Id or traceparent headers, but we only read the first one + activity.SetParentId(parents[0]); + + var tracestates = requestHeaders.GetValues(TracestateHeaderName); + if (tracestates != null && tracestates.Length > 0) + { + if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) + { + activity.TraceStateString = tracestates[0]; + } + else + { + activity.TraceStateString = string.Join(",", tracestates); + } + } + + // Header format - Correlation-Context: key1=value1, key2=value2 + var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); + if (baggages != null) + { + int correlationContextLength = -1; + + // there may be several Correlation-Context header + foreach (var item in baggages) + { + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + foreach (var pair in item.Split(',')) + { + correlationContextLength += pair.Length + 1; // pair and comma + + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + activity.AddBaggage(baggageItem.Name, baggageItem.Value); + } + else + { + AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + } + } + } + } + + return true; + } + + return false; + } + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + [Obsolete("Method is obsolete, use Extract method instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) + { + return Extract(activity, requestHeaders); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index 9bfd9b1a8dd..057891fe8ce 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,162 +1,162 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections; -using System.Diagnostics; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Activity helper class. - /// - internal static class ActivityHelper - { - /// - /// Listener name. - /// - public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; - - /// - /// Activity name for http request. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - - /// - /// Event name for the activity start event. - /// - public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; - - /// - /// Key to store the activity in HttpContext. - /// - public const string ActivityKey = "__AspnetActivity__"; - - private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); - - private static readonly object EmptyPayload = new object(); - - /// - /// Stops the activity and notifies listeners about it. - /// - /// HttpContext.Items. - public static void StopAspNetActivity(IDictionary contextItems) - { - var currentActivity = Activity.Current; - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (currentActivity != aspNetActivity) - { - Activity.Current = aspNetActivity; - currentActivity = aspNetActivity; - } - - if (currentActivity != null) - { - // stop Activity with Stop event - AspNetListener.StopActivity(currentActivity, EmptyPayload); - contextItems[ActivityKey] = null; - } - - AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); - } - - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// Current HttpContext. - /// Determines if headers should be parsed get correlation ids. - /// New root activity. - public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) - { - if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) - { - var rootActivity = new Activity(AspNetActivityName); - - if (parseHeaders) - { - rootActivity.Extract(context.Request.Unvalidated.Headers); - } - - AspNetListener.OnActivityImport(rootActivity, null); - - if (StartAspNetActivity(rootActivity)) - { - context.Items[ActivityKey] = rootActivity; - AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); - return rootActivity; - } - } - - return null; - } - - public static void WriteActivityException(IDictionary contextItems, Exception exception) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (aspNetActivity != null) - { - if (Activity.Current != aspNetActivity) - { - Activity.Current = aspNetActivity; - } - - AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); - AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); - } - } - - /// - /// It's possible that a request is executed in both native threads and managed threads, - /// in such case Activity.Current will be lost during native thread and managed thread switch. - /// This method is intended to restore the current activity in order to correlate the child - /// activities with the root activity of the request. - /// - /// HttpContext.Items dictionary. - internal static void RestoreActivityIfNeeded(IDictionary contextItems) - { - if (Activity.Current == null) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - if (aspNetActivity != null) - { - Activity.Current = aspNetActivity; - } - } - } - - private static bool StartAspNetActivity(Activity activity) - { - if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) - { - if (AspNetListener.IsEnabled(AspNetActivityStartName)) - { - AspNetListener.StartActivity(activity, EmptyPayload); - } - else - { - activity.Start(); - } - - return true; - } - - return false; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections; +using System.Diagnostics; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Activity helper class. + /// + internal static class ActivityHelper + { + /// + /// Listener name. + /// + public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + + /// + /// Activity name for http request. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + + /// + /// Event name for the activity start event. + /// + public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; + + /// + /// Key to store the activity in HttpContext. + /// + public const string ActivityKey = "__AspnetActivity__"; + + private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); + + private static readonly object EmptyPayload = new object(); + + /// + /// Stops the activity and notifies listeners about it. + /// + /// HttpContext.Items. + public static void StopAspNetActivity(IDictionary contextItems) + { + var currentActivity = Activity.Current; + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (currentActivity != aspNetActivity) + { + Activity.Current = aspNetActivity; + currentActivity = aspNetActivity; + } + + if (currentActivity != null) + { + // stop Activity with Stop event + AspNetListener.StopActivity(currentActivity, EmptyPayload); + contextItems[ActivityKey] = null; + } + + AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + } + + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// Current HttpContext. + /// Determines if headers should be parsed get correlation ids. + /// New root activity. + public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) + { + if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) + { + var rootActivity = new Activity(AspNetActivityName); + + if (parseHeaders) + { + rootActivity.Extract(context.Request.Unvalidated.Headers); + } + + AspNetListener.OnActivityImport(rootActivity, null); + + if (StartAspNetActivity(rootActivity)) + { + context.Items[ActivityKey] = rootActivity; + AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + return rootActivity; + } + } + + return null; + } + + public static void WriteActivityException(IDictionary contextItems, Exception exception) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (aspNetActivity != null) + { + if (Activity.Current != aspNetActivity) + { + Activity.Current = aspNetActivity; + } + + AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); + AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); + } + } + + /// + /// It's possible that a request is executed in both native threads and managed threads, + /// in such case Activity.Current will be lost during native thread and managed thread switch. + /// This method is intended to restore the current activity in order to correlate the child + /// activities with the root activity of the request. + /// + /// HttpContext.Items dictionary. + internal static void RestoreActivityIfNeeded(IDictionary contextItems) + { + if (Activity.Current == null) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + if (aspNetActivity != null) + { + Activity.Current = aspNetActivity; + } + } + } + + private static bool StartAspNetActivity(Activity activity) + { + if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) + { + if (AspNetListener.IsEnabled(AspNetActivityStartName)) + { + AspNetListener.StartActivity(activity, EmptyPayload); + } + else + { + activity.Start(); + } + + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index d2b576de95f..f439add5081 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,110 +1,110 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Diagnostics.Tracing; -#pragma warning disable SA1600 // Elements must be documented - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// ETW EventSource tracing class. - /// - [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] - internal sealed class AspNetTelemetryCorrelationEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); - - [NonEvent] - public void ActivityException(string id, string eventName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.ActivityException(id, eventName, ex.ToString()); - } - } - - [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] - public void TraceCallback(string callback) - { - this.WriteEvent(1, callback); - } - - [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStarted(string id) - { - this.WriteEvent(2, id); - } - - [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id, string eventName) - { - this.WriteEvent(3, id, eventName); - } - - [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] - public void HeaderParsingError(string headerName, string headerValue) - { - this.WriteEvent(4, headerName, headerValue); - } - - [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] - public void ActvityExtractionError(string reason) - { - this.WriteEvent(5, reason); - } - - [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] - public void FinishedActivityIsDetected(string id, string name) - { - this.WriteEvent(6, id, name); - } - - [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] - public void ActivityStackIsTooDeepError() - { - this.WriteEvent(7); - } - - [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - this.WriteEvent(8, id); - } - - [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvokationError(string error) - { - this.WriteEvent(9, error); - } - - [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] - public void ActivityStackIsTooDeepDetails(string id, string name) - { - this.WriteEvent(10, id, name); - } - - [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void ActivityException(string id, string eventName, string ex) - { - this.WriteEvent(11, id, eventName, ex); - } - } -} -#pragma warning restore SA1600 // Elements must be documented \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Diagnostics.Tracing; +#pragma warning disable SA1600 // Elements must be documented + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// ETW EventSource tracing class. + /// + [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] + internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + { + /// + /// Instance of the PlatformEventSource class. + /// + public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + + [NonEvent] + public void ActivityException(string id, string eventName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.ActivityException(id, eventName, ex.ToString()); + } + } + + [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] + public void TraceCallback(string callback) + { + this.WriteEvent(1, callback); + } + + [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStarted(string id) + { + this.WriteEvent(2, id); + } + + [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id, string eventName) + { + this.WriteEvent(3, id, eventName); + } + + [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] + public void HeaderParsingError(string headerName, string headerValue) + { + this.WriteEvent(4, headerName, headerValue); + } + + [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] + public void ActvityExtractionError(string reason) + { + this.WriteEvent(5, reason); + } + + [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] + public void FinishedActivityIsDetected(string id, string name) + { + this.WriteEvent(6, id, name); + } + + [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] + public void ActivityStackIsTooDeepError() + { + this.WriteEvent(7); + } + + [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + this.WriteEvent(8, id); + } + + [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvokationError(string error) + { + this.WriteEvent(9, error); + } + + [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] + public void ActivityStackIsTooDeepDetails(string id, string name) + { + this.WriteEvent(10, id, name); + } + + [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void ActivityException(string id, string eventName, string ex) + { + this.WriteEvent(11, id, eventName, ex); + } + } +} +#pragma warning restore SA1600 // Elements must be documented diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index 890303af426..d5967973522 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,34 +1,34 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] - -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] - -#if SIGNED -internal static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -internal static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] + +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; +} +#endif diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index 5f152f67fba..3e884dd095f 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,83 +1,83 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs - internal abstract class BaseHeaderParser : HttpHeaderParser - { - protected BaseHeaderParser(bool supportsMultipleValues) - : base(supportsMultipleValues) - { - } - - public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) - { - parsedValue = default(T); - - // If multiple values are supported (i.e. list of values), then accept an empty string: The header may - // be added multiple times to the request/response message. E.g. - // Accept: text/xml; q=1 - // Accept: - // Accept: text/plain; q=0.2 - if (string.IsNullOrEmpty(value) || (index == value.Length)) - { - return this.SupportsMultipleValues; - } - - var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); - - if (separatorFound && !this.SupportsMultipleValues) - { - return false; // leading separators not allowed if we don't support multiple values. - } - - if (current == value.Length) - { - if (this.SupportsMultipleValues) - { - index = current; - } - - return this.SupportsMultipleValues; - } - - T result; - var length = this.GetParsedValueLength(value, current, out result); - - if (length == 0) - { - return false; - } - - current = current + length; - current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); - - // If we support multiple values and we've not reached the end of the string, then we must have a separator. - if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) - { - return false; - } - - index = current; - parsedValue = result; - return true; - } - - protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return this.SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); + + if (separatorFound && !this.SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (this.SupportsMultipleValues) + { + index = current; + } + + return this.SupportsMultipleValues; + } + + T result; + var length = this.GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index 78b90508f75..136dc224c81 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,43 +1,44 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -using System; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs - internal sealed class GenericHeaderParser : BaseHeaderParser - { - private GetParsedValueLengthDelegate getParsedValueLength; - - internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) - : base(supportsMultipleValues) - { - if (getParsedValueLength == null) - { - throw new ArgumentNullException(nameof(getParsedValueLength)); - } - - this.getParsedValueLength = getParsedValueLength; - } - - internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); - - protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) - { - return this.getParsedValueLength(value, startIndex, out parsedValue); - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return this.getParsedValueLength(value, startIndex, out parsedValue); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 02bd2a54762..84734368871 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,59 +1,59 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs - internal static class HeaderUtilities - { - internal static int GetNextNonEmptyOrWhitespaceIndex( - string input, - int startIndex, - bool skipEmptyValues, - out bool separatorFound) - { - Contract.Requires(input != null); - Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. - - separatorFound = false; - var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); - - if ((current == input.Length) || (input[current] != ',')) - { - return current; - } - - // If we have a separator, skip the separator and all following whitespaces. If we support - // empty values, continue until the current character is neither a separator nor a whitespace. - separatorFound = true; - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - if (skipEmptyValues) - { - while ((current < input.Length) && (input[current] == ',')) - { - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - } - } - - return current; - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + Contract.Requires(input != null); + Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 435eee12c07..6c2568be6ee 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,39 +1,40 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs - internal abstract class HttpHeaderParser - { - private bool supportsMultipleValues; - - protected HttpHeaderParser(bool supportsMultipleValues) - { - this.supportsMultipleValues = supportsMultipleValues; - } - - public bool SupportsMultipleValues - { - get { return this.supportsMultipleValues; } - } - - // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' - // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 - // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first - // non-whitespace after the separator ','. - public abstract bool TryParseValue(string value, ref int index, out T parsedValue); - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return this.supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index a0105e9b4cc..c8c82b93af5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,37 +1,37 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs - internal enum HttpParseResult - { - /// - /// Parsed successfully. - /// - Parsed, - - /// - /// Was not parsed. - /// - NotParsed, - - /// - /// Invalid format. - /// - InvalidFormat, - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult + { + /// + /// Parsed successfully. + /// + Parsed, + + /// + /// Was not parsed. + /// + NotParsed, + + /// + /// Invalid format. + /// + InvalidFormat, + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index de5e3339e27..4959d6407e5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,283 +1,283 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs - internal static class HttpRuleParser - { - internal const char CR = '\r'; - internal const char LF = '\n'; - internal const char SP = ' '; - internal const char Tab = '\t'; - internal const int MaxInt64Digits = 19; - internal const int MaxInt32Digits = 10; - - private const int MaxNestedCount = 5; - private static readonly bool[] TokenChars = CreateTokenChars(); - - internal static bool IsTokenChar(char character) - { - // Must be between 'space' (32) and 'DEL' (127) - if (character > 127) - { - return false; - } - - return TokenChars[character]; - } - - internal static int GetTokenLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - while (current < input.Length) - { - if (!IsTokenChar(input[current])) - { - return current - startIndex; - } - - current++; - } - - return input.Length - startIndex; - } - - internal static int GetWhitespaceLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - char c; - while (current < input.Length) - { - c = input[current]; - - if ((c == SP) || (c == Tab)) - { - current++; - continue; - } - - if (c == CR) - { - // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. - if ((current + 2 < input.Length) && (input[current + 1] == LF)) - { - char spaceOrTab = input[current + 2]; - if ((spaceOrTab == SP) || (spaceOrTab == Tab)) - { - current += 3; - continue; - } - } - } - - return current - startIndex; - } - - // All characters between startIndex and the end of the string are LWS characters. - return input.Length - startIndex; - } - - internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) - { - var nestedCount = 0; - return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); - } - - // quoted-pair = "\" CHAR - // CHAR = - internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && - (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); - - length = 0; - - if (input[startIndex] != '\\') - { - return HttpParseResult.NotParsed; - } - - // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) - // If so, check whether the character is in the range 0-127. If not, it's an invalid value. - if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) - { - return HttpParseResult.InvalidFormat; - } - - // We don't care what the char next to '\' is. - length = 2; - return HttpParseResult.Parsed; - } - - private static bool[] CreateTokenChars() - { - // token = 1* - // CTL = - var tokenChars = new bool[128]; // everything is false - - for (int i = 33; i < 127; i++) - { - // skip Space (32) & DEL (127) - tokenChars[i] = true; - } - - // remove separators: these are not valid token characters - tokenChars[(byte)'('] = false; - tokenChars[(byte)')'] = false; - tokenChars[(byte)'<'] = false; - tokenChars[(byte)'>'] = false; - tokenChars[(byte)'@'] = false; - tokenChars[(byte)','] = false; - tokenChars[(byte)';'] = false; - tokenChars[(byte)':'] = false; - tokenChars[(byte)'\\'] = false; - tokenChars[(byte)'"'] = false; - tokenChars[(byte)'/'] = false; - tokenChars[(byte)'['] = false; - tokenChars[(byte)']'] = false; - tokenChars[(byte)'?'] = false; - tokenChars[(byte)'='] = false; - tokenChars[(byte)'{'] = false; - tokenChars[(byte)'}'] = false; - - return tokenChars; - } - - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // CTL = - // - // Since we don't really care about the content of a quoted string or comment, we're more tolerant and - // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). - // - // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like - // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested - // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) - // is unusual. - private static HttpParseResult GetExpressionLength( - string input, - int startIndex, - char openChar, - char closeChar, - bool supportsNesting, - ref int nestedCount, - out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || - (Contract.ValueAtReturn(out length) > 0)); - - length = 0; - - if (input[startIndex] != openChar) - { - return HttpParseResult.NotParsed; - } - - var current = startIndex + 1; // Start parsing with the character next to the first open-char - while (current < input.Length) - { - // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. - // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. - var quotedPairLength = 0; - if ((current + 2 < input.Length) && - (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) - { - // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, - // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only - // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). - current = current + quotedPairLength; - continue; - } - - // If we support nested expressions and we find an open-char, then parse the nested expressions. - if (supportsNesting && (input[current] == openChar)) - { - nestedCount++; - try - { - // Check if we exceeded the number of nested calls. - if (nestedCount > MaxNestedCount) - { - return HttpParseResult.InvalidFormat; - } - - var nestedLength = 0; - HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); - - switch (nestedResult) - { - case HttpParseResult.Parsed: - current += nestedLength; // add the length of the nested expression and continue. - break; - - case HttpParseResult.NotParsed: - Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); - break; - - case HttpParseResult.InvalidFormat: - // If the nested expression is invalid, we can't continue, so we fail with invalid format. - return HttpParseResult.InvalidFormat; - - default: - Contract.Assert(false, "Unknown enum result: " + nestedResult); - break; - } - } - finally - { - nestedCount--; - } - } - - if (input[current] == closeChar) - { - length = current - startIndex + 1; - return HttpParseResult.Parsed; - } - - current++; - } - - // We didn't see the final quote, therefore we have an invalid expression string. - return HttpParseResult.InvalidFormat; - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. '\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars. + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index a0dd899445b..ad29e226ca5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,130 +1,130 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs - - // According to the RFC, in places where a "parameter" is required, the value is mandatory - // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports - // name-only values in addition to name/value pairs. - internal class NameValueHeaderValue - { - private static readonly HttpHeaderParser SingleValueParser - = new GenericHeaderParser(false, GetNameValueLength); - - private string name; - private string value; - - private NameValueHeaderValue() - { - // Used by the parser to create a new instance of this type. - } - - public string Name - { - get { return this.name; } - } - - public string Value - { - get { return this.value; } - } - - public static bool TryParse(string input, out NameValueHeaderValue parsedValue) - { - var index = 0; - return SingleValueParser.TryParseValue(input, ref index, out parsedValue); - } - - internal static int GetValueLength(string input, int startIndex) - { - Contract.Requires(input != null); - - if (startIndex >= input.Length) - { - return 0; - } - - var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (valueLength == 0) - { - // A value can either be a token or a quoted string. Check if it is a quoted string. - if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) - { - // We have an invalid value. Reset the name and return. - return 0; - } - } - - return valueLength; - } - - private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) - { - Contract.Requires(input != null); - Contract.Requires(startIndex >= 0); - - parsedValue = null; - - if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) - { - return 0; - } - - // Parse the name, i.e. in name/value string "=". Caller must remove - // leading whitespaces. - var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (nameLength == 0) - { - return 0; - } - - var name = input.Substring(startIndex, nameLength); - var current = startIndex + nameLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the separator between name and value - if ((current == input.Length) || (input[current] != '=')) - { - // We only have a name and that's OK. Return. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the value, i.e. in name/value string "=" - int valueLength = GetValueLength(input, current); - - // Value after the '=' may be empty - // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - parsedValue.value = input.Substring(current, valueLength); - current = current + valueLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + + // According to the RFC, in places where a "parameter" is required, the value is mandatory + // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports + // name-only values in addition to name/value pairs. + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return this.name; } + } + + public string Value + { + get { return this.value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + Contract.Requires(input != null); + + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + Contract.Requires(input != null); + Contract.Requires(startIndex >= 0); + + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + parsedValue.value = input.Substring(current, valueLength); + current = current + valueLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj index ed41c1df670..4c8fc26a8be 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -1,21 +1,21 @@ - - - net461 - A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - - $(NoWarn),1591,CS0618 - core- - - - - - - - - - - - - \ No newline at end of file + + + net461 + A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. + + $(NoWarn),1591,CS0618 + core- + + + + + + + + + + + + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/README.md b/src/Microsoft.AspNet.TelemetryCorrelation/README.md index 6d5f844c0cf..3ee8460a596 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/README.md +++ b/src/Microsoft.AspNet.TelemetryCorrelation/README.md @@ -7,9 +7,8 @@ Telemetry correlation http module enables cross tier telemetry tracking. ## Usage 1. Install NuGet for your app. -2. Enable diagnostics source listener using code below. Note, some - telemetry vendors like Azure Application Insights will enable it - automatically. +2. Enable diagnostics source listener using code below. Note, some telemetry + vendors like Azure Application Insights will enable it automatically. ``` csharp public class NoopDiagnosticsListener : IObserver> @@ -31,29 +30,31 @@ Telemetry correlation http module enables cross tier telemetry tracking. public void OnNext(DiagnosticListener listener) { - if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || listener.Name == "System.Net.Http" ) + if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || + listener.Name == "System.Net.Http" ) { listener.Subscribe(new NoopDiagnosticsListener()); } } } ``` -3. Double check that http module was registered in `web.config` for your - app. + +3. Double check that http module was registered in `web.config` for your app. Once enabled - this http module will: - Reads correlation http headers - Start/Stops Activity for the http request -- Ensure the Activity ambient state is transferred thru the IIS - callbacks +- Ensure the Activity ambient state is transferred thru the IIS callbacks -See http protocol [specifications][http-protocol-specification] for -details. +See http protocol [specifications][http-protocol-specification] for details. This http module is used by Application Insights. See [documentation][usage-in-ai-docs] and [code][usage-in-ai-code]. -[http-protocol-specification]: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md -[usage-in-ai-docs]: https://docs.microsoft.com/azure/application-insights/application-insights-correlation -[usage-in-ai-code]: https://github.com/Microsoft/ApplicationInsights-dotnet-server +[http-protocol-specification]: +https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md +[usage-in-ai-docs]: +https://docs.microsoft.com/azure/application-insights/application-insights-correlation +[usage-in-ai-code]: +https://github.com/Microsoft/ApplicationInsights-dotnet-server diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 86f985184d0..6427dbd3ea7 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,172 +1,172 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. - /// - public class TelemetryCorrelationHttpModule : IHttpModule - { - private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; - - // ServerVariable set only on rewritten HttpContext by URL Rewrite module. - private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; - - // ServerVariable set on every request if URL module is registered in HttpModule pipeline. - private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; - - private static MethodInfo onStepMethodInfo = null; - - static TelemetryCorrelationHttpModule() - { - onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - } - - /// - /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ParseHeaders { get; set; } = true; - - /// - public void Dispose() - { - } - - /// - public void Init(HttpApplication context) - { - context.BeginRequest += this.Application_BeginRequest; - context.EndRequest += this.Application_EndRequest; - context.Error += this.Application_Error; - - // OnExecuteRequestStep is availabile starting with 4.7.1 - // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), - // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute - if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) - { - try - { - onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); - } - } - else - { - context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute; - } - } - - /// - /// Restores Activity before each pipeline step if it was lost. - /// - /// HttpContext instance. - /// Step to be executed. - internal void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be - // simplified to just assign Current if is was lost. - // In the mean time, we are creating child Activity to restore the context. We have to send - // event with this Activity to tracing system. It created a lot of issues for listeners as - // we may potentially have a lot of them for different stages. - // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and - // stop/report it to tracing system in EndRequest. - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreActivityIfNeeded(context.Items); - } - - step(); - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - context.Items[BeginCalledFlag] = true; - } - - private void Application_PreRequestHandlerExecute(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); - ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); - } - - private void Application_EndRequest(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - // EndRequest does it's best effort to notify that request has ended - // BeginRequest has never been called - if (!context.Items.Contains(BeginCalledFlag)) - { - // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. - // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. - // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. - // When the child request completes, the parent request executes the end request notifications and completes itself. - // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. - // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. - // Track failed response activity: Different modules in the pipleline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. - if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) - { - trackActivity = false; - } - else - { - // Activity has never been started - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - } - } - - if (trackActivity) - { - ActivityHelper.StopAspNetActivity(context.Items); - } - } - - private void Application_Error(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); - - var context = ((HttpApplication)sender).Context; - - var exception = context.Error; - if (exception != null) - { - if (!context.Items.Contains(BeginCalledFlag)) - { - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - } - - ActivityHelper.WriteActivityException(context.Items, exception); - } - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// + public class TelemetryCorrelationHttpModule : IHttpModule + { + private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + + // ServerVariable set only on rewritten HttpContext by URL Rewrite module. + private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; + + // ServerVariable set on every request if URL module is registered in HttpModule pipeline. + private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; + + private static MethodInfo onStepMethodInfo = null; + + static TelemetryCorrelationHttpModule() + { + onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + } + + /// + /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ParseHeaders { get; set; } = true; + + /// + public void Dispose() + { + } + + /// + public void Init(HttpApplication context) + { + context.BeginRequest += this.Application_BeginRequest; + context.EndRequest += this.Application_EndRequest; + context.Error += this.Application_Error; + + // OnExecuteRequestStep is availabile starting with 4.7.1 + // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), + // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute + if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + { + try + { + onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); + } + catch (Exception e) + { + AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + } + } + else + { + context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute; + } + } + + /// + /// Restores Activity before each pipeline step if it was lost. + /// + /// HttpContext instance. + /// Step to be executed. + internal void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be + // simplified to just assign Current if is was lost. + // In the mean time, we are creating child Activity to restore the context. We have to send + // event with this Activity to tracing system. It created a lot of issues for listeners as + // we may potentially have a lot of them for different stages. + // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and + // stop/report it to tracing system in EndRequest. + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) + { + ActivityHelper.RestoreActivityIfNeeded(context.Items); + } + + step(); + } + + private void Application_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + context.Items[BeginCalledFlag] = true; + } + + private void Application_PreRequestHandlerExecute(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); + } + + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; + + var context = ((HttpApplication)sender).Context; + + // EndRequest does it's best effort to notify that request has ended + // BeginRequest has never been called + if (!context.Items.Contains(BeginCalledFlag)) + { + // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. + // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. + // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. + // When the child request completes, the parent request executes the end request notifications and completes itself. + // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. + // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. + // Track failed response activity: Different modules in the pipleline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. + if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) + { + trackActivity = false; + } + else + { + // Activity has never been started + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + } + } + + if (trackActivity) + { + ActivityHelper.StopAspNetActivity(context.Items); + } + } + + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + + var context = ((HttpApplication)sender).Context; + + var exception = context.Error; + if (exception != null) + { + if (!context.Items.Contains(BeginCalledFlag)) + { + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + } + + ActivityHelper.WriteActivityException(context.Items, exception); + } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs index 9f51e8dc590..20cb0a1f744 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -1,322 +1,322 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using Xunit; - - public class ActivityExtensionsTest - { - private const string TestActivityName = "Activity.Test"; - - [Fact] - public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection(); - - Assert.False(activity.Extract(requestHeaders)); - - Assert.True(string.IsNullOrEmpty(activity.ParentId)); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Extract_Empty_Traceparent() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - - Assert.False(activity.Extract(requestHeaders)); - - Assert.Equal(default, activity.ParentSpanId); - Assert.Null(activity.ParentId); - } - - [Fact] - public void Can_Extract_Multi_Line_Tracestate() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, - { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - } - - [Fact] - public void Restore_Empty_RequestId_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, string.Empty }, - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - new KeyValuePair("key4", "abc"), - new KeyValuePair("key5", "def"), - new KeyValuePair("key6", "xyz"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, - { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Theory] - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars - public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, - { ActivityExtensions.CorrelationContextHeaderName, correlationContext }, - }; - Assert.True(activity.Extract(requestHeaders)); - - var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); - if (expectedLength < 1024) - { - baggageItems.Add(new KeyValuePair("k100", "vx")); - } - - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using Xunit; + + public class ActivityExtensionsTest + { + private const string TestActivityName = "Activity.Test"; + + [Fact] + public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection(); + + Assert.False(activity.Extract(requestHeaders)); + + Assert.True(string.IsNullOrEmpty(activity.ParentId)); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Extract_Empty_Traceparent() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + + Assert.False(activity.Extract(requestHeaders)); + + Assert.Equal(default, activity.ParentSpanId); + Assert.Null(activity.ParentId); + } + + [Fact] + public void Can_Extract_Multi_Line_Tracestate() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, + { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + } + + [Fact] + public void Restore_Empty_RequestId_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, string.Empty }, + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + new KeyValuePair("key4", "abc"), + new KeyValuePair("key5", "def"), + new KeyValuePair("key6", "xyz"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Theory] + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars + public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, + { ActivityExtensions.CorrelationContextHeaderName, correlationContext }, + }; + Assert.True(activity.Extract(requestHeaders)); + + var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); + if (expectedLength < 1024) + { + baggageItems.Add(new KeyValuePair("k100", "vx")); + } + + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index 40e86a9e8d7..cf903b34f32 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,569 +1,569 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using Xunit; - - public class ActivityHelperTest : IDisposable - { - private const string TestActivityName = "Activity.Test"; - private readonly List> baggageItems; - private readonly string baggageInHeader; - private IDisposable subscriptionAllListeners; - private IDisposable subscriptionAspNetListener; - - public ActivityHelperTest() - { - this.baggageItems = new List> - { - new KeyValuePair("TestKey1", "123"), - new KeyValuePair("TestKey2", "456"), - new KeyValuePair("TestKey1", "789"), - }; - - this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - - // reset static fields - var allListenerField = typeof(DiagnosticListener). - GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); - allListenerField.SetValue(null, null); - var aspnetListenerField = typeof(ActivityHelper). - GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); - aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); - } - - public void Dispose() - { - this.subscriptionAspNetListener?.Dispose(); - this.subscriptionAllListeners?.Dispose(); - } - - [Fact] - public void Can_Restore_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Same(Activity.Current, rootActivity); - } - - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - context.Items.Remove(ActivityHelper.ActivityKey); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableAll(); - ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); - - Assert.Null(Activity.Current); - } - - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableAll(); - var root = new Activity("root").Start(); - - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ActivityKey] = root; - - var module = new TelemetryCorrelationHttpModule(); - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Equal(root, Activity.Current); - } - - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - this.EnableAspNetListenerOnly(); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - var child = new Activity("child").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void OnImportActivity_Is_Called() - { - bool onImportIsCalled = false; - Activity importedActivity = null; - this.EnableAll(onImport: (activity, _) => - { - onImportIsCalled = true; - importedActivity = activity; - Assert.Null(Activity.Current); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - Assert.True(onImportIsCalled); - Assert.NotNull(importedActivity); - Assert.Equal(importedActivity, Activity.Current); - Assert.Equal(importedActivity, rootActivity); - } - - [Fact] - public void OnImportActivity_Can_Set_Parent() - { - this.EnableAll(onImport: (activity, _) => - { - Assert.Null(activity.ParentId); - activity.SetParentId("|guid.123."); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - Assert.Equal("|guid.123.", Activity.Current.ParentId); - } - - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - new Activity("child").Start(); - - for (int i = 0; i < 2; i++) - { - await Task.Run(() => - { - // when we enter this method, Current is 'child' activity - Activity.Current.Stop(); - - // here Current is 'parent', but only in this execution context - }); - } - - // when we return back here, in the 'parent' execution context - // Current is still 'child' activity - changes in child context (inside Task.Run) - // do not affect 'parent' context in which Task.Run is called. - // But 'child' Activity is stopped, thus consequent calls to Stop will - // not update Current - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } - - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerOnly(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndDisableActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); - var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); - var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.False(rootActivity.Recorded); - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); - - Assert.NotNull(rootActivity); - Assert.Null(rootActivity.ParentId); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } - - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); - } - - private Activity CreateActivity() - { - var activity = new Activity(TestActivityName); - this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); - - return activity; - } - - private void EnableAll(Action> onNext = null, Action onImport = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, a1, a2) => true, - (a, o) => onImport?.Invoke(a, o), - (a, o) => { }); - } - }); - } - - private void EnableAspNetListenerAndDisableActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName && arg1 == null); - } - }); - } - - private void EnableAspNetListenerAndActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName); - } - }); - } - - private void EnableAspNetListenerOnly(Action> onNext = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - activityName => false); - } - }); - } - - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new NameValueCollection(); - - public override NameValueCollection Headers => this.headers; - - public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); - } - - private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase - { - public TestUnvalidatedRequestValues(NameValueCollection headers) - { - this.Headers = headers; - } - - public override NameValueCollection Headers { get; } - } - - private class TestHttpResponse : HttpResponseBase - { - } - - private class TestHttpServerUtility : HttpServerUtilityBase - { - private readonly HttpContextBase context; - - public TestHttpServerUtility(HttpContextBase context) - { - this.context = context; - } - - public override Exception GetLastError() - { - return this.context.Error; - } - } - - private class TestHttpContext : HttpContextBase - { - private readonly Hashtable items; - - public TestHttpContext(Exception error = null) - { - this.Server = new TestHttpServerUtility(this); - this.items = new Hashtable(); - this.Error = error; - } - - public override HttpRequestBase Request { get; } = new TestHttpRequest(); - - /// - public override IDictionary Items => this.items; - - public override Exception Error { get; } - - public override HttpServerUtilityBase Server { get; } - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Xunit; + + public class ActivityHelperTest : IDisposable + { + private const string TestActivityName = "Activity.Test"; + private readonly List> baggageItems; + private readonly string baggageInHeader; + private IDisposable subscriptionAllListeners; + private IDisposable subscriptionAspNetListener; + + public ActivityHelperTest() + { + this.baggageItems = new List> + { + new KeyValuePair("TestKey1", "123"), + new KeyValuePair("TestKey2", "456"), + new KeyValuePair("TestKey1", "789"), + }; + + this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + + // reset static fields + var allListenerField = typeof(DiagnosticListener). + GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); + allListenerField.SetValue(null, null); + var aspnetListenerField = typeof(ActivityHelper). + GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); + aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); + } + + public void Dispose() + { + this.subscriptionAspNetListener?.Dispose(); + this.subscriptionAllListeners?.Dispose(); + } + + [Fact] + public void Can_Restore_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Same(Activity.Current, rootActivity); + } + + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + context.Items.Remove(ActivityHelper.ActivityKey); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableAll(); + ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); + + Assert.Null(Activity.Current); + } + + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableAll(); + var root = new Activity("root").Start(); + + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ActivityKey] = root; + + var module = new TelemetryCorrelationHttpModule(); + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Equal(root, Activity.Current); + } + + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + this.EnableAspNetListenerOnly(); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + var child = new Activity("child").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void OnImportActivity_Is_Called() + { + bool onImportIsCalled = false; + Activity importedActivity = null; + this.EnableAll(onImport: (activity, _) => + { + onImportIsCalled = true; + importedActivity = activity; + Assert.Null(Activity.Current); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + Assert.True(onImportIsCalled); + Assert.NotNull(importedActivity); + Assert.Equal(importedActivity, Activity.Current); + Assert.Equal(importedActivity, rootActivity); + } + + [Fact] + public void OnImportActivity_Can_Set_Parent() + { + this.EnableAll(onImport: (activity, _) => + { + Assert.Null(activity.ParentId); + activity.SetParentId("|guid.123."); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + Assert.Equal("|guid.123.", Activity.Current.ParentId); + } + + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + new Activity("child").Start(); + + for (int i = 0; i < 2; i++) + { + await Task.Run(() => + { + // when we enter this method, Current is 'child' activity + Activity.Current.Stop(); + + // here Current is 'parent', but only in this execution context + }); + } + + // when we return back here, in the 'parent' execution context + // Current is still 'child' activity - changes in child context (inside Task.Run) + // do not affect 'parent' context in which Task.Run is called. + // But 'child' Activity is stopped, thus consequent calls to Stop will + // not update Current + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); + } + + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerOnly(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndDisableActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); + var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); + var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.False(rootActivity.Recorded); + + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); + Assert.True(rootActivity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); + + Assert.NotNull(rootActivity); + Assert.Null(rootActivity.ParentId); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } + + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); + } + + private Activity CreateActivity() + { + var activity = new Activity(TestActivityName); + this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); + + return activity; + } + + private void EnableAll(Action> onNext = null, Action onImport = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, a1, a2) => true, + (a, o) => onImport?.Invoke(a, o), + (a, o) => { }); + } + }); + } + + private void EnableAspNetListenerAndDisableActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName && arg1 == null); + } + }); + } + + private void EnableAspNetListenerAndActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName); + } + }); + } + + private void EnableAspNetListenerOnly(Action> onNext = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + activityName => false); + } + }); + } + + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new NameValueCollection(); + + public override NameValueCollection Headers => this.headers; + + public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); + } + + private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase + { + public TestUnvalidatedRequestValues(NameValueCollection headers) + { + this.Headers = headers; + } + + public override NameValueCollection Headers { get; } + } + + private class TestHttpResponse : HttpResponseBase + { + } + + private class TestHttpServerUtility : HttpServerUtilityBase + { + private readonly HttpContextBase context; + + public TestHttpServerUtility(HttpContextBase context) + { + this.context = context; + } + + public override Exception GetLastError() + { + return this.context.Error; + } + } + + private class TestHttpContext : HttpContextBase + { + private readonly Hashtable items; + + public TestHttpContext(Exception error = null) + { + this.Server = new TestHttpServerUtility(this); + this.items = new Hashtable(); + this.Error = error; + } + + public override HttpRequestBase Request { get; } = new TestHttpRequest(); + + /// + public override IDictionary Items => this.items; + + public override Exception Error { get; } + + public override HttpServerUtilityBase Server { get; } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index eb6feab52a5..48e1fd70e31 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,103 +1,103 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Threading; - using System.Web; - using System.Web.Hosting; - - internal class HttpContextHelper - { - public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) - { - Thread.GetDomain().SetData(".appPath", string.Empty); - Thread.GetDomain().SetData(".appVPath", string.Empty); - - var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); - var context = new HttpContext(workerRequest); - HttpContext.Current = context; - return context; - } - - public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) - { - var context = GetFakeHttpContext(page, query, headers); - return new HttpContextWrapper(context); - } - - private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest - { - private readonly IDictionary headers; - - public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) - : base(page, query, output) - { - if (headers != null) - { - this.headers = headers; - } - else - { - this.headers = new Dictionary(); - } - } - - public override string[][] GetUnknownRequestHeaders() - { - List result = new List(); - - foreach (var header in this.headers) - { - result.Add(new string[] { header.Key, header.Value }); - } - - var baseResult = base.GetUnknownRequestHeaders(); - if (baseResult != null) - { - result.AddRange(baseResult); - } - - return result.ToArray(); - } - - public override string GetUnknownRequestHeader(string name) - { - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetUnknownRequestHeader(name); - } - - public override string GetKnownRequestHeader(int index) - { - var name = HttpWorkerRequest.GetKnownRequestHeaderName(index); - - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetKnownRequestHeader(index); - } - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Threading; + using System.Web; + using System.Web.Hosting; + + internal class HttpContextHelper + { + public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) + { + Thread.GetDomain().SetData(".appPath", string.Empty); + Thread.GetDomain().SetData(".appVPath", string.Empty); + + var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); + var context = new HttpContext(workerRequest); + HttpContext.Current = context; + return context; + } + + public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) + { + var context = GetFakeHttpContext(page, query, headers); + return new HttpContextWrapper(context); + } + + private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest + { + private readonly IDictionary headers; + + public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) + : base(page, query, output) + { + if (headers != null) + { + this.headers = headers; + } + else + { + this.headers = new Dictionary(); + } + } + + public override string[][] GetUnknownRequestHeaders() + { + List result = new List(); + + foreach (var header in this.headers) + { + result.Add(new string[] { header.Key, header.Value }); + } + + var baseResult = base.GetUnknownRequestHeaders(); + if (baseResult != null) + { + result.AddRange(baseResult); + } + + return result.ToArray(); + } + + public override string GetUnknownRequestHeader(string name) + { + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetUnknownRequestHeader(name); + } + + public override string GetKnownRequestHeader(int index) + { + var name = HttpWorkerRequest.GetKnownRequestHeaderName(index); + + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetKnownRequestHeader(index); + } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj index 93819ccf2b3..d8d179c4587 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -1,33 +1,33 @@ - - - Unit test project for ASP.NET HttpModule - net461 - false - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - Resources\web.config.install.xdt - - - Resources\web.config.uninstall.xdt - - - \ No newline at end of file + + + Unit test project for ASP.NET HttpModule + net461 + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + Resources\web.config.install.xdt + + + Resources\web.config.uninstall.xdt + + + diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index 059b1c456ee..ca6260088cf 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,28 +1,28 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Reflection; - - internal static class PropertyExtensions - { - public static object GetProperty(this object obj, string propertyName) - { - return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Reflection; + + internal static class PropertyExtensions + { + public static object GetProperty(this object obj, string propertyName) + { + return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index bd380d3e5e3..e6787d30c85 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,44 +1,44 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections.Generic; - - internal class TestDiagnosticListener : IObserver> - { - private readonly Action> onNextCallBack; - - public TestDiagnosticListener(Action> onNext) - { - this.onNextCallBack = onNext; - } - - public void OnCompleted() - { - } - - public void OnError(Exception error) - { - } - - public void OnNext(KeyValuePair value) - { - this.onNextCallBack?.Invoke(value); - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections.Generic; + + internal class TestDiagnosticListener : IObserver> + { + private readonly Action> onNextCallBack; + + public TestDiagnosticListener(Action> onNext) + { + this.onNextCallBack = onNext; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + this.onNextCallBack?.Invoke(value); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 208a5e20988..2f4e023b989 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,411 +1,411 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.uninstall.xdt"; - - [Fact] - public void VerifyInstallationToBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithTypeRenamingWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateNewerVersionWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithIntegratedModeWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallWithIntegratedPrecondition() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToEmptyWebConfig() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithoutModules() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using (var transformation = new XmlTransformation(stream, null)) - { - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } -} \ No newline at end of file +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.uninstall.xdt"; + + [Fact] + public void VerifyInstallationToBasicWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateWithTypeRenamingWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateNewerVersionWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUpdateWithIntegratedModeWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallationWithBasicWebConfig() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallWithIntegratedPrecondition() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyUninstallationWithUserModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToWebConfigWithUserModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToEmptyWebConfig() + { + const string OriginalWebConfigContent = @""; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToWebConfigWithoutModules() + { + const string OriginalWebConfigContent = @""; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } + + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try + { + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using (var transformation = new XmlTransformation(stream, null)) + { + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); + } + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + return result; + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 4d8b3545468..1b7cd8f6ae2 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,441 +1,441 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - - [Fact] - public void VerifyInstallationWhenNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using (var transformation = new XmlTransformation(stream, null)) - { - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } -} +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigWithLocationTagTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + + [Fact] + public void VerifyInstallationWhenNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithNoModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + [Fact] + public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() + { + const string OriginalWebConfigContent = @" + + + + + + + + + + + + + + "; + + const string ExpectedWebConfigContent = @" + + + + + + + + + + + + + + + + + + "; + + var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); + this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); + } + + private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) + { + return this.ApplyTransformation(originalConfiguration, resourceName); + } + + private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) + { + Assert.True( + XNode.DeepEquals( + transformedWebConfig.FirstNode, + XDocument.Parse(expectedConfigContent).FirstNode)); + } + + private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) + { + XDocument result; + Stream stream = null; + try + { + stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); + var document = new XmlTransformableDocument(); + using (var transformation = new XmlTransformation(stream, null)) + { + stream = null; + document.LoadXml(originalConfiguration); + transformation.Apply(document); + result = XDocument.Parse(document.OuterXml); + } + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + return result; + } + } +}