opentelemetry-dotnet/test/OpenTelemetry.Tests/Resources/ResourceTest.cs

602 lines
22 KiB
C#

// <copyright file="ResourceTest.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace OpenTelemetry.Resources.Tests
{
public class ResourceTest : IDisposable
{
private const string KeyName = "key";
private const string ValueName = "value";
public ResourceTest()
{
ClearEnvVars();
}
public void Dispose()
{
ClearEnvVars();
GC.SuppressFinalize(this);
}
[Fact]
public void CreateResource_NullAttributeCollection()
{
// Act and Assert
var resource = new Resource(null);
Assert.Empty(resource.Attributes);
}
[Fact]
public void CreateResource_NullAttributeValue()
{
// Arrange
var attributes = new Dictionary<string, object> { { "NullValue", null } };
// Act and Assert
Assert.Throws<ArgumentException>(() => new Resource(attributes));
}
[Fact]
public void CreateResource_EmptyAttributeKey()
{
// Arrange
var attributes = new Dictionary<string, object> { { string.Empty, "value" } };
// Act
var resource = new Resource(attributes);
// Assert
Assert.Single(resource.Attributes);
var attribute = resource.Attributes.Single();
Assert.Empty(attribute.Key);
Assert.Equal("value", attribute.Value);
}
[Fact]
public void CreateResource_EmptyAttributeValue()
{
// Arrange
var attributes = new Dictionary<string, object> { { "EmptyValue", string.Empty } };
// does not throw
var resource = new Resource(attributes);
// Assert
Assert.Single(resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("EmptyValue", string.Empty), resource.Attributes);
}
[Fact]
public void CreateResource_EmptyArray()
{
// Arrange
var attributes = new Dictionary<string, object> { { "EmptyArray", Array.Empty<string>() } };
// does not throw
var resource = new Resource(attributes);
// Assert
Assert.Single(resource.Attributes);
Assert.Equal(Array.Empty<string>(), resource.Attributes.Where(x => x.Key == "EmptyArray").FirstOrDefault().Value);
}
[Fact]
public void CreateResource_EmptyAttribute()
{
// Arrange
var attributeCount = 0;
var attributes = CreateAttributes(attributeCount);
// Act
var resource = new Resource(attributes);
// Assert
ValidateResource(resource, attributeCount);
}
[Fact]
public void CreateResource_SingleAttribute()
{
// Arrange
var attributeCount = 1;
var attributes = CreateAttributes(attributeCount);
// Act
var resource = new Resource(attributes);
// Assert
ValidateResource(resource, attributeCount);
}
[Fact]
public void CreateResource_MultipleAttribute()
{
// Arrange
var attributeCount = 5;
var attributes = CreateAttributes(attributeCount);
// Act
var resource = new Resource(attributes);
// Assert
ValidateResource(resource, attributeCount);
}
[Fact]
public void CreateResource_SupportedAttributeTypes()
{
// Arrange
var attributes = new Dictionary<string, object>
{
{ "string", "stringValue" },
{ "bool", true },
{ "double", 0.1d },
{ "long", 1L },
// int and float supported by conversion to long and double
{ "int", 1 },
{ "short", (short)1 },
{ "float", 0.1f },
};
// Act
var resource = new Resource(attributes);
// Assert
Assert.Equal(7, resource.Attributes.Count());
Assert.Contains(new KeyValuePair<string, object>("string", "stringValue"), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("bool", true), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("double", 0.1d), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("long", 1L), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("int", 1L), resource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("short", 1L), resource.Attributes);
double convertedFloat = Convert.ToDouble(0.1f, System.Globalization.CultureInfo.InvariantCulture);
Assert.Contains(new KeyValuePair<string, object>("float", convertedFloat), resource.Attributes);
}
[Fact]
public void CreateResource_SupportedAttributeArrayTypes()
{
// Arrange
var attributes = new Dictionary<string, object>
{
// natively supported array types
{ "string arr", new string[] { "stringValue" } },
{ "bool arr", new bool[] { true } },
{ "double arr", new double[] { 0.1d } },
{ "long arr", new long[] { 1L } },
// have to convert to other primitive array types
{ "int arr", new int[] { 1 } },
{ "short arr", new short[] { (short)1 } },
{ "float arr", new float[] { 0.1f } },
};
// Act
var resource = new Resource(attributes);
// Assert
Assert.Equal(7, resource.Attributes.Count());
Assert.Equal(new string[] { "stringValue" }, resource.Attributes.Where(x => x.Key == "string arr").FirstOrDefault().Value);
Assert.Equal(new bool[] { true }, resource.Attributes.Where(x => x.Key == "bool arr").FirstOrDefault().Value);
Assert.Equal(new double[] { 0.1d }, resource.Attributes.Where(x => x.Key == "double arr").FirstOrDefault().Value);
Assert.Equal(new long[] { 1L }, resource.Attributes.Where(x => x.Key == "long arr").FirstOrDefault().Value);
var longArr = new long[] { 1 };
var doubleArr = new double[] { Convert.ToDouble(0.1f, System.Globalization.CultureInfo.InvariantCulture) };
Assert.Equal(longArr, resource.Attributes.Where(x => x.Key == "int arr").FirstOrDefault().Value);
Assert.Equal(longArr, resource.Attributes.Where(x => x.Key == "short arr").FirstOrDefault().Value);
Assert.Equal(doubleArr, resource.Attributes.Where(x => x.Key == "float arr").FirstOrDefault().Value);
}
[Fact]
public void CreateResource_NotSupportedAttributeTypes()
{
var attributes = new Dictionary<string, object>
{
{ "dynamic", new { } },
{ "array", new int[1] },
{ "complex", this },
};
Assert.Throws<ArgumentException>(() => new Resource(attributes));
}
[Fact]
public void MergeResource_EmptyAttributeSource_MultiAttributeTarget()
{
// Arrange
var sourceAttributeCount = 0;
var sourceAttributes = CreateAttributes(sourceAttributeCount);
var sourceResource = new Resource(sourceAttributes);
var otherAttributeCount = 3;
var otherAttributes = CreateAttributes(otherAttributeCount);
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
ValidateResource(newResource, sourceAttributeCount + otherAttributeCount);
}
[Fact]
public void MergeResource_MultiAttributeSource_EmptyAttributeTarget()
{
// Arrange
var sourceAttributeCount = 3;
var sourceAttributes = CreateAttributes(sourceAttributeCount);
var sourceResource = new Resource(sourceAttributes);
var otherAttributeCount = 0;
var otherAttributes = CreateAttributes(otherAttributeCount);
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
ValidateResource(newResource, sourceAttributeCount + otherAttributeCount);
}
[Fact]
public void MergeResource_MultiAttributeSource_MultiAttributeTarget_NoOverlap()
{
// Arrange
var sourceAttributeCount = 3;
var sourceAttributes = CreateAttributes(sourceAttributeCount);
var sourceResource = new Resource(sourceAttributes);
var otherAttributeCount = 3;
var otherAttributes = CreateAttributes(otherAttributeCount, sourceAttributeCount);
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
ValidateResource(newResource, sourceAttributeCount + otherAttributeCount);
}
[Fact]
public void MergeResource_MultiAttributeSource_MultiAttributeTarget_SingleOverlap()
{
// Arrange
var sourceAttributeCount = 3;
var sourceAttributes = CreateAttributes(sourceAttributeCount);
var sourceResource = new Resource(sourceAttributes);
var otherAttributeCount = 3;
var otherAttributes = CreateAttributes(otherAttributeCount, sourceAttributeCount - 1);
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
ValidateResource(newResource, sourceAttributeCount + otherAttributeCount - 1);
// Also verify target attributes were not overwritten
foreach (var otherAttribute in otherAttributes)
{
Assert.Contains(otherAttribute, otherResource.Attributes);
}
}
[Fact]
public void MergeResource_MultiAttributeSource_MultiAttributeTarget_FullOverlap()
{
// Arrange
var sourceAttributeCount = 3;
var sourceAttributes = CreateAttributes(sourceAttributeCount);
var sourceResource = new Resource(sourceAttributes);
var otherAttributeCount = 3;
var otherAttributes = CreateAttributes(otherAttributeCount);
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
ValidateResource(newResource, otherAttributeCount);
// Also verify target attributes were not overwritten
foreach (var otherAttribute in otherAttributes)
{
Assert.Contains(otherAttribute, otherResource.Attributes);
}
}
[Fact]
public void MergeResource_MultiAttributeSource_DuplicatedKeysInPrimary()
{
// Arrange
var sourceAttributes = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("key1", "value1"),
new KeyValuePair<string, object>("key1", "value1.1"),
};
var sourceResource = new Resource(sourceAttributes);
var otherAttributes = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("key2", "value2"),
};
var otherResource = new Resource(otherAttributes);
// Act
var newResource = sourceResource.Merge(otherResource);
// Assert
Assert.NotSame(otherResource, newResource);
Assert.NotSame(sourceResource, newResource);
Assert.Equal(2, newResource.Attributes.Count());
Assert.Contains(new KeyValuePair<string, object>("key1", "value1"), newResource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("key2", "value2"), newResource.Attributes);
}
[Fact]
public void MergeResource_UpdatingResourceOverridesCurrentResource()
{
// Arrange
var currentAttributes = new Dictionary<string, object> { { "value", "currentValue" } };
var updatingAttributes = new Dictionary<string, object> { { "value", "updatedValue" } };
var currentResource = new Resource(currentAttributes);
var updatingResource = new Resource(updatingAttributes);
var newResource = currentResource.Merge(updatingResource);
// Assert
Assert.Single(newResource.Attributes);
Assert.Contains(new KeyValuePair<string, object>("value", "updatedValue"), newResource.Attributes);
}
[Fact]
public void GetResourceWithTelemetrySDKAttributes()
{
// Arrange
var resource = ResourceBuilder.CreateDefault().AddTelemetrySdk().Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(4, attributes.Count());
ValidateTelemetrySdkAttributes(attributes);
}
[Fact]
public void GetResourceWithDefaultAttributes_EmptyResource()
{
// Arrange
var resource = ResourceBuilder.CreateDefault().Build();
// Assert
var attributes = resource.Attributes;
Assert.Single(attributes);
ValidateDefaultAttributes(attributes);
}
[Fact]
public void GetResourceWithDefaultAttributes_ResourceWithAttrs()
{
// Arrange
var resource = ResourceBuilder.CreateDefault().AddAttributes(CreateAttributes(2)).Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(3, attributes.Count());
ValidateAttributes(attributes, 0, 1);
ValidateDefaultAttributes(attributes);
}
[Fact]
public void GetResourceWithDefaultAttributes_WithResourceEnvVar()
{
// Arrange
Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2");
var resource = ResourceBuilder.CreateDefault().AddAttributes(CreateAttributes(2)).Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(5, attributes.Count());
ValidateAttributes(attributes, 0, 1);
ValidateDefaultAttributes(attributes);
Assert.Contains(new KeyValuePair<string, object>("EVKey1", "EVVal1"), attributes);
Assert.Contains(new KeyValuePair<string, object>("EVKey2", "EVVal2"), attributes);
}
[Fact]
public void EnvironmentVariableDetectors_DoNotDuplicateAttributes()
{
// Arrange
Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2");
var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddEnvironmentVariableDetector().Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(3, attributes.Count());
Assert.Contains(new KeyValuePair<string, object>("EVKey1", "EVVal1"), attributes);
Assert.Contains(new KeyValuePair<string, object>("EVKey2", "EVVal2"), attributes);
}
[Fact]
public void GetResource_WithServiceEnvVar()
{
// Arrange
Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "some-service");
var resource = ResourceBuilder.CreateDefault().AddAttributes(CreateAttributes(2)).Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(3, attributes.Count());
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "some-service"), attributes);
}
[Fact]
public void GetResource_WithServiceNameSetWithTwoEnvVars()
{
// Arrange
Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr");
Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name");
var resource = ResourceBuilder.CreateDefault().AddAttributes(CreateAttributes(2)).Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(3, attributes.Count());
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "from-service-name"), attributes);
}
[Fact]
public void GetResource_WithServiceNameSetWithTwoEnvVarsAndCode()
{
// Arrange
Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr");
Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name");
var resource = ResourceBuilder.CreateDefault().AddService("from-code").AddAttributes(CreateAttributes(2)).Build();
// Assert
var attributes = resource.Attributes;
Assert.Equal(4, attributes.Count());
ValidateAttributes(attributes, 0, 1);
Assert.Contains(new KeyValuePair<string, object>("service.name", "from-code"), attributes);
}
[Fact]
public void ResourceBuilder_ServiceProvider_Available()
{
var builder = ResourceBuilder.CreateDefault();
bool nullTestRun = false;
builder.AddDetector(sp =>
{
nullTestRun = true;
Assert.Null(sp);
return new NoopResourceDetector();
});
builder.Build();
Assert.True(nullTestRun);
builder = ResourceBuilder.CreateDefault();
bool validTestRun = false;
var serviceCollection = new ServiceCollection();
using var serviceProvider = serviceCollection.BuildServiceProvider();
builder.ServiceProvider = serviceProvider;
builder.AddDetector(sp =>
{
validTestRun = true;
Assert.NotNull(sp);
return new NoopResourceDetector();
});
builder.Build();
Assert.True(validTestRun);
}
private static void ClearEnvVars()
{
Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, null);
Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, null);
}
private static void AddAttributes(Dictionary<string, object> attributes, int attributeCount, int startIndex = 0)
{
for (var i = startIndex; i < attributeCount + startIndex; ++i)
{
attributes.Add($"{KeyName}{i}", $"{ValueName}{i}");
}
}
private static void ValidateAttributes(IEnumerable<KeyValuePair<string, object>> attributes, int startIndex = 0, int endIndex = 0)
{
var keyValuePairs = attributes as KeyValuePair<string, object>[] ?? attributes.ToArray();
var endInd = endIndex == 0 ? keyValuePairs.Length - 1 : endIndex;
for (var i = startIndex; i <= endInd; ++i)
{
Assert.Contains(new KeyValuePair<string, object>($"{KeyName}{i}", $"{ValueName}{i}"), keyValuePairs);
}
}
private static void ValidateResource(Resource resource, int attributeCount)
{
Assert.NotNull(resource);
Assert.NotNull(resource.Attributes);
Assert.Equal(attributeCount, resource.Attributes.Count());
ValidateAttributes(resource.Attributes);
}
private static void ValidateTelemetrySdkAttributes(IEnumerable<KeyValuePair<string, object>> attributes)
{
Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.name", "opentelemetry"), attributes);
Assert.Contains(new KeyValuePair<string, object>("telemetry.sdk.language", "dotnet"), attributes);
var versionAttribute = attributes.Where(pair => pair.Key.Equals("telemetry.sdk.version"));
Assert.Single(versionAttribute);
}
private static void ValidateDefaultAttributes(IEnumerable<KeyValuePair<string, object>> attributes)
{
var serviceName = attributes.Where(pair => pair.Key.Equals("service.name"));
Assert.Single(serviceName);
Assert.Contains("unknown_service", serviceName.FirstOrDefault().Value as string);
}
private static Dictionary<string, object> CreateAttributes(int attributeCount, int startIndex = 0)
{
var attributes = new Dictionary<string, object>();
AddAttributes(attributes, attributeCount, startIndex);
return attributes;
}
private sealed class NoopResourceDetector : IResourceDetector
{
public Resource Detect() => Resource.Empty;
}
}
}