[Exporter.OpenTelemetry.Protocol.Tests] enable analysis (#6264)

This commit is contained in:
Piotr Kiełkowicz 2025-04-25 20:55:09 +02:00 committed by GitHub
parent 13ed63a88c
commit ceebd456f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 549 additions and 392 deletions

View File

@ -0,0 +1,144 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
using Xunit;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests;
#pragma warning disable CA1515 // Consider making public types internal
public class GrpcRetryTestCase
#pragma warning restore CA1515 // Consider making public types internal
{
private readonly string testRunnerName;
private GrpcRetryTestCase(string testRunnerName, GrpcRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1)
{
this.ExpectedRetryAttempts = expectedRetryAttempts;
this.RetryAttempts = retryAttempts;
this.testRunnerName = testRunnerName;
}
public int ExpectedRetryAttempts { get; }
internal GrpcRetryAttempt[] RetryAttempts { get; }
public static TheoryData<GrpcRetryTestCase> GetGrpcTestCases()
{
return
[
new("Cancelled", [new(StatusCode.Cancelled)]),
new("DeadlineExceeded", [new(StatusCode.DeadlineExceeded)]),
new("Aborted", [new(StatusCode.Aborted)]),
new("OutOfRange", [new(StatusCode.OutOfRange)]),
new("DataLoss", [new(StatusCode.DataLoss)]),
new("Unavailable", [new(StatusCode.Unavailable)]),
new("OK", [new(StatusCode.OK, expectedSuccess: false)]),
new("PermissionDenied", [new(StatusCode.PermissionDenied, expectedSuccess: false)]),
new("Unknown", [new(StatusCode.Unknown, expectedSuccess: false)]),
new("ResourceExhausted w/o RetryInfo", [new(StatusCode.ResourceExhausted, expectedSuccess: false)]),
new("ResourceExhausted w/ RetryInfo", [new(StatusCode.ResourceExhausted, throttleDelay: GetThrottleDelayString(new Duration { Seconds = 2 }), expectedNextRetryDelayMilliseconds: 3000)]),
new("Unavailable w/ RetryInfo", [new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(2000))), expectedNextRetryDelayMilliseconds: 3000)]),
new("Expired deadline", [new(StatusCode.Unavailable, deadlineExceeded: true, expectedSuccess: false)]),
new(
"Exponential backoff",
[
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000)
],
expectedRetryAttempts: 5),
new(
"Retry until non-retryable status code encountered",
[
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375),
new(StatusCode.PermissionDenied, expectedSuccess: false),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000)
],
expectedRetryAttempts: 4),
// Test throttling affects exponential backoff.
new(
"Exponential backoff after throttling",
[
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(500))), expectedNextRetryDelayMilliseconds: 750),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1125),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1688),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2532),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3798),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000)
],
expectedRetryAttempts: 9),
];
}
public override string ToString()
{
return this.testRunnerName;
}
private static string GetThrottleDelayString(Duration throttleDelay)
{
var status = new Google.Rpc.Status
{
Code = 4,
Message = "Only nanos",
Details =
{
Any.Pack(new Google.Rpc.RetryInfo
{
RetryDelay = throttleDelay,
}),
},
};
return Convert.ToBase64String(status.ToByteArray());
}
internal struct GrpcRetryAttempt
{
internal GrpcRetryAttempt(
StatusCode statusCode,
bool deadlineExceeded = false,
string? throttleDelay = null,
int expectedNextRetryDelayMilliseconds = 1500,
bool expectedSuccess = true)
{
var status = new Status(statusCode, "Error");
// Using arbitrary +1 hr for deadline for test purposes.
var deadlineUtc = deadlineExceeded ? DateTime.UtcNow.AddSeconds(-1) : DateTime.UtcNow.AddHours(1);
this.ThrottleDelay = throttleDelay;
this.Response = new ExportClientGrpcResponse(expectedSuccess, deadlineUtc, null, status, this.ThrottleDelay);
this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds;
this.ExpectedSuccess = expectedSuccess;
}
public string? ThrottleDelay { get; }
public int? ExpectedNextRetryDelayMilliseconds { get; }
public bool ExpectedSuccess { get; }
internal ExportClientGrpcResponse Response { get; }
}
}

View File

@ -0,0 +1,113 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Net;
#if NETFRAMEWORK
using System.Net.Http;
#endif
using System.Net.Http.Headers;
using Xunit;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests;
#pragma warning disable CA1515 // Consider making public types internal
public class HttpRetryTestCase
#pragma warning restore CA1515 // Consider making public types internal
{
private readonly string testRunnerName;
private HttpRetryTestCase(string testRunnerName, HttpRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1)
{
this.ExpectedRetryAttempts = expectedRetryAttempts;
this.RetryAttempts = retryAttempts;
this.testRunnerName = testRunnerName;
}
public int ExpectedRetryAttempts { get; }
internal HttpRetryAttempt[] RetryAttempts { get; }
public static TheoryData<HttpRetryTestCase> GetHttpTestCases()
{
return
[
new("NetworkError", [new(statusCode: null)]),
new("GatewayTimeout", [new(statusCode: HttpStatusCode.GatewayTimeout, throttleDelay: TimeSpan.FromSeconds(1))]),
#if NETSTANDARD2_1_OR_GREATER || NET
new("ServiceUnavailable", [new(statusCode: HttpStatusCode.TooManyRequests, throttleDelay: TimeSpan.FromSeconds(1))]),
#endif
new(
"Exponential Backoff",
[
new(statusCode: null, expectedNextRetryDelayMilliseconds: 1500),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 2250),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 3375),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000)
],
expectedRetryAttempts: 5),
new(
"Retry until non-retryable status code encountered",
[
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 1500),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 2250),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 3375),
new(statusCode: HttpStatusCode.BadRequest, expectedSuccess: false),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 5000)
],
expectedRetryAttempts: 4),
new(
"Expired deadline",
[
new(statusCode: HttpStatusCode.ServiceUnavailable, isDeadlineExceeded: true, expectedSuccess: false)
]),
];
// TODO: Add more cases.
}
public override string ToString()
{
return this.testRunnerName;
}
internal sealed class HttpRetryAttempt
{
public ExportClientHttpResponse Response;
public TimeSpan? ThrottleDelay;
public int? ExpectedNextRetryDelayMilliseconds;
public bool ExpectedSuccess;
internal HttpRetryAttempt(
HttpStatusCode? statusCode,
TimeSpan? throttleDelay = null,
bool isDeadlineExceeded = false,
int expectedNextRetryDelayMilliseconds = 1500,
bool expectedSuccess = true)
{
this.ThrottleDelay = throttleDelay;
HttpResponseMessage? responseMessage = null;
if (statusCode != null)
{
#pragma warning disable CA2000 // Dispose objects before losing scope
responseMessage = new HttpResponseMessage();
#pragma warning restore CA2000 // Dispose objects before losing scope
if (throttleDelay != null)
{
responseMessage.Headers.RetryAfter = new RetryConditionHeaderValue(throttleDelay.Value);
}
responseMessage.StatusCode = (HttpStatusCode)statusCode;
}
// Using arbitrary +1 hr for deadline for test purposes.
var deadlineUtc = isDeadlineExceeded ? DateTime.UtcNow.AddMilliseconds(-1) : DateTime.UtcNow.AddHours(1);
this.Response = new ExportClientHttpResponse(expectedSuccess, deadlineUtc, responseMessage, new HttpRequestException());
this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds;
this.ExpectedSuccess = expectedSuccess;
}
}
}

View File

