diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d6afe9c5aa..be422efc08 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -178,6 +178,12 @@ updates:
interval: "daily"
open-pull-requests-limit: 20
+ - package-ecosystem: nuget
+ directory: /test/test-applications/integrations/TestApplication.Plugins
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 20
+
- package-ecosystem: nuget
directory: /test/test-applications/integrations/TestApplication.Smoke
schedule:
diff --git a/OpenTelemetry.AutoInstrumentation.sln b/OpenTelemetry.AutoInstrumentation.sln
index 70a3e5f3c4..a048eba46a 100644
--- a/OpenTelemetry.AutoInstrumentation.sln
+++ b/OpenTelemetry.AutoInstrumentation.sln
@@ -127,6 +127,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.MongoDB", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.SqlClient", "test\test-applications\integrations\TestApplication.SqlClient\TestApplication.SqlClient.csproj", "{2120CFA2-9D16-45F0-A333-B0385C371509}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.Plugins", "test\test-applications\integrations\TestApplication.Plugins\TestApplication.Plugins.csproj", "{42BABA6C-1954-4F52-85C8-D336C00F06D9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -503,6 +505,18 @@ Global
{2120CFA2-9D16-45F0-A333-B0385C371509}.Release|x64.Build.0 = Release|x64
{2120CFA2-9D16-45F0-A333-B0385C371509}.Release|x86.ActiveCfg = Release|x86
{2120CFA2-9D16-45F0-A333-B0385C371509}.Release|x86.Build.0 = Release|x86
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|Any CPU.Build.0 = Debug|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|x64.ActiveCfg = Debug|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|x64.Build.0 = Debug|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|x86.ActiveCfg = Debug|x86
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Debug|x86.Build.0 = Debug|x86
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|Any CPU.ActiveCfg = Release|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|Any CPU.Build.0 = Release|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|x64.ActiveCfg = Release|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|x64.Build.0 = Release|x64
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|x86.ActiveCfg = Release|x86
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -545,6 +559,7 @@ Global
{6ACF2A48-427F-4EF2-81B5-EE9CDB0476C6} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{3B77F975-BF66-4218-9982-F48D3271DDFE} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{2120CFA2-9D16-45F0-A333-B0385C371509} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
+ {42BABA6C-1954-4F52-85C8-D336C00F06D9} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
diff --git a/test/IntegrationTests/AspNetTests.cs b/test/IntegrationTests/AspNetTests.cs
index 894a132bfa..9b39182d28 100644
--- a/test/IntegrationTests/AspNetTests.cs
+++ b/test/IntegrationTests/AspNetTests.cs
@@ -45,20 +45,17 @@ public async Task SubmitsTraces()
{
Assert.True(EnvironmentTools.IsWindowsAdministrator(), "This test requires Windows Administrator privileges.");
- var agentPort = TcpPortProvider.GetOpenPort();
- var webPort = TcpPortProvider.GetOpenPort();
-
- var testSettings = new TestSettings
- {
- TracesSettings = new TracesSettings { Port = agentPort }
- };
-
// Using "*" as host requires Administrator. This is needed to make the mock collector endpoint
// accessible to the Windows docker container where the test application is executed by binding
// the endpoint to all network interfaces. In order to do that it is necessary to open the port
// on the firewall.
- using var fwPort = FirewallHelper.OpenWinPort(agentPort, Output);
- using var agent = new MockZipkinCollector(Output, agentPort, host: "*");
+ using var agent = new MockZipkinCollector(Output, host: "*");
+ using var fwPort = FirewallHelper.OpenWinPort(agent.Port, Output);
+ var testSettings = new TestSettings
+ {
+ TracesSettings = new TracesSettings { Port = agent.Port }
+ };
+ var webPort = TcpPortProvider.GetOpenPort();
using var container = await StartContainerAsync(testSettings, webPort);
var client = new HttpClient();
@@ -81,21 +78,19 @@ public async Task SubmitsTraces()
[Trait("Containers", "Windows")]
public async Task SubmitMetrics()
{
- var collectorPort = TcpPortProvider.GetOpenPort();
- var webPort = TcpPortProvider.GetOpenPort();
const int expectedMetricRequests = 1;
- var testSettings = new TestSettings
- {
- MetricsSettings = new MetricsSettings { Port = collectorPort },
- };
-
// Using "*" as host requires Administrator. This is needed to make the mock collector endpoint
// accessible to the Windows docker container where the test application is executed by binding
// the endpoint to all network interfaces. In order to do that it is necessary to open the port
// on the firewall.
- using var fwPort = FirewallHelper.OpenWinPort(collectorPort, Output);
- using var collector = new MockCollector(Output, collectorPort, host: "*");
+ using var collector = new MockMetricsCollector(Output, host: "*");
+ using var fwPort = FirewallHelper.OpenWinPort(collector.Port, Output);
+ var testSettings = new TestSettings
+ {
+ MetricsSettings = new MetricsSettings { Port = collector.Port },
+ };
+ var webPort = TcpPortProvider.GetOpenPort();
using var container = await StartContainerAsync(testSettings, webPort);
var client = new HttpClient();
diff --git a/test/IntegrationTests/GraphQLTests.cs b/test/IntegrationTests/GraphQLTests.cs
index f0ed5c649b..243767ebef 100644
--- a/test/IntegrationTests/GraphQLTests.cs
+++ b/test/IntegrationTests/GraphQLTests.cs
@@ -82,10 +82,8 @@ public void SubmitsTraces()
{
SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName);
- int agentPort = TcpPortProvider.GetOpenPort();
int aspNetCorePort = TcpPortProvider.GetOpenPort();
-
- using (var agent = new MockZipkinCollector(Output, agentPort))
+ using (var agent = new MockZipkinCollector(Output))
using (Process process = StartTestApplication(agent.Port, arguments: null, packageVersion: string.Empty, aspNetCorePort: aspNetCorePort))
{
if (process.HasExited)
diff --git a/test/IntegrationTests/Helpers/EnvironmentHelper.cs b/test/IntegrationTests/Helpers/EnvironmentHelper.cs
index 4e065e5642..940a953988 100644
--- a/test/IntegrationTests/Helpers/EnvironmentHelper.cs
+++ b/test/IntegrationTests/Helpers/EnvironmentHelper.cs
@@ -207,7 +207,7 @@ public void SetEnvironmentVariables(
if (testSettings.MetricsSettings != null)
{
environmentVariables["OTEL_METRICS_EXPORTER"] = testSettings.MetricsSettings.Exporter;
- environmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = $"http://127.0.0.1:{testSettings.MetricsSettings.Port}";
+ environmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = $"http://localhost:{testSettings.MetricsSettings.Port}";
}
// for ASP.NET Core test applications, set the server's port
diff --git a/test/IntegrationTests/Helpers/MockCollector.cs b/test/IntegrationTests/Helpers/MockCollector.cs
deleted file mode 100644
index d2f2925362..0000000000
--- a/test/IntegrationTests/Helpers/MockCollector.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-//
-// 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.Generic;
-using System.Collections.Immutable;
-using System.Collections.Specialized;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using Google.Protobuf;
-using IntegrationTests.Helpers.Models;
-using Opentelemetry.Proto.Collector.Metrics.V1;
-using Xunit.Abstractions;
-
-namespace IntegrationTests.Helpers;
-
-public class MockCollector : IDisposable
-{
- private static readonly TimeSpan DefaultWaitTimeout = TimeSpan.FromSeconds(20);
-
- private readonly ITestOutputHelper _output;
- private readonly HttpListener _listener;
- private readonly Thread _listenerThread;
-
- public MockCollector(ITestOutputHelper output, int port = 4318, int retries = 5, string host = "127.0.0.1")
- {
- _output = output;
-
- // try up to 5 consecutive ports before giving up
- while (true)
- {
- // seems like we can't reuse a listener if it fails to start,
- // so create a new listener each time we retry
- var listener = new HttpListener();
-
- try
- {
- listener.Start();
- string prefix = new UriBuilder("http", host, port).ToString();
- listener.Prefixes.Add(prefix);
-
- // successfully listening
- Port = port;
- _listener = listener;
-
- _listenerThread = new Thread(HandleHttpRequests);
- _listenerThread.Start();
-
- WriteOutput($"Running on port '{Port}'");
-
- return;
- }
- catch (HttpListenerException) when (retries > 0)
- {
- // only catch the exception if there are retries left
- port++;
- retries--;
- }
-
- // always close listener if exception is thrown,
- // whether it was caught or not
- listener.Close();
-
- WriteOutput("Listener shut down. Could not find available port.");
- }
- }
-
- public event EventHandler> RequestReceived;
-
- public event EventHandler> RequestDeserialized;
-
- ///
- /// Gets or sets a value indicating whether to skip deserialization of metrics.
- ///
- public bool ShouldDeserializeMetrics { get; set; } = true;
-
- ///
- /// Gets the TCP port that this Agent is listening on.
- /// Can be different from 's initialPort
- /// parameter if listening on that port fails.
- ///
- public int Port { get; }
-
- ///
- /// Gets the filters used to filter out metrics we don't want to look at for a test.
- ///
- public List> MetricFilters { get; private set; } = new List>();
-
- public IImmutableList MetricsMessages { get; private set; } = ImmutableList.Empty;
-
- public IImmutableList RequestHeaders { get; private set; } = ImmutableList.Empty;
-
- ///
- /// Wait for the given number of metric requests to appear.
- ///
- /// The expected number of metric requests.
- /// The timeout
- /// The list of metric requests.
- public IImmutableList WaitForMetrics(
- int count,
- TimeSpan? timeout = null)
- {
- timeout ??= DefaultWaitTimeout;
- var deadline = DateTime.Now.Add(timeout.Value);
-
- IImmutableList relevantMetricRequests = ImmutableList.Empty;
-
- while (DateTime.Now < deadline)
- {
- relevantMetricRequests =
- MetricsMessages
- .Where(m => MetricFilters.All(shouldReturn => shouldReturn(m)))
- .ToImmutableList();
-
- if (relevantMetricRequests.Count >= count)
- {
- break;
- }
-
- Thread.Sleep(500);
- }
-
- return relevantMetricRequests;
- }
-
- public void Dispose()
- {
- WriteOutput($"Shutting down. Total metric requests received: '{MetricsMessages.Count}'");
- _listener?.Stop();
- }
-
- protected virtual void OnRequestReceived(HttpListenerContext context)
- {
- RequestReceived?.Invoke(this, new EventArgs(context));
- }
-
- protected virtual void OnRequestDeserialized(ExportMetricsServiceRequest metricsRequest)
- {
- RequestDeserialized?.Invoke(this, new EventArgs(metricsRequest));
- }
-
- private void AssertHeader(
- NameValueCollection headers,
- string headerKey,
- Func assertion)
- {
- var header = headers.Get(headerKey);
-
- if (string.IsNullOrEmpty(header))
- {
- throw new Exception($"Every submission to the agent should have a {headerKey} header.");
- }
-
- if (!assertion(header))
- {
- throw new Exception($"Failed assertion for {headerKey} on {header}");
- }
- }
-
- private void HandleHttpRequests()
- {
- while (_listener.IsListening)
- {
- try
- {
- var ctx = _listener.GetContext();
- OnRequestReceived(ctx);
-
- if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
- {
- CreateHealthResponse(ctx);
-
- continue;
- }
-
- if (ctx.Request.RawUrl.Equals("/v1/metrics", StringComparison.OrdinalIgnoreCase))
- {
- if (ShouldDeserializeMetrics)
- {
- var metricsMessage = ExportMetricsServiceRequest.Parser.ParseFrom(ctx.Request.InputStream);
- OnRequestDeserialized(metricsMessage);
-
- lock (this)
- {
- // we only need to lock when replacing the metric collection,
- // not when reading it because it is immutable
- MetricsMessages = MetricsMessages.Add(metricsMessage);
- RequestHeaders = RequestHeaders.Add(new NameValueCollection(ctx.Request.Headers));
- }
- }
-
- // NOTE: HttpStreamRequest doesn't support Transfer-Encoding: Chunked
- // (Setting content-length avoids that)
- ctx.Response.ContentType = "application/x-protobuf";
- ctx.Response.StatusCode = (int)HttpStatusCode.OK;
- var responseMessage = new ExportMetricsServiceResponse();
- ctx.Response.ContentLength64 = responseMessage.CalculateSize();
- responseMessage.WriteTo(ctx.Response.OutputStream);
- ctx.Response.Close();
- continue;
- }
-
- // We received an unsupported request
- ctx.Response.StatusCode = (int)HttpStatusCode.NotImplemented;
- ctx.Response.Close();
- }
- catch (HttpListenerException)
- {
- // listener was stopped,
- // ignore to let the loop end and the method return
- }
- catch (ObjectDisposedException)
- {
- // the response has been already disposed.
- }
- catch (InvalidOperationException)
- {
- // this can occur when setting Response.ContentLength64, with the framework claiming that the response has already been submitted
- // for now ignore, and we'll see if this introduces downstream issues
- }
- catch (Exception) when (!_listener.IsListening)
- {
- // we don't care about any exception when listener is stopped
- }
- }
- }
-
- private void WriteOutput(string msg)
- {
- const string name = nameof(MockCollector);
-
- _output.WriteLine($"[{name}]: {msg}");
- }
-
- private void CreateHealthResponse(HttpListenerContext ctx)
- {
- ctx.Response.ContentType = "text/plain";
- var buffer = Encoding.UTF8.GetBytes("OK");
- ctx.Response.ContentLength64 = buffer.LongLength;
- ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
- ctx.Response.StatusCode = (int)HttpStatusCode.OK;
- ctx.Response.Close();
- }
-}
diff --git a/test/IntegrationTests/Helpers/MockMetricsCollector.cs b/test/IntegrationTests/Helpers/MockMetricsCollector.cs
new file mode 100644
index 0000000000..abed2b50ac
--- /dev/null
+++ b/test/IntegrationTests/Helpers/MockMetricsCollector.cs
@@ -0,0 +1,174 @@
+//
+// 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.Generic;
+using System.Collections.Immutable;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using Google.Protobuf;
+using IntegrationTests.Helpers.Models;
+using Opentelemetry.Proto.Collector.Metrics.V1;
+using Xunit.Abstractions;
+
+namespace IntegrationTests.Helpers;
+
+public class MockMetricsCollector : IDisposable
+{
+ private static readonly TimeSpan DefaultWaitTimeout = TimeSpan.FromSeconds(20);
+
+ private readonly ITestOutputHelper _output;
+ private readonly TestHttpListener _listener;
+
+ public MockMetricsCollector(ITestOutputHelper output, string host = "localhost")
+ {
+ _output = output;
+ _listener = new(output, HandleHttpRequests, host);
+ }
+
+ public event EventHandler> RequestReceived;
+
+ public event EventHandler> RequestDeserialized;
+
+ ///
+ /// Gets or sets a value indicating whether to skip deserialization of metrics.
+ ///
+ public bool ShouldDeserializeMetrics { get; set; } = true;
+
+ ///
+ /// Gets the TCP port that this collector is listening on.
+ ///
+ public int Port { get => _listener.Port; }
+
+ ///
+ /// Gets the filters used to filter out metrics we don't want to look at for a test.
+ ///
+ public List> MetricFilters { get; private set; } = new List>();
+
+ public IImmutableList MetricsMessages { get; private set; } = ImmutableList.Empty;
+
+ public IImmutableList RequestHeaders { get; private set; } = ImmutableList.Empty;
+
+ ///
+ /// Wait for the given number of metric requests to appear.
+ ///
+ /// The expected number of metric requests.
+ /// The timeout
+ /// The list of metric requests.
+ public IImmutableList WaitForMetrics(
+ int count,
+ TimeSpan? timeout = null)
+ {
+ timeout ??= DefaultWaitTimeout;
+ var deadline = DateTime.Now.Add(timeout.Value);
+
+ IImmutableList relevantMetricRequests = ImmutableList.Empty;
+
+ while (DateTime.Now < deadline)
+ {
+ relevantMetricRequests =
+ MetricsMessages
+ .Where(m => MetricFilters.All(shouldReturn => shouldReturn(m)))
+ .ToImmutableList();
+
+ if (relevantMetricRequests.Count >= count)
+ {
+ break;
+ }
+
+ Thread.Sleep(500);
+ }
+
+ return relevantMetricRequests;
+ }
+
+ public void Dispose()
+ {
+ WriteOutput($"Shutting down. Total metric requests received: '{MetricsMessages.Count}'");
+ _listener.Dispose();
+ }
+
+ protected virtual void OnRequestReceived(HttpListenerContext context)
+ {
+ RequestReceived?.Invoke(this, new EventArgs(context));
+ }
+
+ protected virtual void OnRequestDeserialized(ExportMetricsServiceRequest metricsRequest)
+ {
+ RequestDeserialized?.Invoke(this, new EventArgs(metricsRequest));
+ }
+
+ private void HandleHttpRequests(HttpListenerContext ctx)
+ {
+ OnRequestReceived(ctx);
+
+ if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
+ {
+ CreateHealthResponse(ctx);
+ return;
+ }
+
+ if (ctx.Request.RawUrl.Equals("/v1/metrics", StringComparison.OrdinalIgnoreCase))
+ {
+ if (ShouldDeserializeMetrics)
+ {
+ var metricsMessage = ExportMetricsServiceRequest.Parser.ParseFrom(ctx.Request.InputStream);
+ OnRequestDeserialized(metricsMessage);
+
+ lock (this)
+ {
+ // we only need to lock when replacing the metric collection,
+ // not when reading it because it is immutable
+ MetricsMessages = MetricsMessages.Add(metricsMessage);
+ RequestHeaders = RequestHeaders.Add(new NameValueCollection(ctx.Request.Headers));
+ }
+ }
+
+ // NOTE: HttpStreamRequest doesn't support Transfer-Encoding: Chunked
+ // (Setting content-length avoids that)
+ ctx.Response.ContentType = "application/x-protobuf";
+ ctx.Response.StatusCode = (int)HttpStatusCode.OK;
+ var responseMessage = new ExportMetricsServiceResponse();
+ ctx.Response.ContentLength64 = responseMessage.CalculateSize();
+ responseMessage.WriteTo(ctx.Response.OutputStream);
+ ctx.Response.Close();
+ return;
+ }
+
+ // We received an unsupported request
+ ctx.Response.StatusCode = (int)HttpStatusCode.NotImplemented;
+ ctx.Response.Close();
+ }
+
+ private void CreateHealthResponse(HttpListenerContext ctx)
+ {
+ ctx.Response.ContentType = "text/plain";
+ var buffer = Encoding.UTF8.GetBytes("OK");
+ ctx.Response.ContentLength64 = buffer.LongLength;
+ ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
+ ctx.Response.StatusCode = (int)HttpStatusCode.OK;
+ ctx.Response.Close();
+ }
+
+ private void WriteOutput(string msg)
+ {
+ const string name = nameof(MockMetricsCollector);
+ _output.WriteLine($"[{name}]: {msg}");
+ }
+}
diff --git a/test/IntegrationTests/Helpers/MockZipkinCollector.cs b/test/IntegrationTests/Helpers/MockZipkinCollector.cs
index 72e1ca069e..40009b2f02 100644
--- a/test/IntegrationTests/Helpers/MockZipkinCollector.cs
+++ b/test/IntegrationTests/Helpers/MockZipkinCollector.cs
@@ -35,53 +35,12 @@ public class MockZipkinCollector : IDisposable
private static readonly TimeSpan DefaultSpanWaitTimeout = TimeSpan.FromSeconds(20);
private readonly ITestOutputHelper _output;
- private readonly HttpListener _listener;
- private readonly Thread _listenerThread;
+ private readonly TestHttpListener _listener;
- public MockZipkinCollector(ITestOutputHelper output, int port = 9411, int retries = 5, string host = "localhost")
+ public MockZipkinCollector(ITestOutputHelper output, string host = "localhost")
{
_output = output;
-
- // try up to 5 consecutive ports before giving up
- while (true)
- {
- // seems like we can't reuse a listener if it fails to start,
- // so create a new listener each time we retry
- var listener = new HttpListener();
-
- try
- {
- listener.Start();
-
- // See https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistenerprefixcollection.add?redirectedfrom=MSDN&view=net-6.0#remarks
- // for info about the host value.
- string prefix = new UriBuilder("http", host, port, "/api/v2/spans/").ToString();
- listener.Prefixes.Add(prefix);
-
- // successfully listening
- Port = port;
- _listener = listener;
-
- _listenerThread = new Thread(HandleHttpRequests);
- _listenerThread.Start();
-
- WriteOutput($"Running on port '{Port}'");
-
- return;
- }
- catch (HttpListenerException) when (retries > 0)
- {
- // only catch the exception if there are retries left
- port++;
- retries--;
- }
-
- // always close listener if exception is thrown,
- // whether it was caught or not
- listener.Close();
-
- WriteOutput("Listener shut down. Could not find available port.");
- }
+ _listener = new(output, HandleHttpRequests, host, "/api/v2/spans/");
}
public event EventHandler> RequestReceived;
@@ -94,11 +53,9 @@ public MockZipkinCollector(ITestOutputHelper output, int port = 9411, int retrie
public bool ShouldDeserializeTraces { get; set; } = true;
///
- /// Gets the TCP port that this Agent is listening on.
- /// Can be different from 's initialPort
- /// parameter if listening on that port fails.
+ /// Gets the TCP port that this collector is listening on.
///
- public int Port { get; }
+ public int Port { get => _listener.Port; }
///
/// Gets the filters used to filter out spans we don't want to look at for a test.
@@ -161,7 +118,7 @@ public IImmutableList WaitForSpans(
public void Dispose()
{
WriteOutput($"Shutting down. Total spans received: '{Spans.Count}'");
- _listener?.Stop();
+ _listener.Dispose();
}
protected virtual void OnRequestReceived(HttpListenerContext context)
@@ -174,96 +131,43 @@ protected virtual void OnRequestDeserialized(IList trace)
RequestDeserialized?.Invoke(this, new EventArgs>(trace));
}
- private void AssertHeader(
- NameValueCollection headers,
- string headerKey,
- Func assertion)
+ private void HandleHttpRequests(HttpListenerContext ctx)
{
- var header = headers.Get(headerKey);
-
- if (string.IsNullOrEmpty(header))
- {
- throw new Exception($"Every submission to the agent should have a {headerKey} header.");
- }
-
- if (!assertion(header))
+ if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
{
- throw new Exception($"Failed assertion for {headerKey} on {header}");
+ CreateHealthResponse(ctx);
+ return;
}
- }
- private void HandleHttpRequests()
- {
- while (_listener.IsListening)
+ if (ShouldDeserializeTraces)
{
- try
+ using (var reader = new StreamReader(ctx.Request.InputStream))
{
- var ctx = _listener.GetContext();
- OnRequestReceived(ctx);
-
- if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
+ var zspans = JsonConvert.DeserializeObject>(reader.ReadToEnd());
+ if (zspans != null)
{
- CreateHealthResponse(ctx);
+ IList spans = zspans.ConvertAll(x => (IMockSpan)x);
+ OnRequestDeserialized(spans);
- continue;
- }
-
- if (ShouldDeserializeTraces)
- {
- using (var reader = new StreamReader(ctx.Request.InputStream))
+ lock (this)
{
- var zspans = JsonConvert.DeserializeObject>(reader.ReadToEnd());
- if (zspans != null)
- {
- IList spans = zspans.ConvertAll(x => (IMockSpan)x);
- OnRequestDeserialized(spans);
-
- lock (this)
- {
- // we only need to lock when replacing the span collection,
- // not when reading it because it is immutable
- Spans = Spans.AddRange(spans);
- RequestHeaders = RequestHeaders.Add(new NameValueCollection(ctx.Request.Headers));
- }
- }
+ // we only need to lock when replacing the span collection,
+ // not when reading it because it is immutable
+ Spans = Spans.AddRange(spans);
+ RequestHeaders = RequestHeaders.Add(new NameValueCollection(ctx.Request.Headers));
}
}
-
- // NOTE: HttpStreamRequest doesn't support Transfer-Encoding: Chunked
- // (Setting content-length avoids that)
-
- ctx.Response.ContentType = "application/json";
- var buffer = Encoding.UTF8.GetBytes("{}");
- ctx.Response.ContentLength64 = buffer.LongLength;
- ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
- ctx.Response.Close();
- }
- catch (HttpListenerException)
- {
- // listener was stopped,
- // ignore to let the loop end and the method return
- }
- catch (ObjectDisposedException)
- {
- // the response has been already disposed.
- }
- catch (InvalidOperationException)
- {
- // this can occur when setting Response.ContentLength64, with the framework claiming that the response has already been submitted
- // for now ignore, and we'll see if this introduces downstream issues
- }
- catch (Exception) when (!_listener.IsListening)
- {
- // we don't care about any exception when listener is stopped
}
}
- }
- private void WriteOutput(string msg)
- {
- const string name = nameof(MockZipkinCollector);
+ // NOTE: HttpStreamRequest doesn't support Transfer-Encoding: Chunked
+ // (Setting content-length avoids that)
- _output.WriteLine($"[{name}]: {msg}");
+ ctx.Response.ContentType = "application/json";
+ var buffer = Encoding.UTF8.GetBytes("{}");
+ ctx.Response.ContentLength64 = buffer.LongLength;
+ ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
+ ctx.Response.Close();
}
private void CreateHealthResponse(HttpListenerContext ctx)
@@ -275,4 +179,10 @@ private void CreateHealthResponse(HttpListenerContext ctx)
ctx.Response.StatusCode = (int)HttpStatusCode.OK;
ctx.Response.Close();
}
+
+ private void WriteOutput(string msg)
+ {
+ const string name = nameof(MockZipkinCollector);
+ _output.WriteLine($"[{name}]: {msg}");
+ }
}
diff --git a/test/IntegrationTests/Helpers/Models/ProcessResult.cs b/test/IntegrationTests/Helpers/Models/ProcessResult.cs
deleted file mode 100644
index 6f3d0c64d5..0000000000
--- a/test/IntegrationTests/Helpers/Models/ProcessResult.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// 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;
-
-namespace IntegrationTests.Helpers.Models;
-
-public class ProcessResult : IDisposable
-{
- public ProcessResult(
- Process process,
- string standardOutput,
- string standardError,
- int exitCode)
- {
- Process = process;
- StandardOutput = standardOutput;
- StandardError = standardError;
- ExitCode = exitCode;
- }
-
- public Process Process { get; }
-
- public string StandardOutput { get; }
-
- public string StandardError { get; }
-
- public int ExitCode { get; }
-
- public void Dispose()
- {
- Process?.Dispose();
- }
-}
diff --git a/test/IntegrationTests/Helpers/TestHelper.cs b/test/IntegrationTests/Helpers/TestHelper.cs
index 49368533ce..174d18275a 100644
--- a/test/IntegrationTests/Helpers/TestHelper.cs
+++ b/test/IntegrationTests/Helpers/TestHelper.cs
@@ -117,6 +117,10 @@ public async Task StartContainerAsync(TestSettings testSettings, int
return new Container(container);
}
+ ///
+ /// StartTestApplication starts the test application
+ // and returns the Process instance for further interaction.
+ ///
public Process StartTestApplication(int traceAgentPort, string arguments, string packageVersion, int aspNetCorePort, string framework = "", bool enableStartupHook = true)
{
var testSettings = new TestSettings
@@ -131,46 +135,32 @@ public Process StartTestApplication(int traceAgentPort, string arguments, string
return StartTestApplication(testSettings);
}
- public ProcessResult RunTestApplicationAndWaitForExit(int traceAgentPort, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool enableStartupHook = true)
+ ///
+ /// RunTestApplication starts the test application, wait up to DefaultProcessTimeout.
+ /// Assertion exceptions are thrown if it timed out or the exit code is non-zero.
+ ///
+ public void RunTestApplication(int traceAgentPort = 0, int metricsAgentPort = 0, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool enableStartupHook = true)
{
var testSettings = new TestSettings
{
- TracesSettings = new TracesSettings { Port = traceAgentPort },
Arguments = arguments,
PackageVersion = packageVersion,
AspNetCorePort = aspNetCorePort,
Framework = framework,
EnableStartupHook = enableStartupHook
};
- return RunTestApplicationAndWaitForExit(testSettings);
- }
-
- public ProcessResult RunTestApplicationAndWaitForExit(TestSettings testSettings)
- {
- var process = StartTestApplication(testSettings);
- var name = process.ProcessName;
- using var helper = new ProcessHelper(process);
-
- bool processTimeout = !process.WaitForExit((int)DefaultProcessTimeout.TotalMilliseconds);
- if (processTimeout)
+ if (traceAgentPort != 0)
{
- process.Kill();
+ testSettings.TracesSettings = new() { Port = traceAgentPort };
}
- var exitCode = process.ExitCode;
-
- Output.WriteLine($"ProcessName: " + name);
- Output.WriteLine($"ProcessId: " + process.Id);
- Output.WriteLine($"Exit Code: " + exitCode);
- Output.WriteResult(helper);
-
- if (processTimeout)
+ if (metricsAgentPort != 0)
{
- throw new TimeoutException($"{name} ({process.Id}) did not exit within {DefaultProcessTimeout.TotalSeconds} sec");
+ testSettings.MetricsSettings = new() { Port = metricsAgentPort };
}
- return new ProcessResult(process, helper.StandardOutput, helper.ErrorOutput, exitCode);
+ RunTestApplication(testSettings);
}
protected void EnableDebugMode()
@@ -183,6 +173,26 @@ protected void SetEnvironmentVariable(string key, string value)
EnvironmentHelper.CustomEnvironmentVariables.Add(key, value);
}
+ private void RunTestApplication(TestSettings testSettings)
+ {
+ using var process = StartTestApplication(testSettings);
+ Output.WriteLine($"ProcessName: " + process.ProcessName);
+ using var helper = new ProcessHelper(process);
+
+ bool processTimeout = !process.WaitForExit((int)DefaultProcessTimeout.TotalMilliseconds);
+ if (processTimeout)
+ {
+ process.Kill();
+ }
+
+ Output.WriteLine($"ProcessId: " + process.Id);
+ Output.WriteLine($"Exit Code: " + process.ExitCode);
+ Output.WriteResult(helper);
+
+ processTimeout.Should().BeFalse("Test application timed out");
+ process.ExitCode.Should().Be(0, $"Test application exited with non-zero exit code");
+ }
+
private Process StartTestApplication(TestSettings testSettings)
{
// get path to test application that the profiler will attach to
diff --git a/test/IntegrationTests/Helpers/TestHttpListener.cs b/test/IntegrationTests/Helpers/TestHttpListener.cs
new file mode 100644
index 0000000000..d97f678d09
--- /dev/null
+++ b/test/IntegrationTests/Helpers/TestHttpListener.cs
@@ -0,0 +1,120 @@
+//
+// 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.Net;
+using System.Threading;
+using Xunit.Abstractions;
+
+namespace IntegrationTests.Helpers;
+
+public class TestHttpListener : IDisposable
+{
+ private readonly ITestOutputHelper _output;
+ private readonly Action _requestHandler;
+ private readonly HttpListener _listener;
+ private readonly Thread _listenerThread;
+
+ public TestHttpListener(ITestOutputHelper output, Action requestHandler, string host = "localhost", string sufix = "/")
+ {
+ _output = output;
+ _requestHandler = requestHandler;
+
+ // try up to 5 consecutive ports before giving up
+ int retries = 4;
+ while (true)
+ {
+ // seems like we can't reuse a listener if it fails to start,
+ // so create a new listener each time we retry
+ _listener = new HttpListener();
+
+ try
+ {
+ _listener.Start();
+
+ // See https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistenerprefixcollection.add?redirectedfrom=MSDN&view=net-6.0#remarks
+ // for info about the host value.
+ Port = TcpPortProvider.GetOpenPort();
+ string prefix = new UriBuilder("http", host, Port, sufix).ToString();
+ _listener.Prefixes.Add(prefix);
+
+ // successfully listening
+ _listenerThread = new Thread(HandleHttpRequests);
+ _listenerThread.Start();
+ WriteOutput($"Listening on '{prefix}'");
+
+ return;
+ }
+ catch (HttpListenerException) when (retries > 0)
+ {
+ retries--;
+ }
+
+ // always close listener if exception is thrown,
+ // whether it was caught or not
+ _listener.Close();
+
+ WriteOutput("Listener shut down. Could not find available port.");
+ }
+ }
+
+ ///
+ /// Gets the TCP port that this listener is listening on.
+ ///
+ public int Port { get; }
+
+ public void Dispose()
+ {
+ WriteOutput($"Listener is shutting down.");
+ _listener.Stop();
+ }
+
+ private void HandleHttpRequests()
+ {
+ while (_listener.IsListening)
+ {
+ try
+ {
+ var ctx = _listener.GetContext();
+ _requestHandler(ctx);
+ }
+ catch (HttpListenerException)
+ {
+ // listener was stopped,
+ // ignore to let the loop end and the method return
+ }
+ catch (ObjectDisposedException)
+ {
+ // the response has been already disposed.
+ }
+ catch (InvalidOperationException)
+ {
+ // this can occur when setting Response.ContentLength64, with the framework claiming that the response has already been submitted
+ // for now ignore, and we'll see if this introduces downstream issues
+ }
+ catch (Exception) when (!_listener.IsListening)
+ {
+ // we don't care about any exception when listener is stopped
+ }
+ }
+ }
+
+ private void WriteOutput(string msg)
+ {
+ const string name = nameof(TestHttpListener);
+ _output.WriteLine($"[{name}]: {msg}");
+ }
+}
diff --git a/test/IntegrationTests/HttpTests.cs b/test/IntegrationTests/HttpTests.cs
index 6d981d1283..82aed7d065 100644
--- a/test/IntegrationTests/HttpTests.cs
+++ b/test/IntegrationTests/HttpTests.cs
@@ -42,13 +42,11 @@ public HttpTests(ITestOutputHelper output)
[Trait("Category", "EndToEnd")]
public void SubmitTraces()
{
- var agentPort = TcpPortProvider.GetOpenPort();
- using var agent = new MockZipkinCollector(Output, agentPort);
+ using var agent = new MockZipkinCollector(Output);
const int expectedSpanCount = 3;
- using var processResult = RunTestApplicationAndWaitForExit(agent.Port, enableStartupHook: true);
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
+ RunTestApplication(agent.Port);
var spans = agent.WaitForSpans(expectedSpanCount, TimeSpan.FromSeconds(5));
using (new AssertionScope())
@@ -91,19 +89,10 @@ public void SubmitTraces()
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
{
- var collectorPort = TcpPortProvider.GetOpenPort();
- using var collector = new MockCollector(Output, collectorPort);
-
const int expectedMetricRequests = 1;
- var testSettings = new TestSettings
- {
- MetricsSettings = new MetricsSettings { Port = collectorPort },
- EnableStartupHook = true
- };
-
- using var processResult = RunTestApplicationAndWaitForExit(testSettings);
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
+ using var collector = new MockMetricsCollector(Output);
+ RunTestApplication(metricsAgentPort: collector.Port);
var metricRequests = collector.WaitForMetrics(expectedMetricRequests, TimeSpan.FromSeconds(5));
using (new AssertionScope())
diff --git a/test/IntegrationTests/MongoDBTests.cs b/test/IntegrationTests/MongoDBTests.cs
index 2c5b23e386..99601d9ae0 100644
--- a/test/IntegrationTests/MongoDBTests.cs
+++ b/test/IntegrationTests/MongoDBTests.cs
@@ -43,49 +43,43 @@ public MongoDBTests(ITestOutputHelper output, MongoDBFixture mongoDB)
[Trait("Containers", "Linux")]
public void SubmitsTraces()
{
- int agentPort = TcpPortProvider.GetOpenPort();
+ using var agent = new MockZipkinCollector(Output);
+ RunTestApplication(agent.Port, arguments: $"--mongo-db {_mongoDB.Port}");
+ var spans = agent.WaitForSpans(3, TimeSpan.FromSeconds(5));
+ Assert.True(spans.Count >= 3, $"Expecting at least 3 spans, only received {spans.Count}");
- using (var agent = new MockZipkinCollector(Output, agentPort))
- using (var processResult = RunTestApplicationAndWaitForExit(agent.Port, arguments: $"--mongo-db {_mongoDB.Port}", enableStartupHook: true))
- {
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
-
- var spans = agent.WaitForSpans(3, TimeSpan.FromSeconds(5));
- Assert.True(spans.Count >= 3, $"Expecting at least 3 spans, only received {spans.Count}");
+ var rootSpan = spans.Single(s => s.ParentId == null);
- var rootSpan = spans.Single(s => s.ParentId == null);
+ // Check for manual trace
+ Assert.Equal("Main()", rootSpan.Name);
+ Assert.Null(rootSpan.Type);
- // Check for manual trace
- Assert.Equal("Main()", rootSpan.Name);
- Assert.Null(rootSpan.Type);
+ int spansWithStatement = 0;
- int spansWithStatement = 0;
+ foreach (var span in spans)
+ {
+ Assert.Equal("TestApplication.MongoDB", span.Service);
- foreach (var span in spans)
+ if (Regex.IsMatch(span.Name, "employees\\.*"))
{
- Assert.Equal("TestApplication.MongoDB", span.Service);
-
- if (Regex.IsMatch(span.Name, "employees\\.*"))
- {
- Assert.Equal("mongodb", span.Tags["db.system"]);
- Assert.Equal("test-db", span.Tags["db.name"]);
- Assert.True("1.0.0.0" == span.Tags["otel.library.version"], span.ToString());
+ Assert.Equal("mongodb", span.Tags["db.system"]);
+ Assert.Equal("test-db", span.Tags["db.name"]);
+ Assert.True("1.0.0.0" == span.Tags["otel.library.version"], span.ToString());
- if (span.Tags?.ContainsKey("db.statement") ?? false)
- {
- spansWithStatement++;
- Assert.True(span.Tags?.ContainsKey("db.statement"), $"No db.statement found on span {span}");
- }
- }
- else
+ if (span.Tags?.ContainsKey("db.statement") ?? false)
{
- // These are manual (DiagnosticSource) traces
- Assert.True("1.0.0" == span.Tags["otel.library.version"], span.ToString());
+ spansWithStatement++;
+ Assert.True(span.Tags?.ContainsKey("db.statement"), $"No db.statement found on span {span}");
}
}
-
- Assert.False(spansWithStatement == 0, "Extraction of the command failed on all spans");
+ else
+ {
+ // These are manual (DiagnosticSource) traces
+ Assert.True("1.0.0" == span.Tags["otel.library.version"], span.ToString());
+ }
}
+
+ Assert.False(spansWithStatement == 0, "Extraction of the command failed on all spans");
}
}
#endif
diff --git a/test/IntegrationTests/PluginsTests.cs b/test/IntegrationTests/PluginsTests.cs
new file mode 100644
index 0000000000..d91b1115e6
--- /dev/null
+++ b/test/IntegrationTests/PluginsTests.cs
@@ -0,0 +1,61 @@
+//
+// 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.Generic;
+using System.Linq;
+using FluentAssertions;
+using IntegrationTests.Helpers;
+using IntegrationTests.Helpers.Models;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace IntegrationTests;
+
+public class PluginsTests : TestHelper
+{
+ public PluginsTests(ITestOutputHelper output)
+ : base("Plugins", output)
+ {
+ }
+
+ [Fact]
+ [Trait("Category", "EndToEnd")]
+ public void SubmitsTraces()
+ {
+ SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_PLUGINS", "TestApplication.Plugins.Plugin, TestApplication.Plugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
+
+ using var collector = new MockZipkinCollector(Output);
+ RunTestApplication(collector.Port);
+ var spans = collector.WaitForSpans(1, TimeSpan.FromSeconds(5));
+
+ spans.Should().Contain(x => x.Name == "SayHello");
+ }
+
+ [Fact]
+ [Trait("Category", "EndToEnd")]
+ public void SubmitMetrics()
+ {
+ SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_PLUGINS", "TestApplication.Plugins.Plugin, TestApplication.Plugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
+
+ using var collector = new MockMetricsCollector(Output);
+ RunTestApplication(metricsAgentPort: collector.Port);
+ var metricRequests = collector.WaitForMetrics(1, TimeSpan.FromSeconds(5));
+
+ var metrics = metricRequests.Should().NotBeEmpty().And.Subject.First().ResourceMetrics.Should().ContainSingle().Subject.ScopeMetrics;
+ metrics.Should().Contain(x => x.Scope.Name == "MyCompany.MyProduct.MyLibrary");
+ }
+}
diff --git a/test/IntegrationTests/SmokeTests.cs b/test/IntegrationTests/SmokeTests.cs
index f8428cda5c..5115f384bf 100644
--- a/test/IntegrationTests/SmokeTests.cs
+++ b/test/IntegrationTests/SmokeTests.cs
@@ -33,8 +33,6 @@ public class SmokeTests : TestHelper
{
private const string ServiceName = "TestApplication.Smoke";
- private List _expectations = new List();
-
public SmokeTests(ITestOutputHelper output)
: base("Smoke", output)
{
@@ -120,19 +118,10 @@ public void ApplicationIsIncluded()
public void SubmitMetrics()
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary");
- var collectorPort = TcpPortProvider.GetOpenPort();
- using var collector = new MockCollector(Output, collectorPort);
-
const int expectedMetricRequests = 1;
- var testSettings = new TestSettings
- {
- MetricsSettings = new MetricsSettings { Port = collectorPort },
- EnableStartupHook = true,
- };
-
- using var processResult = RunTestApplicationAndWaitForExit(testSettings);
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
+ using var collector = new MockMetricsCollector(Output);
+ RunTestApplication(metricsAgentPort: collector.Port);
var metricRequests = collector.WaitForMetrics(expectedMetricRequests, TimeSpan.FromSeconds(5));
using (new AssertionScope())
@@ -204,11 +193,12 @@ private static void AssertSpanExpectations(List expect
private IImmutableList RunTestApplication(bool enableStartupHook = true)
{
+ SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary");
+
int agentPort = TcpPortProvider.GetOpenPort();
- using var agent = new MockZipkinCollector(Output, agentPort);
- using var processResult = RunTestApplicationAndWaitForExit(agent.Port, enableStartupHook: enableStartupHook);
+ using var agent = new MockZipkinCollector(Output);
+ RunTestApplication(agent.Port, enableStartupHook: enableStartupHook);
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
return agent.WaitForSpans(2, TimeSpan.FromSeconds(5));
}
}
diff --git a/test/IntegrationTests/SqlClientTests.cs b/test/IntegrationTests/SqlClientTests.cs
index 715469377b..145673827d 100644
--- a/test/IntegrationTests/SqlClientTests.cs
+++ b/test/IntegrationTests/SqlClientTests.cs
@@ -43,13 +43,11 @@ public SqlClientTests(ITestOutputHelper output, SqlClientFixture sqlClientFixtur
[Trait("Containers", "Linux")]
public void SubmitTraces()
{
- var agentPort = TcpPortProvider.GetOpenPort();
- using var agent = new MockZipkinCollector(Output, agentPort);
+ using var agent = new MockZipkinCollector(Output);
const int expectedSpanCount = 8;
- using var processResult = RunTestApplicationAndWaitForExit(agent.Port, arguments: $"{_sqlClientFixture.Password} {_sqlClientFixture.Port}", enableStartupHook: true);
- Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
+ RunTestApplication(agent.Port, arguments: $"{_sqlClientFixture.Password} {_sqlClientFixture.Port}");
var spans = agent.WaitForSpans(expectedSpanCount, TimeSpan.FromSeconds(5));
using (new AssertionScope())
diff --git a/test/test-applications/integrations/TestApplication.Plugins/Plugin.cs b/test/test-applications/integrations/TestApplication.Plugins/Plugin.cs
new file mode 100644
index 0000000000..f5f6e351c0
--- /dev/null
+++ b/test/test-applications/integrations/TestApplication.Plugins/Plugin.cs
@@ -0,0 +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 OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace TestApplication.Plugins
+{
+ public class Plugin
+ {
+ public TracerProviderBuilder ConfigureTracerProvider(TracerProviderBuilder builder)
+ {
+ return builder.AddSource(TestApplication.Smoke.Program.SourceName);
+ }
+
+ public MeterProviderBuilder ConfigureMeterProvider(MeterProviderBuilder builder)
+ {
+ return builder.AddMeter(TestApplication.Smoke.Program.SourceName);
+ }
+ }
+}
diff --git a/test/test-applications/integrations/TestApplication.Plugins/TestApplication.Plugins.csproj b/test/test-applications/integrations/TestApplication.Plugins/TestApplication.Plugins.csproj
new file mode 100644
index 0000000000..3dfb3c49e9
--- /dev/null
+++ b/test/test-applications/integrations/TestApplication.Plugins/TestApplication.Plugins.csproj
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/test-applications/integrations/TestApplication.Smoke/Program.cs b/test/test-applications/integrations/TestApplication.Smoke/Program.cs
index a5ee56b4dd..a752b57d52 100644
--- a/test/test-applications/integrations/TestApplication.Smoke/Program.cs
+++ b/test/test-applications/integrations/TestApplication.Smoke/Program.cs
@@ -19,10 +19,12 @@
using System.Diagnostics.Metrics;
using System.Net.Http;
-namespace TestApplication.StartupHook
+namespace TestApplication.Smoke
{
public class Program
{
+ public static readonly string SourceName = "MyCompany.MyProduct.MyLibrary";
+
public static void Main(string[] args)
{
EmitTraces();
@@ -31,7 +33,7 @@ public static void Main(string[] args)
private static void EmitTraces()
{
- var myActivitySource = new ActivitySource("TestApplication.StartupHook", "1.0.0");
+ var myActivitySource = new ActivitySource(SourceName, "1.0.0");
using (var activity = myActivitySource.StartActivity("SayHello"))
{
@@ -46,7 +48,7 @@ private static void EmitTraces()
private static void EmitMetrics()
{
- var myMeter = new Meter("MyCompany.MyProduct.MyLibrary", "1.0");
+ var myMeter = new Meter(SourceName, "1.0");
var myFruitCounter = myMeter.CreateCounter("MyFruitCounter");
myFruitCounter.Add(1, new KeyValuePair("name", "apple"));