//
// 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 Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenTelemetry.Resources;
using Xunit;
namespace OpenTelemetry.Metrics.Tests
{
public class MeterProviderBuilderExtensionsTests
{
[Fact]
public void ServiceLifecycleAvailableToSDKBuilderTest()
{
var builder = Sdk.CreateMeterProviderBuilder();
MyInstrumentation myInstrumentation = null;
RunBuilderServiceLifecycleTest(
builder,
() =>
{
var provider = builder.Build() as MeterProviderSdk;
// Note: Build can only be called once
Assert.Throws(() => builder.Build());
Assert.NotNull(provider);
Assert.NotNull(provider.OwnedServiceProvider);
myInstrumentation = ((IServiceProvider)provider.OwnedServiceProvider).GetRequiredService();
return provider;
},
provider =>
{
provider.Dispose();
});
Assert.NotNull(myInstrumentation);
Assert.True(myInstrumentation.Disposed);
}
[Fact]
public void ServiceLifecycleAvailableToServicesBuilderTest()
{
var services = new ServiceCollection();
bool testRun = false;
ServiceProvider serviceProvider = null;
MeterProviderSdk provider = null;
services.AddOpenTelemetry().WithMetrics(builder =>
{
testRun = true;
RunBuilderServiceLifecycleTest(
builder,
() =>
{
// Note: Build can't be called directly on builder tied to external services
Assert.Throws(() => builder.Build());
serviceProvider = services.BuildServiceProvider();
provider = serviceProvider.GetRequiredService() as MeterProviderSdk;
Assert.NotNull(provider);
Assert.Null(provider.OwnedServiceProvider);
return provider;
},
(provider) => { });
});
Assert.True(testRun);
Assert.NotNull(serviceProvider);
Assert.NotNull(provider);
Assert.False(provider.Disposed);
serviceProvider.Dispose();
Assert.True(provider.Disposed);
}
[Fact]
public void SingleProviderForServiceCollectionTest()
{
var services = new ServiceCollection();
services.AddOpenTelemetry().WithMetrics(builder =>
{
builder.AddInstrumentation(() => new());
});
services.AddOpenTelemetry().WithMetrics(builder =>
{
builder.AddInstrumentation(() => new());
});
using var serviceProvider = services.BuildServiceProvider();
Assert.NotNull(serviceProvider);
var meterProviders = serviceProvider.GetServices();
Assert.Single(meterProviders);
var provider = meterProviders.First() as MeterProviderSdk;
Assert.NotNull(provider);
Assert.Equal(2, provider.Instrumentations.Count);
}
[Fact]
public void AddReaderUsingDependencyInjectionTest()
{
var builder = Sdk.CreateMeterProviderBuilder();
builder.AddReader();
builder.AddReader();
using var provider = builder.Build() as MeterProviderSdk;
Assert.NotNull(provider);
var readers = ((IServiceProvider)provider.OwnedServiceProvider).GetServices();
// Note: Two "Add" calls but it is a singleton so only a single registration is produced
Assert.Single(readers);
var reader = provider.Reader as CompositeMetricReader;
Assert.NotNull(reader);
// Note: Two "Add" calls due yield two readers added to provider, even though they are the same
Assert.True(reader.Head.Value is MyReader);
Assert.True(reader.Head.Next?.Value is MyReader);
}
[Fact]
public void SetAndConfigureResourceTest()
{
var builder = Sdk.CreateMeterProviderBuilder();
int configureInvocations = 0;
bool serviceProviderTestExecuted = false;
builder.SetResourceBuilder(ResourceBuilder.CreateEmpty().AddService("Test"));
builder.ConfigureResource(builder =>
{
configureInvocations++;
Assert.Single(builder.ResourceDetectors);
builder.AddAttributes(new Dictionary() { ["key1"] = "value1" });
Assert.Equal(2, builder.ResourceDetectors.Count);
});
builder.SetResourceBuilder(ResourceBuilder.CreateEmpty());
builder.ConfigureResource(builder =>
{
configureInvocations++;
Assert.Empty(builder.ResourceDetectors);
builder.AddDetector(sp =>
{
serviceProviderTestExecuted = true;
Assert.NotNull(sp);
return new ResourceBuilder.WrapperResourceDetector(new Resource(new Dictionary() { ["key2"] = "value2" }));
});
Assert.Single(builder.ResourceDetectors);
});
using var provider = builder.Build() as MeterProviderSdk;
Assert.True(serviceProviderTestExecuted);
Assert.Equal(2, configureInvocations);
Assert.Single(provider.Resource.Attributes);
Assert.Contains(provider.Resource.Attributes, kvp => kvp.Key == "key2" && (string)kvp.Value == "value2");
}
[Fact]
public void ConfigureBuilderIConfigurationAvailableTest()
{
Environment.SetEnvironmentVariable("TEST_KEY", "TEST_KEY_VALUE");
bool configureBuilderCalled = false;
using var provider = Sdk.CreateMeterProviderBuilder()
.ConfigureBuilder((sp, builder) =>
{
var configuration = sp.GetRequiredService();
configureBuilderCalled = true;
var testKeyValue = configuration.GetValue("TEST_KEY", null);
Assert.Equal("TEST_KEY_VALUE", testKeyValue);
})
.Build();
Assert.True(configureBuilderCalled);
Environment.SetEnvironmentVariable("TEST_KEY", null);
}
[Fact]
public void ConfigureBuilderIConfigurationModifiableTest()
{
bool configureBuilderCalled = false;
using var provider = Sdk.CreateMeterProviderBuilder()
.ConfigureServices(services =>
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary { ["TEST_KEY_2"] = "TEST_KEY_2_VALUE" })
.Build();
services.AddSingleton(configuration);
})
.ConfigureBuilder((sp, builder) =>
{
var configuration = sp.GetRequiredService();
configureBuilderCalled = true;
var testKey2Value = configuration.GetValue("TEST_KEY_2", null);
Assert.Equal("TEST_KEY_2_VALUE", testKey2Value);
})
.Build();
Assert.True(configureBuilderCalled);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MeterProviderNestedResolutionUsingBuilderTest(bool callNestedConfigure)
{
bool innerTestExecuted = false;
using var provider = Sdk.CreateMeterProviderBuilder()
.ConfigureServices(services =>
{
if (callNestedConfigure)
{
services.AddOpenTelemetry().WithMetrics(builder => { });
}
})
.ConfigureBuilder((sp, builder) =>
{
innerTestExecuted = true;
Assert.Throws(() => sp.GetService());
})
.Build();
Assert.True(innerTestExecuted);
Assert.Throws(() => provider.GetServiceProvider()?.GetService());
}
[Fact]
public void MeterProviderNestedResolutionUsingConfigureTest()
{
bool innerTestExecuted = false;
var services = new ServiceCollection();
services.AddOpenTelemetry().WithMetrics(builder =>
{
builder.ConfigureBuilder((sp, builder) =>
{
innerTestExecuted = true;
Assert.Throws(() => sp.GetService());
});
});
using var serviceProvider = services.BuildServiceProvider();
var resolvedProvider = serviceProvider.GetRequiredService();
Assert.True(innerTestExecuted);
}
private static void RunBuilderServiceLifecycleTest(
MeterProviderBuilder builder,
Func buildFunc,
Action postAction)
{
var baseBuilder = builder as MeterProviderBuilderBase;
builder.AddMeter("TestSource");
bool configureServicesCalled = false;
builder.ConfigureServices(services =>
{
configureServicesCalled = true;
Assert.NotNull(services);
services.TryAddSingleton();
services.TryAddSingleton();
// Note: This is strange to call ConfigureOpenTelemetryMeterProvider here, but supported
services.ConfigureOpenTelemetryMeterProvider((sp, b) =>
{
Assert.Throws(() => b.ConfigureServices(services => { }));
b.AddInstrumentation(sp.GetRequiredService());
});
});
int configureBuilderInvocations = 0;
builder.ConfigureBuilder((sp, builder) =>
{
configureBuilderInvocations++;
var sdkBuilder = builder as MeterProviderBuilderSdk;
Assert.NotNull(sdkBuilder);
builder.AddMeter("TestSource2");
Assert.Contains(sdkBuilder.MeterSources, s => s == "TestSource");
Assert.Contains(sdkBuilder.MeterSources, s => s == "TestSource2");
// Note: Services can't be configured at this stage
Assert.Throws(
() => builder.ConfigureServices(services => services.TryAddSingleton()));
builder.AddReader(sp.GetRequiredService());
builder.ConfigureBuilder((_, b) =>
{
// Note: ConfigureBuilder calls can be nested, this is supported
configureBuilderInvocations++;
b.ConfigureBuilder((_, _) =>
{
configureBuilderInvocations++;
});
});
});
var provider = buildFunc();
Assert.True(configureServicesCalled);
Assert.Equal(3, configureBuilderInvocations);
Assert.Single(provider.Instrumentations);
Assert.True(provider.Instrumentations[0] is MyInstrumentation);
Assert.True(provider.Reader is MyReader);
postAction(provider);
}
private sealed class MyInstrumentation : IDisposable
{
internal bool Disposed;
public void Dispose()
{
this.Disposed = true;
}
}
private sealed class MyReader : MetricReader
{
}
private sealed class MyExporter : BaseExporter
{
public override ExportResult Export(in Batch batch)
{
return ExportResult.Success;
}
}
}
}