@ -90,11 +90,11 @@ public sealed class OtlpHttpTraceExportClientTests : IDisposable
Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
};
#pragma warning disable CA2000 // Dispose objects before losing scope
var testHttpHandler = new TestHttpMessageHandler();
#pragma warning restore CA2000 // Dispose objects before losing scope
var httpRequestContent = Array.Empty<byte>();
var httpClient = new HttpClient(testHttpHandler);
using var httpClient = new HttpClient(testHttpHandler);
var exportClient = new OtlpHttpExportClient(options, httpClient, string.Empty);
@ -117,7 +117,9 @@ public sealed class OtlpHttpTraceExportClientTests : IDisposable
using var openTelemetrySdk = builder.Build();
var exportedItems = new List<Activity>();
#pragma warning disable CA2000 // Dispose objects before losing scope
var processor = new BatchActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems));
#pragma warning restore CA2000 // Dispose objects before losing scope
const int numOfSpans = 10;
bool isEven;
for (var i = 0; i < numOfSpans; i++)

View File

@ -43,7 +43,7 @@ public sealed class OtlpArrayTagWriterTests : IDisposable
// Assert
Assert.NotNull(arrayState.Buffer);
Assert.Equal(0, arrayState.WritePosition);
Assert.True(arrayState.Buffer.Length == 2048);
Assert.Equal(2048, arrayState.Buffer.Length);
}
[Fact]
@ -166,10 +166,11 @@ public sealed class OtlpArrayTagWriterTests : IDisposable
lessthat1MBArray[i] = "1234";
}
string?[] stringArray = ["12345"];
var tags = new ActivityTagsCollection
{
new("lessthat1MBArray", lessthat1MBArray),
new("StringArray", new string?[] { "12345" }),
new("StringArray", stringArray),
new("LargeArray", largeArray),
};
@ -181,7 +182,7 @@ public sealed class OtlpArrayTagWriterTests : IDisposable
var otlpSpan = ToOtlpSpanWithExtendedBuffer(new SdkLimitOptions(), activity);
Assert.NotNull(otlpSpan);
Assert.True(otlpSpan.Attributes.Count == 3);
Assert.Equal(3, otlpSpan.Attributes.Count);
var keyValue = otlpSpan.Attributes.FirstOrDefault(kvp => kvp.Key == "StringArray");
Assert.NotNull(keyValue);
Assert.Equal("12345", keyValue.Value.ArrayValue.Values[0].StringValue);

View File

