diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/TelemetryDispatchMessageInspectorForOneWayOperationsTests.netfx.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/TelemetryDispatchMessageInspectorForOneWayOperationsTests.netfx.cs new file mode 100644 index 0000000000..868a5e86f7 --- /dev/null +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/TelemetryDispatchMessageInspectorForOneWayOperationsTests.netfx.cs @@ -0,0 +1,147 @@ +// +// 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. +// +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.ServiceModel; +using System.Threading; +using OpenTelemetry.Contrib.Instrumentation.Wcf.Tests.Tools; +using OpenTelemetry.Trace; +using Xunit; +using Xunit.Abstractions; + +namespace OpenTelemetry.Contrib.Instrumentation.Wcf.Tests +{ + [Collection("WCF")] + public class TelemetryDispatchMessageInspectorForOneWayOperationsTests : IDisposable + { + private readonly ITestOutputHelper output; + private readonly Uri serviceBaseUri; + private readonly ServiceHost serviceHost; + + private readonly EventWaitHandle thrownExceptionsHandle = new EventWaitHandle(false, EventResetMode.ManualReset); + private readonly List thrownExceptions = new List(); + + public TelemetryDispatchMessageInspectorForOneWayOperationsTests(ITestOutputHelper outputHelper) + { + this.output = outputHelper; + + Random random = new Random(); + var retryCount = 5; + while (retryCount > 0) + { + try + { + this.serviceBaseUri = new Uri($"net.tcp://localhost:{random.Next(2000, 5000)}/"); + this.serviceHost = new ServiceHost(new Service(), this.serviceBaseUri); + + var endpoint = this.serviceHost.AddServiceEndpoint( + typeof(IServiceContract), + new NetTcpBinding(), + "/Service"); + endpoint.Behaviors.Add(new TelemetryEndpointBehavior()); + + this.serviceHost.Description.Behaviors.Add( + new ErrorHandlerServiceBehavior(this.thrownExceptionsHandle, ex => this.thrownExceptions.Add(ex))); + + this.serviceHost.Open(); + + break; + } + catch (Exception ex) + { + this.output.WriteLine(ex.ToString()); + if (this.serviceHost.State == CommunicationState.Faulted) + { + this.serviceHost.Abort(); + } + else + { + this.serviceHost.Close(); + } + + this.serviceHost = null; + retryCount--; + } + } + + if (this.serviceHost == null) + { + throw new InvalidOperationException("ServiceHost could not be started."); + } + } + + public void Dispose() + { + this.serviceHost?.Close(); + this.thrownExceptionsHandle?.Dispose(); + } + + [Fact] + public void IncomingRequestOneWayOperationInstrumentationTest() + { + List stoppedActivities = new List(); + + using ActivityListener activityListener = new ActivityListener + { + ShouldListenTo = activitySource => true, + ActivityStopped = activity => stoppedActivities.Add(activity), + }; + + ActivitySource.AddActivityListener(activityListener); + TracerProvider tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddWcfInstrumentation() + .Build(); + + ServiceClient client = new ServiceClient( + new NetTcpBinding(), + new EndpointAddress(new Uri(this.serviceBaseUri, "/Service"))); + + try + { + client.ExecuteWithOneWay(new ServiceRequest()); + this.thrownExceptionsHandle.WaitOne(3000); + } + finally + { + if (client.State == CommunicationState.Faulted) + { + client.Abort(); + } + else + { + client.Close(); + } + + tracerProvider?.Shutdown(); + tracerProvider?.Dispose(); + + WcfInstrumentationActivitySource.Options = null; + } + + // Assert + Assert.Empty(this.thrownExceptions); + + Activity activity = stoppedActivities[0]; + Assert.Equal("http://opentelemetry.io/Service/ExecuteWithOneWay", activity.DisplayName); + Assert.Equal("ExecuteWithOneWay", activity.TagObjects.FirstOrDefault(t => t.Key == WcfInstrumentationConstants.RpcMethodTag).Value); + Assert.DoesNotContain(activity.TagObjects, t => t.Key == WcfInstrumentationConstants.SoapReplyActionTag); + } + } +} +#endif diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandler.netfx.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandler.netfx.cs new file mode 100644 index 0000000000..3e2a69e21e --- /dev/null +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandler.netfx.cs @@ -0,0 +1,48 @@ +// +// 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. +// +#if NETFRAMEWORK +using System; +using System.ServiceModel.Channels; +using System.ServiceModel.Dispatcher; +using System.Threading; + +namespace OpenTelemetry.Contrib.Instrumentation.Wcf.Tests.Tools +{ + internal class ErrorHandler : IErrorHandler + { + private readonly EventWaitHandle handler; + private readonly Action log; + + public ErrorHandler(EventWaitHandle handler, Action log) + { + this.handler = handler; + this.log = log; + } + + public bool HandleError(Exception error) + { + this.log(error); + this.handler.Set(); + + return true; + } + + public void ProvideFault(Exception error, MessageVersion version, ref Message fault) + { + } + } +} +#endif diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandlerServiceBehavior.netfx.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandlerServiceBehavior.netfx.cs new file mode 100644 index 0000000000..b32cc2b0e3 --- /dev/null +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/Tools/ErrorHandlerServiceBehavior.netfx.cs @@ -0,0 +1,55 @@ +// +// 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. +// +#if NETFRAMEWORK +using System; +using System.Collections.ObjectModel; +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Dispatcher; +using System.Threading; + +namespace OpenTelemetry.Contrib.Instrumentation.Wcf.Tests.Tools +{ + internal class ErrorHandlerServiceBehavior : IServiceBehavior + { + private readonly EventWaitHandle handler; + private readonly Action action; + + public ErrorHandlerServiceBehavior(EventWaitHandle handler, Action action) + { + this.handler = handler; + this.action = action; + } + + public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters) + { + } + + public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) + { + foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers) + { + dispatcher.ErrorHandlers.Add(new ErrorHandler(this.handler, this.action)); + } + } + + public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) + { + } + } +} +#endif diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/IServiceContract.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/IServiceContract.cs index d4358f2b6d..49746d2178 100644 --- a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/IServiceContract.cs +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/IServiceContract.cs @@ -27,5 +27,8 @@ public interface IServiceContract [OperationContract(Action = "")] Task ExecuteWithEmptyActionNameAsync(ServiceRequest request); + + [OperationContract(IsOneWay = true)] + void ExecuteWithOneWay(ServiceRequest request); } } diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/Service.netfx.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/Service.netfx.cs index aa75e5e8bf..0f8aa6c9f5 100644 --- a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/Service.netfx.cs +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/Service.netfx.cs @@ -44,6 +44,10 @@ public Task ExecuteWithEmptyActionNameAsync(ServiceRequest requ Payload = $"RSP: {request.Payload}", }); } + + public void ExecuteWithOneWay(ServiceRequest request) + { + } } } #endif diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/ServiceClient.cs b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/ServiceClient.cs index 98792bb3c1..a3af914511 100644 --- a/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/ServiceClient.cs +++ b/test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/WCF/ServiceClient.cs @@ -32,5 +32,8 @@ public Task ExecuteAsync(ServiceRequest request) public Task ExecuteWithEmptyActionNameAsync(ServiceRequest request) => this.Channel.ExecuteWithEmptyActionNameAsync(request); + + public void ExecuteWithOneWay(ServiceRequest request) + => this.Channel.ExecuteWithOneWay(request); } }