@ -102,6 +102,14 @@ public class ProtobufSerializerTests
[InlineData(268435455, new byte[] { 0xFF, 0xFF, 0xFF, 0x7F })] // Max 4-byte value
public void WriteReservedLength_WritesCorrectly(int length, byte[] expectedBytes)
{
#if NET
Assert.NotNull(expectedBytes);
#else
if (expectedBytes == null)
{
throw new ArgumentNullException(nameof(expectedBytes));
}
#endif
byte[] buffer = new byte[10];
ProtobufSerializer.WriteReservedLength(buffer, 0, length);

View File

@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Diagnostics.Tracing;
using System.Globalization;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OpenTelemetry.Logs;
@ -265,7 +266,7 @@ public sealed class IntegrationTests : IDisposable
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFrom("tomato", 2.99);
switch (processorOptions.ExportProcessorType)
{
@ -307,7 +308,7 @@ public sealed class IntegrationTests : IDisposable
string? message;
if (eventData.Message != null && eventData.Payload != null && eventData.Payload.Count > 0)
{
message = string.Format(eventData.Message, eventData.Payload.ToArray());
message = string.Format(CultureInfo.InvariantCulture, eventData.Message, eventData.Payload.ToArray());
}
else
{

View File

@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using Microsoft.Extensions.Logging;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
internal static partial class LoggerExtensions
{
[LoggerMessage(LogLevel.Information, "Hello from {Name} {Price}.")]
public static partial void HelloFrom(this ILogger logger, string name, double price);
[LoggerMessage("Hello from {Name} {Price}.")]
public static partial void HelloFrom(this ILogger logger, LogLevel logLevel, string name, double price);
[LoggerMessage(LogLevel.Information, EventId = 10, Message = "Hello from {Name} {Price}.")]
public static partial void HelloFromWithEventId(this ILogger logger, string name, double price);
[LoggerMessage(LogLevel.Information, EventId = 10, EventName = "MyEvent10", Message = "Hello from {Name} {Price}.")]
public static partial void HelloFromWithEventIdAndEventName(this ILogger logger, string name, double price);
[LoggerMessage(LogLevel.Information, "Log message")]
public static partial void LogMessage(this ILogger logger);
[LoggerMessage(LogLevel.Information, "Log when there is no activity.")]
public static partial void LogWhenThereIsNoActivity(this ILogger logger);
[LoggerMessage(LogLevel.Information, "Log within an activity.")]
public static partial void LogWithinAnActivity(this ILogger logger);
[LoggerMessage(LogLevel.Information, "OpenTelemetry {Greeting} {Subject}!")]
public static partial void OpenTelemetryGreeting(this ILogger logger, string greeting, string subject);
[LoggerMessage(LogLevel.Information, "OpenTelemetry {AttributeOne} {AttributeTwo} {AttributeThree}!")]
public static partial void OpenTelemetryWithAttributes(this ILogger logger, string attributeOne, string attributeTwo, string attributeThree);
[LoggerMessage(LogLevel.Information, "Some log information message.")]
public static partial void SomeLogInformation(this ILogger logger);
[LoggerMessage(LogLevel.Information, "Hello from red-tomato")]
public static partial void HelloFromRedTomato(this ILogger logger);
[LoggerMessage(LogLevel.Information, "Hello from green-tomato")]
public static partial void HelloFromGreenTomato(this ILogger logger);
[LoggerMessage(LogLevel.Information, "Exception Occurred")]
public static partial void ExceptionOccured(this ILogger logger, Exception exception);
}

View File

@ -4,6 +4,7 @@
#if !NETFRAMEWORK
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using Grpc.Core;
using Microsoft.AspNetCore.Builder;
@ -58,7 +59,7 @@ public sealed class MockCollectorIntegrationTests
"/MockCollector/SetResponseCodes/{responseCodesCsv}",
(MockCollectorState collectorState, string responseCodesCsv) =>
{
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray();
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray();
collectorState.SetStatusCodes(codes);
});
@ -70,13 +71,15 @@ public sealed class MockCollectorIntegrationTests
using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") };
var codes = new[] { Grpc.Core.StatusCode.Unimplemented, Grpc.Core.StatusCode.OK };
await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}");
await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative));
var exportResults = new List<ExportResult>();
using var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions() { Endpoint = new Uri($"http://localhost:{testGrpcPort}") });
#pragma warning disable CA2000 // Dispose objects before losing scope
var delegatingExporter = new DelegatingExporter<Activity>
#pragma warning disable CA2000 // Dispose objects before losing scope
{
OnExportFunc = (batch) =>
OnExportFunc = batch =>
{
var result = otlpExporter.Export(batch);
exportResults.Add(result);
@ -88,7 +91,9 @@ public sealed class MockCollectorIntegrationTests
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(activitySourceName)
#pragma warning disable CA2000 // Dispose objects before losing scope
.AddProcessor(new SimpleActivityExportProcessor(delegatingExporter))
#pragma warning restore CA2000 // Dispose objects before losing scope
.Build();
using var source = new ActivitySource(activitySourceName);
@ -158,7 +163,7 @@ public sealed class MockCollectorIntegrationTests
"/MockCollector/SetResponseCodes/{responseCodesCsv}",
(MockCollectorState collectorState, string responseCodesCsv) =>
{
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray();
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray();
collectorState.SetStatusCodes(codes);
});
@ -171,7 +176,7 @@ public sealed class MockCollectorIntegrationTests
// First reply with failure and then Ok
var codes = new[] { initialStatusCode, Grpc.Core.StatusCode.OK };
await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}");
await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative));
var endpoint = new Uri($"http://localhost:{testGrpcPort}");
@ -241,7 +246,7 @@ public sealed class MockCollectorIntegrationTests
"/MockCollector/SetResponseCodes/{responseCodesCsv}",
(MockCollectorHttpState collectorState, string responseCodesCsv) =>
{
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray();
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray();
collectorState.SetStatusCodes(codes);
});
@ -259,7 +264,7 @@ public sealed class MockCollectorIntegrationTests
using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") };
var codes = new[] { initialHttpStatusCode, HttpStatusCode.OK };
await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}");
await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative));
var endpoint = new Uri($"http://localhost:{testHttpPort}/v1/traces");
@ -327,7 +332,7 @@ public sealed class MockCollectorIntegrationTests
"/MockCollector/SetResponseCodes/{responseCodesCsv}",
(MockCollectorHttpState collectorState, string responseCodesCsv) =>
{
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray();
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray();
collectorState.SetStatusCodes(codes);
});
@ -345,13 +350,14 @@ public sealed class MockCollectorIntegrationTests
using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") };
var codes = new[] { initialHttpStatusCode, HttpStatusCode.OK };
await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}");
await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative));
var endpoint = new Uri($"http://localhost:{testHttpPort}/v1/traces");
var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000 };
var exportClient = new OtlpHttpExportClient(exporterOptions, new HttpClient(), "/v1/traces");
using var exporterHttpClient = new HttpClient();
var exportClient = new OtlpHttpExportClient(exporterOptions, exporterHttpClient, "/v1/traces");
// TODO: update this to configure via experimental environment variable.
OtlpExporterTransmissionHandler transmissionHandler;
@ -466,7 +472,7 @@ public sealed class MockCollectorIntegrationTests
"/MockCollector/SetResponseCodes/{responseCodesCsv}",
(MockCollectorState collectorState, string responseCodesCsv) =>
{
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x)).ToArray();
var codes = responseCodesCsv.Split(",").Select(x => int.Parse(x, CultureInfo.InvariantCulture)).ToArray();
collectorState.SetStatusCodes(codes);
});
@ -478,13 +484,14 @@ public sealed class MockCollectorIntegrationTests
using var httpClient = new HttpClient() { BaseAddress = new Uri($"http://localhost:{testHttpPort}") };
var codes = new[] { initialgrpcStatusCode, Grpc.Core.StatusCode.OK };
await httpClient.GetAsync($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}");
await httpClient.GetAsync(new Uri($"/MockCollector/SetResponseCodes/{string.Join(",", codes.Select(x => (int)x))}", UriKind.Relative));
var endpoint = new Uri($"http://localhost:{testGrpcPort}");
var exporterOptions = new OtlpExporterOptions() { Endpoint = endpoint, TimeoutMilliseconds = 20000 };
var exportClient = new OtlpGrpcExportClient(exporterOptions, new HttpClient(), "opentelemetry.proto.collector.trace.v1.TraceService/Export");
using var exporterHttpClient = new HttpClient();
var exportClient = new OtlpGrpcExportClient(exporterOptions, exporterHttpClient, "opentelemetry.proto.collector.trace.v1.TraceService/Export");
// TODO: update this to configure via experimental environment variable.
OtlpExporterTransmissionHandler transmissionHandler;
@ -547,10 +554,10 @@ public sealed class MockCollectorIntegrationTests
transmissionHandler.Dispose();
}
private class MockCollectorState
private sealed class MockCollectorState
{
private Grpc.Core.StatusCode[] statusCodes = { };
private int statusCodeIndex = 0;
private Grpc.Core.StatusCode[] statusCodes = [];
private int statusCodeIndex;
public void SetStatusCodes(int[] statusCodes)
{
@ -566,10 +573,10 @@ public sealed class MockCollectorIntegrationTests
}
}
private class MockCollectorHttpState
private sealed class MockCollectorHttpState
{
private HttpStatusCode[] statusCodes = { };
private int statusCodeIndex = 0;
private HttpStatusCode[] statusCodes = [];
private int statusCodeIndex;
public void SetStatusCodes(int[] statusCodes)
{
@ -585,7 +592,9 @@ public sealed class MockCollectorIntegrationTests
}
}
private class MockTraceService : TraceService.TraceServiceBase
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
private sealed class MockTraceService : TraceService.TraceServiceBase
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
{
private readonly MockCollectorState state;
@ -606,9 +615,9 @@ public sealed class MockCollectorIntegrationTests
}
}
private class MockFileProvider : PersistentBlobProvider
private sealed class MockFileProvider : PersistentBlobProvider
{
private readonly List<PersistentBlob> mockStorage = new();
private readonly List<PersistentBlob> mockStorage = [];
public IEnumerable<PersistentBlob> TryGetBlobs() => this.mockStorage.AsEnumerable();
@ -637,11 +646,11 @@ public sealed class MockCollectorIntegrationTests
}
}
private class MockFileBlob : PersistentBlob
private sealed class MockFileBlob : PersistentBlob
{
private readonly List<PersistentBlob> mockStorage;
private byte[] buffer = Array.Empty<byte>();
private byte[] buffer = [];
public MockFileBlob(List<PersistentBlob> mockStorage)
{

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
<AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>
<!-- Add MSBuild Task to Generate Certificates -->
@ -47,7 +48,6 @@
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\DelegatingExporter.cs" Link="Includes\DelegatingExporter.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\EventSourceTestHelper.cs" Link="Includes\EventSourceTestHelper.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\SkipUnlessEnvVarFoundTheoryAttribute.cs" Link="Includes\SkipUnlessEnvVarFoundTheoryAttribute.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\SkipUnlessEnvVarFoundFactAttribute.cs" Link="Includes\SkipUnlessEnvVarFoundFactAttribute.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestActivityProcessor.cs" Link="Includes\TestActivityProcessor.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestEventListener.cs" Link="Includes\TestEventListener.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\Utils.cs" Link="Includes\Utils.cs" />

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using Xunit;
using OtlpCommon = OpenTelemetry.Proto.Common.V1;
@ -64,14 +65,14 @@ public class OtlpAttributeTests
var expectedArray = new long[array.Length];
for (var i = 0; i < array.Length; i++)
{
expectedArray[i] = Convert.ToInt64(array.GetValue(i));
expectedArray[i] = Convert.ToInt64(array.GetValue(i), CultureInfo.InvariantCulture);
}
Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.IntValue));
break;
default:
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.IntValue, attribute.Value.ValueCase);
Assert.Equal(Convert.ToInt64(value), attribute.Value.IntValue);
Assert.Equal(Convert.ToInt64(value, CultureInfo.InvariantCulture), attribute.Value.IntValue);
break;
}
}
@ -93,14 +94,14 @@ public class OtlpAttributeTests
var expectedArray = new double[array.Length];
for (var i = 0; i < array.Length; i++)
{
expectedArray[i] = Convert.ToDouble(array.GetValue(i));
expectedArray[i] = Convert.ToDouble(array.GetValue(i), CultureInfo.InvariantCulture);
}
Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.DoubleValue));
break;
default:
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.DoubleValue, attribute.Value.ValueCase);
Assert.Equal(Convert.ToDouble(value), attribute.Value.DoubleValue);
Assert.Equal(Convert.ToDouble(value, CultureInfo.InvariantCulture), attribute.Value.DoubleValue);
break;
}
}
@ -120,14 +121,14 @@ public class OtlpAttributeTests
var expectedArray = new bool[array.Length];
for (var i = 0; i < array.Length; i++)
{
expectedArray[i] = Convert.ToBoolean(array.GetValue(i));
expectedArray[i] = Convert.ToBoolean(array.GetValue(i), CultureInfo.InvariantCulture);
}
Assert.Equal(expectedArray, attribute.Value.ArrayValue.Values.Select(x => x.BoolValue));
break;
default:
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.BoolValue, attribute.Value.ValueCase);
Assert.Equal(Convert.ToBoolean(value), attribute.Value.BoolValue);
Assert.Equal(Convert.ToBoolean(value, CultureInfo.InvariantCulture), attribute.Value.BoolValue);
break;
}
}
@ -140,7 +141,7 @@ public class OtlpAttributeTests
var kvp = new KeyValuePair<string, object?>("key", value);
Assert.True(TryTransformTag(kvp, out var attribute));
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ValueCase);
Assert.Equal(Convert.ToString(value), attribute.Value.StringValue);
Assert.Equal(Convert.ToString(value, CultureInfo.InvariantCulture), attribute.Value.StringValue);
}
[Fact]
@ -219,7 +220,7 @@ public class OtlpAttributeTests
(nint)int.MaxValue,
(nuint)uint.MaxValue,
decimal.MaxValue,
new object(),
new(),
};
var testArrayValues = new object[]
@ -227,7 +228,7 @@ public class OtlpAttributeTests
new nint[] { 1, 2, 3 },
new nuint[] { 1, 2, 3 },
new decimal[] { 1, 2, 3 },
new object?[] { new object[3], new object(), null },
new object?[] { new object[3], new(), null },
};
foreach (var value in testValues)
@ -297,11 +298,11 @@ public class OtlpAttributeTests
return false;
}
private class MyToStringMethodThrowsAnException
private sealed class MyToStringMethodThrowsAnException
{
public override string ToString()
{
throw new Exception("Nope.");
throw new InvalidOperationException("Nope.");
}
}
}

View File

@ -41,7 +41,7 @@ public class OtlpExporterOptionsExtensionsTests
[InlineData("key1")]
public void GetHeaders_InvalidOptionHeaders_ThrowsArgumentException(string inputOptionHeaders)
{
this.VerifyHeaders(inputOptionHeaders, string.Empty, true);
VerifyHeaders(inputOptionHeaders, string.Empty, true);
}
[Theory]
@ -57,7 +57,7 @@ public class OtlpExporterOptionsExtensionsTests
[InlineData("key1=value1%2Ckey2=value2%2Ckey3=value3", "key1=value1,key2=value2,key3=value3")]
public void GetHeaders_ValidAndUrlEncodedHeaders_ReturnsCorrectHeaders(string inputOptionHeaders, string expectedNormalizedOptional)
{
this.VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional);
VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional);
}
[Theory]
@ -93,13 +93,13 @@ public class OtlpExporterOptionsExtensionsTests
[InlineData("http://test:8888/", "http://test:8888/v1/traces")]
[InlineData("http://test:8888/v1/traces", "http://test:8888/v1/traces")]
[InlineData("http://test:8888/v1/traces/", "http://test:8888/v1/traces/")]
public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string inputUri, string expectedUri)
public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string input, string expected)
{
var uri = new Uri(inputUri, UriKind.Absolute);
var uri = new Uri(input, UriKind.Absolute);
var resultUri = uri.AppendPathIfNotPresent("v1/traces");
Assert.Equal(expectedUri, resultUri.AbsoluteUri);
Assert.Equal(expected, resultUri.AbsoluteUri);
}
[Theory]
@ -163,7 +163,7 @@ public class OtlpExporterOptionsExtensionsTests
/// This will be parsed into a dictionary and compared with the actual extracted headers.</param>
/// <param name="expectException">If `true`, the method expects `GetHeaders` to throw an `ArgumentException`
/// when processing `inputOptionHeaders`.</param>
private void VerifyHeaders(string inputOptionHeaders, string expectedNormalizedOptional, bool expectException = false)
private static void VerifyHeaders(string inputOptionHeaders, string expectedNormalizedOptional, bool expectException = false)
{
var options = new OtlpExporterOptions { Headers = inputOptionHeaders };
@ -179,9 +179,9 @@ public class OtlpExporterOptionsExtensionsTests
if (!string.IsNullOrEmpty(expectedNormalizedOptional))
{
foreach (var segment in expectedNormalizedOptional.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
foreach (var segment in expectedNormalizedOptional.Split([','], StringSplitOptions.RemoveEmptyEntries))
{
var parts = segment.Split(new[] { '=' }, 2);
var parts = segment.Split(['='], 2);
expectedOptional.Add(parts[0].Trim(), parts[1].Trim());
}
}

View File

@ -7,7 +7,7 @@ using Xunit;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
[Collection("EnvVars")]
public class OtlpExporterOptionsTests : IDisposable
public sealed class OtlpExporterOptionsTests : IDisposable
{
public OtlpExporterOptionsTests()
{
@ -52,6 +52,14 @@ public class OtlpExporterOptionsTests : IDisposable
[ClassData(typeof(OtlpSpecConfigDefinitionTests))]
public void OtlpExporterOptions_EnvironmentVariableOverride(object testDataObject)
{
#if NET
Assert.NotNull(testDataObject);
#else
if (testDataObject == null)
{
throw new ArgumentNullException(nameof(testDataObject));
}
#endif
var testData = testDataObject as OtlpSpecConfigDefinitionTests.TestData;
Assert.NotNull(testData);
@ -66,6 +74,14 @@ public class OtlpExporterOptionsTests : IDisposable
[ClassData(typeof(OtlpSpecConfigDefinitionTests))]
public void OtlpExporterOptions_UsingIConfiguration(object testDataObject)
{
#if NET
Assert.NotNull(testDataObject);
#else
if (testDataObject == null)
{
throw new ArgumentNullException(nameof(testDataObject));
}
#endif
var testData = testDataObject as OtlpSpecConfigDefinitionTests.TestData;
Assert.NotNull(testData);

View File

@ -30,7 +30,8 @@ public class OtlpHttpExportClientTests
options.Endpoint = new Uri(optionEndpoint);
}
var exporterClient = new OtlpHttpExportClient(options, new HttpClient(), "signal/path");
using var httpClient = new HttpClient();
var exporterClient = new OtlpHttpExportClient(options, httpClient, "signal/path");
Assert.Equal(new Uri(expectedExporterEndpoint), exporterClient.Endpoint);
}
finally

View File

@ -3,6 +3,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -148,7 +149,7 @@ public class OtlpLogExporterTests
Assert.True(optionsValidated);
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFrom("tomato", 2.99);
Assert.Single(logRecords);
var logRecord = logRecords[0];
#pragma warning disable CS0618 // Type or member is obsolete
@ -261,8 +262,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFrom("tomato", 2.99);
Assert.Single(logRecords);
var logRecord = logRecords[0];
@ -274,16 +274,16 @@ public class OtlpLogExporterTests
var index = 0;
var attribute = otlpLogRecord.Attributes[index];
Assert.Equal("name", attribute.Key);
Assert.Equal("Name", attribute.Key);
Assert.Equal("tomato", attribute.Value.StringValue);
attribute = otlpLogRecord.Attributes[++index];
Assert.Equal("price", attribute.Key);
Assert.Equal("Price", attribute.Key);
Assert.Equal(2.99, attribute.Value.DoubleValue);
attribute = otlpLogRecord.Attributes[++index];
Assert.Equal("{OriginalFormat}", attribute.Key);
Assert.Equal("Hello from {name} {price}.", attribute.Value.StringValue);
Assert.Equal("Hello from {Name} {Price}.", attribute.Value.StringValue);
}
[Theory]
@ -305,7 +305,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation(new EventId(10, null), "Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFromWithEventId("tomato", 2.99);
Assert.Single(logRecords);
var configuration = new ConfigurationBuilder()
@ -313,7 +313,7 @@ public class OtlpLogExporterTests
.Build();
var logRecord = logRecords[0];
OtlpLogs.LogRecord? otlpLogRecord = otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord);
OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new(configuration), logRecord);
Assert.NotNull(otlpLogRecord);
Assert.Equal("Hello from tomato 2.99.", otlpLogRecord.Body.StringValue);
@ -322,17 +322,17 @@ public class OtlpLogExporterTests
var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString();
if (emitLogEventAttributes == "true")
{
Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes);
Assert.Contains("10", otlpLogRecordAttributes);
Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains("10", otlpLogRecordAttributes, StringComparison.Ordinal);
}
else
{
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes);
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
}
logRecords.Clear();
logger.LogInformation(new EventId(10, "MyEvent10"), "Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFromWithEventIdAndEventName("tomato", 2.99);
Assert.Single(logRecords);
logRecord = logRecords[0];
@ -346,15 +346,15 @@ public class OtlpLogExporterTests
otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString();
if (emitLogEventAttributes == "true")
{
Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes);
Assert.Contains("10", otlpLogRecordAttributes);
Assert.Contains(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes);
Assert.Contains("MyEvent10", otlpLogRecordAttributes);
Assert.Contains(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains("10", otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains("MyEvent10", otlpLogRecordAttributes, StringComparison.Ordinal);
}
else
{
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes);
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes);
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventIdAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.DoesNotContain(ExperimentalOptions.LogRecordEventNameAttribute, otlpLogRecordAttributes, StringComparison.Ordinal);
}
}
@ -368,7 +368,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Log message");
logger.LogMessage();
var logRecord = logRecords[0];
OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord);
@ -388,7 +388,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation("Log when there is no activity.");
logger.LogWhenThereIsNoActivity();
var logRecord = logRecords[0];
OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), logRecord);
@ -413,9 +413,10 @@ public class OtlpLogExporterTests
ActivityTraceId expectedTraceId = default;
ActivitySpanId expectedSpanId = default;
using (var activity = new Activity(Utils.GetCurrentMethodName()).Start())
using (var activity = new Activity(Utils.GetCurrentMethodName()))
{
logger.LogInformation("Log within an activity.");
activity.Start();
logger.LogWithinAnActivity();
expectedTraceId = activity.TraceId;
expectedSpanId = activity.SpanId;
@ -451,7 +452,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("CheckToOtlpLogRecordSeverityLevelAndText");
logger.Log(logLevel, "Hello from {name} {price}.", "tomato", 2.99);
logger.HelloFrom(logLevel, "tomato", 2.99);
Assert.Single(logRecords);
var logRecord = logRecords[0];
@ -506,7 +507,7 @@ public class OtlpLogExporterTests
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
// Scenario 1 - Using ExtensionMethods on ILogger.Log
logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World");
logger.OpenTelemetryGreeting("Hello", "World");
Assert.Single(logRecords);
var logRecord = logRecords[0];
@ -620,7 +621,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger("OtlpLogExporterTests");
logger.LogInformation(new Exception("Exception Message"), "Exception Occurred");
logger.ExceptionOccured(new InvalidOperationException("Exception Message"));
var logRecord = logRecords[0];
var loggedException = logRecord.Exception;
@ -630,15 +631,15 @@ public class OtlpLogExporterTests
Assert.NotNull(otlpLogRecord);
var otlpLogRecordAttributes = otlpLogRecord.Attributes.ToString();
Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes);
Assert.Contains(SemanticConventions.AttributeExceptionType, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.NotNull(logRecord.Exception);
Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes);
Assert.Contains(logRecord.Exception.GetType().Name, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes);
Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes);
Assert.Contains(SemanticConventions.AttributeExceptionMessage, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains(logRecord.Exception.Message, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes);
Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes);
Assert.Contains(SemanticConventions.AttributeExceptionStacktrace, otlpLogRecordAttributes, StringComparison.Ordinal);
Assert.Contains(logRecord.Exception.ToInvariantString(), otlpLogRecordAttributes, StringComparison.Ordinal);
}
[Fact]
@ -659,7 +660,7 @@ public class OtlpLogExporterTests
});
var logger = loggerFactory.CreateLogger(string.Empty);
logger.LogInformation("OpenTelemetry {AttributeOne} {AttributeTwo} {AttributeThree}!", "I'm an attribute", "I too am an attribute", "I get dropped :(");
logger.OpenTelemetryWithAttributes("I'm an attribute", "I too am an attribute", "I get dropped :(");
var logRecord = logRecords[0];
OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(sdkLimitOptions, new(), logRecord);
@ -688,10 +689,10 @@ public class OtlpLogExporterTests
// Arrange.
var testExportClient = new TestExportClient();
var exporterOptions = new OtlpExporterOptions();
var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
var emptyLogRecords = Array.Empty<LogRecord>();
var emptyBatch = new Batch<LogRecord>(emptyLogRecords, emptyLogRecords.Length);
var sut = new OtlpLogExporter(
using var sut = new OtlpLogExporter(
exporterOptions,
new SdkLimitOptions(),
new ExperimentalOptions(),
@ -710,10 +711,10 @@ public class OtlpLogExporterTests
// Arrange.
var testExportClient = new TestExportClient(throwException: true);
var exporterOptions = new OtlpExporterOptions();
var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
var emptyLogRecords = Array.Empty<LogRecord>();
var emptyBatch = new Batch<LogRecord>(emptyLogRecords, emptyLogRecords.Length);
var sut = new OtlpLogExporter(
using var sut = new OtlpLogExporter(
exporterOptions,
new SdkLimitOptions(),
new ExperimentalOptions(),
@ -732,10 +733,10 @@ public class OtlpLogExporterTests
// Arrange.
var testExportClient = new TestExportClient();
var exporterOptions = new OtlpExporterOptions();
var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
using var transmissionHandler = new OtlpExporterTransmissionHandler(testExportClient, exporterOptions.TimeoutMilliseconds);
var emptyLogRecords = Array.Empty<LogRecord>();
var emptyBatch = new Batch<LogRecord>(emptyLogRecords, emptyLogRecords.Length);
var sut = new OtlpLogExporter(
using var sut = new OtlpLogExporter(
exporterOptions,
new SdkLimitOptions(),
new ExperimentalOptions(),
@ -767,10 +768,10 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(expectedScopeKey, expectedScopeValue),
new(expectedScopeKey, expectedScopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -787,6 +788,15 @@ public class OtlpLogExporterTests
[InlineData('a')]
public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeStringValue(object scopeValue)
{
#if NET
Assert.NotNull(scopeValue);
#else
if (scopeValue == null)
{
throw new ArgumentNullException(nameof(scopeValue));
}
#endif
// Arrange.
var logRecords = new List<LogRecord>(1);
using var loggerFactory = LoggerFactory.Create(builder =>
@ -802,10 +812,10 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(scopeKey, scopeValue),
new(scopeKey, scopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -841,10 +851,10 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(scopeKey, scopeValue),
new(scopeKey, scopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -877,6 +887,15 @@ public class OtlpLogExporterTests
[InlineData(long.MaxValue)]
public void ToOtlpLog_WhenOptionsIncludeScopesIsTrue_ContainsScopeAttributeIntValue(object scopeValue)
{
#if NET
Assert.NotNull(scopeValue);
#else
if (scopeValue == null)
{
throw new ArgumentNullException(nameof(scopeValue));
}
#endif
// Arrange.
var logRecords = new List<LogRecord>(1);
using var loggerFactory = LoggerFactory.Create(builder =>
@ -892,10 +911,10 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(scopeKey, scopeValue),
new(scopeKey, scopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -908,7 +927,7 @@ public class OtlpLogExporterTests
Assert.NotNull(actualScope);
Assert.Equal(scopeKey, actualScope.Key);
Assert.Equal(ValueOneofCase.IntValue, actualScope.Value.ValueCase);
Assert.Equal(scopeValue.ToString(), actualScope.Value.IntValue.ToString());
Assert.Equal(scopeValue.ToString(), actualScope.Value.IntValue.ToString(CultureInfo.InvariantCulture));
}
[Theory]
@ -934,7 +953,7 @@ public class OtlpLogExporterTests
new(scopeKey, scopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -948,7 +967,7 @@ public class OtlpLogExporterTests
Assert.NotNull(actualScope);
Assert.Equal(scopeKey, actualScope.Key);
Assert.Equal(ValueOneofCase.DoubleValue, actualScope.Value.ValueCase);
Assert.Equal(((double)scopeValue).ToString(), actualScope.Value.DoubleValue.ToString());
Assert.Equal(scopeValue, actualScope.Value.DoubleValue);
}
[Theory]
@ -974,7 +993,7 @@ public class OtlpLogExporterTests
new(scopeKey, scopeValue),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -986,7 +1005,7 @@ public class OtlpLogExporterTests
var actualScope = TryGetAttribute(otlpLogRecord, scopeKey);
Assert.NotNull(actualScope);
Assert.Equal(scopeKey, actualScope.Key);
Assert.Equal(scopeValue.ToString(), actualScope.Value.DoubleValue.ToString());
Assert.Equal(scopeValue, actualScope.Value.DoubleValue);
}
[Fact]
@ -1007,7 +1026,7 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(scopeState))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -1042,7 +1061,7 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(scopeState))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -1073,7 +1092,7 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(scopeState))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -1113,7 +1132,7 @@ public class OtlpLogExporterTests
Assert.NotNull(scopeState);
using (logger.BeginScope(scopeState))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -1149,11 +1168,11 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(scopeKey1, scopeValue1),
new KeyValuePair<string, object>(scopeKey2, scopeValue2),
new(scopeKey1, scopeValue1),
new(scopeKey2, scopeValue2),
}))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
// Assert.
@ -1189,11 +1208,11 @@ public class OtlpLogExporterTests
const string scopeValue2 = "Some other scope value";
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>(scopeKey1, scopeValue1) }))
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new(scopeKey1, scopeValue1) }))
{
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>(scopeKey2, scopeValue2) }))
using (logger.BeginScope(new List<KeyValuePair<string, object>> { new(scopeKey2, scopeValue2) }))
{
logger.LogInformation("Some log information message.");
logger.SomeLogInformation();
}
}
@ -1232,14 +1251,14 @@ public class OtlpLogExporterTests
// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(scopeKey1, scopeValue1),
new(scopeKey1, scopeValue1),
}))
{
logger.Log(
LogLevel.Error,
new EventId(1),
new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>(scopeKey2, scopeValue2) },
exception: new Exception("Some exception message"),
new List<KeyValuePair<string, object>> { new(scopeKey2, scopeValue2) },
exception: new InvalidOperationException("Some exception message"),
formatter: (s, e) => string.Empty);
}
@ -1336,10 +1355,10 @@ public class OtlpLogExporterTests
});
var logger1 = loggerFactory.CreateLogger("OtlpLogExporterTests-A");
logger1.LogInformation("Hello from red-tomato");
logger1.HelloFromRedTomato();
var logger2 = loggerFactory.CreateLogger("OtlpLogExporterTests-B");
logger2.LogInformation("Hello from green-tomato");
logger2.HelloFromGreenTomato();
Assert.Equal(2, logRecords.Count);

View File

@ -19,7 +19,7 @@ using OtlpMetrics = OpenTelemetry.Proto.Metrics.V1;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
[Collection("EnvVars")]
public class OtlpMetricsExporterTests : IDisposable
public sealed class OtlpMetricsExporterTests : IDisposable
{
private static readonly KeyValuePair<string, object?>[] KeyValues =
[

View File

@ -1,13 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Net;
using System.Net.Http.Headers;
#if NETFRAMEWORK
using System.Net.Http;
#endif
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
using Xunit;
@ -15,14 +8,22 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClie
public class OtlpRetryTests
{
public static IEnumerable<object[]> GrpcRetryTestData => GrpcRetryTestCase.GetGrpcTestCases();
public static TheoryData<GrpcRetryTestCase> GrpcRetryTestData => GrpcRetryTestCase.GetGrpcTestCases();
public static IEnumerable<object[]> HttpRetryTestData => HttpRetryTestCase.GetHttpTestCases();
public static TheoryData<HttpRetryTestCase> HttpRetryTestData => HttpRetryTestCase.GetHttpTestCases();
[Theory]
[MemberData(nameof(GrpcRetryTestData))]
public void TryGetGrpcRetryResultTest(GrpcRetryTestCase testCase)
{
#if NET
Assert.NotNull(testCase);
#else
if (testCase == null)
{
throw new ArgumentNullException(nameof(testCase));
}
#endif
var attempts = 0;
var nextRetryDelayMilliseconds = OtlpRetry.InitialBackoffMilliseconds;
@ -65,6 +66,14 @@ public class OtlpRetryTests
[MemberData(nameof(HttpRetryTestData))]
public void TryGetHttpRetryResultTest(HttpRetryTestCase testCase)
{
#if NET
Assert.NotNull(testCase);
#else
if (testCase == null)
{
throw new ArgumentNullException(nameof(testCase));
}
#endif
var attempts = 0;
var nextRetryDelayMilliseconds = OtlpRetry.InitialBackoffMilliseconds;
@ -101,242 +110,4 @@ public class OtlpRetryTests
Assert.Equal(testCase.ExpectedRetryAttempts, attempts);
}
public class GrpcRetryTestCase
{
public int ExpectedRetryAttempts;
public GrpcRetryAttempt[] RetryAttempts;
private string testRunnerName;
private GrpcRetryTestCase(string testRunnerName, GrpcRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1)
{
this.ExpectedRetryAttempts = expectedRetryAttempts;
this.RetryAttempts = retryAttempts;
this.testRunnerName = testRunnerName;
}
public static IEnumerable<object[]> GetGrpcTestCases()
{
yield return new[] { new GrpcRetryTestCase("Cancelled", new GrpcRetryAttempt[] { new(StatusCode.Cancelled) }) };
yield return new[] { new GrpcRetryTestCase("DeadlineExceeded", new GrpcRetryAttempt[] { new(StatusCode.DeadlineExceeded) }) };
yield return new[] { new GrpcRetryTestCase("Aborted", new GrpcRetryAttempt[] { new(StatusCode.Aborted) }) };
yield return new[] { new GrpcRetryTestCase("OutOfRange", new GrpcRetryAttempt[] { new(StatusCode.OutOfRange) }) };
yield return new[] { new GrpcRetryTestCase("DataLoss", new GrpcRetryAttempt[] { new(StatusCode.DataLoss) }) };
yield return new[] { new GrpcRetryTestCase("Unavailable", new GrpcRetryAttempt[] { new(StatusCode.Unavailable) }) };
yield return new[] { new GrpcRetryTestCase("OK", new GrpcRetryAttempt[] { new(StatusCode.OK, expectedSuccess: false) }) };
yield return new[] { new GrpcRetryTestCase("PermissionDenied", new GrpcRetryAttempt[] { new(StatusCode.PermissionDenied, expectedSuccess: false) }) };
yield return new[] { new GrpcRetryTestCase("Unknown", new GrpcRetryAttempt[] { new(StatusCode.Unknown, expectedSuccess: false) }) };
yield return new[] { new GrpcRetryTestCase("ResourceExhausted w/o RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.ResourceExhausted, expectedSuccess: false) }) };
yield return new[] { new GrpcRetryTestCase("ResourceExhausted w/ RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.ResourceExhausted, throttleDelay: GetThrottleDelayString(new Duration { Seconds = 2 }), expectedNextRetryDelayMilliseconds: 3000) }) };
yield return new[] { new GrpcRetryTestCase("Unavailable w/ RetryInfo", new GrpcRetryAttempt[] { new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(2000))), expectedNextRetryDelayMilliseconds: 3000) }) };
yield return new[] { new GrpcRetryTestCase("Expired deadline", new GrpcRetryAttempt[] { new(StatusCode.Unavailable, deadlineExceeded: true, expectedSuccess: false) }) };
yield return new[]
{
new GrpcRetryTestCase(
"Exponential backoff",
new GrpcRetryAttempt[]
{
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
},
expectedRetryAttempts: 5),
};
yield return new[]
{
new GrpcRetryTestCase(
"Retry until non-retryable status code encountered",
new GrpcRetryAttempt[]
{
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3375),
new(StatusCode.PermissionDenied, expectedSuccess: false),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
},
expectedRetryAttempts: 4),
};
// Test throttling affects exponential backoff.
yield return new[]
{
new GrpcRetryTestCase(
"Exponential backoff after throttling",
new GrpcRetryAttempt[]
{
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1500),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2250),
new(StatusCode.Unavailable, throttleDelay: GetThrottleDelayString(Duration.FromTimeSpan(TimeSpan.FromMilliseconds(500))), expectedNextRetryDelayMilliseconds: 750),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1125),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 1688),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 2532),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 3798),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
new(StatusCode.Unavailable, expectedNextRetryDelayMilliseconds: 5000),
},
expectedRetryAttempts: 9),
};
}
public override string ToString()
{
return this.testRunnerName;
}
private static string GetThrottleDelayString(Duration throttleDelay)
{
var status = new Google.Rpc.Status
{
Code = 4,
Message = "Only nanos",
Details =
{
Any.Pack(new Google.Rpc.RetryInfo
{
RetryDelay = throttleDelay,
}),
},
};
return Convert.ToBase64String(status.ToByteArray());
}
public struct GrpcRetryAttempt
{
public string? ThrottleDelay;
public int? ExpectedNextRetryDelayMilliseconds;
public bool ExpectedSuccess;
internal ExportClientGrpcResponse Response;
internal GrpcRetryAttempt(
StatusCode statusCode,
bool deadlineExceeded = false,
string? throttleDelay = null,
int expectedNextRetryDelayMilliseconds = 1500,
bool expectedSuccess = true)
{
var status = new Status(statusCode, "Error");
// Using arbitrary +1 hr for deadline for test purposes.
var deadlineUtc = deadlineExceeded ? DateTime.UtcNow.AddSeconds(-1) : DateTime.UtcNow.AddHours(1);
this.ThrottleDelay = throttleDelay;
this.Response = new ExportClientGrpcResponse(expectedSuccess, deadlineUtc, null, status, this.ThrottleDelay);
this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds;
this.ExpectedSuccess = expectedSuccess;
}
}
}
public class HttpRetryTestCase
{
public int ExpectedRetryAttempts;
internal HttpRetryAttempt[] RetryAttempts;
private string testRunnerName;
private HttpRetryTestCase(string testRunnerName, HttpRetryAttempt[] retryAttempts, int expectedRetryAttempts = 1)
{
this.ExpectedRetryAttempts = expectedRetryAttempts;
this.RetryAttempts = retryAttempts;
this.testRunnerName = testRunnerName;
}
public static IEnumerable<object[]> GetHttpTestCases()
{
yield return new[] { new HttpRetryTestCase("NetworkError", [new(statusCode: null)]) };
yield return new[] { new HttpRetryTestCase("GatewayTimeout", [new(statusCode: HttpStatusCode.GatewayTimeout, throttleDelay: TimeSpan.FromSeconds(1))]) };
#if NETSTANDARD2_1_OR_GREATER || NET
yield return new[] { new HttpRetryTestCase("ServiceUnavailable", [new(statusCode: HttpStatusCode.TooManyRequests, throttleDelay: TimeSpan.FromSeconds(1))]) };
#endif
yield return new[]
{
new HttpRetryTestCase(
"Exponential Backoff",
new HttpRetryAttempt[]
{
new(statusCode: null, expectedNextRetryDelayMilliseconds: 1500),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 2250),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 3375),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000),
new(statusCode: null, expectedNextRetryDelayMilliseconds: 5000),
},
expectedRetryAttempts: 5),
};
yield return new[]
{
new HttpRetryTestCase(
"Retry until non-retryable status code encountered",
new HttpRetryAttempt[]
{
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 1500),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 2250),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 3375),
new(statusCode: HttpStatusCode.BadRequest, expectedSuccess: false),
new(statusCode: HttpStatusCode.ServiceUnavailable, expectedNextRetryDelayMilliseconds: 5000),
},
expectedRetryAttempts: 4),
};
yield return new[] { new HttpRetryTestCase("Expired deadline", new HttpRetryAttempt[] { new(statusCode: HttpStatusCode.ServiceUnavailable, isDeadlineExceeded: true, expectedSuccess: false) }) };
// TODO: Add more cases.
}
public override string ToString()
{
return this.testRunnerName;
}
internal class HttpRetryAttempt
{
public ExportClientHttpResponse Response;
public TimeSpan? ThrottleDelay;
public int? ExpectedNextRetryDelayMilliseconds;
public bool ExpectedSuccess;
internal HttpRetryAttempt(
HttpStatusCode? statusCode,
TimeSpan? throttleDelay = null,
bool isDeadlineExceeded = false,
int expectedNextRetryDelayMilliseconds = 1500,
bool expectedSuccess = true)
{
this.ThrottleDelay = throttleDelay;
HttpResponseMessage? responseMessage = null;
if (statusCode != null)
{
responseMessage = new HttpResponseMessage();
if (throttleDelay != null)
{
responseMessage.Headers.RetryAfter = new RetryConditionHeaderValue(throttleDelay.Value);
}
responseMessage.StatusCode = (HttpStatusCode)statusCode;
}
// Using arbitrary +1 hr for deadline for test purposes.
var deadlineUtc = isDeadlineExceeded ? DateTime.UtcNow.AddMilliseconds(-1) : DateTime.UtcNow.AddHours(1);
this.Response = new ExportClientHttpResponse(expectedSuccess, deadlineUtc, responseMessage, new HttpRequestException());
this.ExpectedNextRetryDelayMilliseconds = expectedNextRetryDelayMilliseconds;
this.ExpectedSuccess = expectedSuccess;
}
}
}
}

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Collections;
using System.Globalization;
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Metrics;
using Xunit;
@ -190,12 +191,13 @@ public class OtlpSpecConfigDefinitionTests : IEnumerable<object[]>
public ConfigurationBuilder AddToConfiguration(ConfigurationBuilder configurationBuilder)
{
Dictionary<string, string?> dictionary = new();
dictionary[this.EndpointKeyName] = this.EndpointValue;
dictionary[this.HeadersKeyName] = this.HeadersValue;
dictionary[this.TimeoutKeyName] = this.TimeoutValue;
dictionary[this.ProtocolKeyName] = this.ProtocolValue;
Dictionary<string, string?> dictionary = new()
{
[this.EndpointKeyName] = this.EndpointValue,
[this.HeadersKeyName] = this.HeadersValue,
[this.TimeoutKeyName] = this.TimeoutValue,
[this.ProtocolKeyName] = this.ProtocolValue,
};
this.OnAddToDictionary(dictionary);
@ -228,7 +230,7 @@ public class OtlpSpecConfigDefinitionTests : IEnumerable<object[]>
{
Assert.Equal(new Uri(this.EndpointValue), otlpExporterOptions.Endpoint);
Assert.Equal(this.HeadersValue, otlpExporterOptions.Headers);
Assert.Equal(int.Parse(this.TimeoutValue), otlpExporterOptions.TimeoutMilliseconds);
Assert.Equal(int.Parse(this.TimeoutValue, CultureInfo.InvariantCulture), otlpExporterOptions.TimeoutMilliseconds);
if (!OtlpExportProtocolParser.TryParse(this.ProtocolValue, out var protocol))
{
@ -291,7 +293,11 @@ public class OtlpSpecConfigDefinitionTests : IEnumerable<object[]>
public void AssertMatches(MetricReaderOptions metricReaderOptions)
{
#if NET
Assert.Equal(Enum.Parse<MetricReaderTemporalityPreference>(this.TemporalityValue), metricReaderOptions.TemporalityPreference);
#else
Assert.Equal(Enum.Parse(typeof(MetricReaderTemporalityPreference), this.TemporalityValue), metricReaderOptions.TemporalityPreference);
#endif
}
protected override void OnSetEnvVars()

View File

@ -161,7 +161,9 @@ public sealed class OtlpTraceExporterTests : IDisposable
.SetResourceBuilder(resourceBuilder)
.AddSource(sources[0].Name)
.AddSource(sources[1].Name)
#pragma warning disable CA2000 // Dispose objects before losing scope
.AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems)));
#pragma warning restore CA2000 // Dispose objects before losing scope
using var openTelemetrySdk = builder.Build();
@ -248,7 +250,9 @@ public sealed class OtlpTraceExporterTests : IDisposable
.SetResourceBuilder(resourceBuilder)
.AddSource(activitySourceWithTags.Name)
.AddSource(activitySourceWithoutTags.Name)
#pragma warning disable CA2000 // Dispose objects before losing scope
.AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems)));
#pragma warning restore CA2000 // Dispose objects before losing scope
using var openTelemetrySdk = builder.Build();
@ -336,7 +340,9 @@ public sealed class OtlpTraceExporterTests : IDisposable
var builder = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource(activitySource.Name)
#pragma warning disable CA2000 // Dispose objects before losing scope
.AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems)));
#pragma warning restore CA2000 // Dispose objects before losing scope
using var openTelemetrySdk = builder.Build();
@ -363,7 +369,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
Assert.Equal(3, scope.Attributes.Count);
Assert.Equal(1u, scope.DroppedAttributesCount);
Assert.Equal("1234", scope.Attributes[0].Value.StringValue);
this.ArrayValueAsserts(scope.Attributes[1].Value.ArrayValue.Values);
ArrayValueAsserts(scope.Attributes[1].Value.ArrayValue.Values);
Assert.Equal(new object().ToString()!.Substring(0, 4), scope.Attributes[2].Value.StringValue);
}
}
@ -410,7 +416,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
Assert.Equal(3, otlpSpan.Attributes.Count);
Assert.Equal(1u, otlpSpan.DroppedAttributesCount);
Assert.Equal("1234", otlpSpan.Attributes[0].Value.StringValue);
this.ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values);
ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values);
Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue);
Assert.Single(otlpSpan.Events);
@ -418,7 +424,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
Assert.Equal(3, otlpSpan.Events[0].Attributes.Count);
Assert.Equal(1u, otlpSpan.Events[0].DroppedAttributesCount);
Assert.Equal("1234", otlpSpan.Events[0].Attributes[0].Value.StringValue);
this.ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values);
ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values);
Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Events[0].Attributes[2].Value.StringValue);
Assert.Single(otlpSpan.Links);
@ -426,7 +432,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
Assert.Equal(3, otlpSpan.Links[0].Attributes.Count);
Assert.Equal(1u, otlpSpan.Links[0].DroppedAttributesCount);
Assert.Equal("1234", otlpSpan.Links[0].Attributes[0].Value.StringValue);
this.ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values);
ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values);
Assert.Equal(new object().ToString()!.Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue);
}
@ -437,6 +443,11 @@ public sealed class OtlpTraceExporterTests : IDisposable
using var rootActivity = activitySource.StartActivity("root", ActivityKind.Producer);
bool[] boolArray = [true, false];
int[] intArray = [1, 2];
double[] doubleArray = [1.0, 2.09];
string[] stringArray = ["a", "b"];
var attributes = new List<KeyValuePair<string, object?>>
{
new("bool", true),
@ -445,10 +456,10 @@ public sealed class OtlpTraceExporterTests : IDisposable
new("double", 3.14),
new("int", 1),
new("datetime", DateTime.UtcNow),
new("bool_array", new bool[] { true, false }),
new("int_array", new int[] { 1, 2 }),
new("double_array", new double[] { 1.0, 2.09 }),
new("string_array", new string[] { "a", "b" }),
new("bool_array", boolArray),
new("int_array", intArray),
new("double_array", doubleArray),
new("string_array", stringArray),
new("datetime_array", new DateTime[] { DateTime.UtcNow, DateTime.Now }),
};
@ -761,7 +772,9 @@ public sealed class OtlpTraceExporterTests : IDisposable
public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor()
{
const string ActivitySourceName = "otlp.test";
#pragma warning disable CA2000 // Dispose objects before losing scope
TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
#pragma warning restore CA2000 // Dispose objects before losing scope
bool startCalled = false;
bool endCalled = false;
@ -798,7 +811,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
var exportClientMock = new TestExportClient();
var exporterOptions = new OtlpExporterOptions();
var transmissionHandler = new OtlpExporterTransmissionHandler(exportClientMock, exporterOptions.TimeoutMilliseconds);
using var transmissionHandler = new OtlpExporterTransmissionHandler(exportClientMock, exporterOptions.TimeoutMilliseconds);
using var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, DefaultExperimentalOptions, transmissionHandler);
exporter.Shutdown();
@ -1030,7 +1043,7 @@ public sealed class OtlpTraceExporterTests : IDisposable
return request;
}
private void ArrayValueAsserts(RepeatedField<OtlpCommon.AnyValue> values)
private static void ArrayValueAsserts(RepeatedField<OtlpCommon.AnyValue> values)
{
var expectedStringArray = new string?[] { "1234", "1234", string.Empty, null };
for (var i = 0; i < expectedStringArray.Length; ++i)

View File

@ -5,7 +5,7 @@ using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
internal class TestExportClient(bool throwException = false) : IExportClient
internal sealed class TestExportClient(bool throwException = false) : IExportClient
{
public bool SendExportRequestCalled { get; private set; }
@ -17,7 +17,7 @@ internal class TestExportClient(bool throwException = false) : IExportClient
{
if (this.ThrowException)
{
throw new Exception("Exception thrown from SendExportRequest");
throw new InvalidOperationException("Exception thrown from SendExportRequest");
}
this.SendExportRequestCalled = true;
@ -30,7 +30,7 @@ internal class TestExportClient(bool throwException = false) : IExportClient
return true;
}
private class TestExportClientResponse : ExportClientResponse
private sealed class TestExportClientResponse : ExportClientResponse
{
public TestExportClientResponse(bool success, DateTime deadline, Exception? exception)
: base(success, deadline, exception)

View File

@ -7,16 +7,20 @@ using System.Net.Http;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
internal class TestHttpMessageHandler : HttpMessageHandler
internal sealed class TestHttpMessageHandler : HttpMessageHandler
{
public HttpRequestMessage? HttpRequestMessage { get; private set; }
public byte[]? HttpRequestContent { get; private set; }
public virtual HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken)
public HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.HttpRequestMessage = request;
#if NET
this.HttpRequestContent = request.Content!.ReadAsByteArrayAsync(cancellationToken).Result;
#else
this.HttpRequestContent = request.Content!.ReadAsByteArrayAsync().Result;
#endif
return new HttpResponseMessage();
}

View File

@ -14,7 +14,7 @@ using Xunit;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
[Collection("EnvVars")]
public class UseOtlpExporterExtensionTests : IDisposable
public sealed class UseOtlpExporterExtensionTests : IDisposable
{
public UseOtlpExporterExtensionTests()
{