Skip to content

Commit

Permalink
Azure Monitor Exporter: Basic Integration Tests (Azure#15539)
Browse files Browse the repository at this point in the history
* adding empty IntegrationTest projects

* demo tests working with webapp

* update basic tests

* refactor AzureMonitorTransmitter

* extra controller

* saving work in progress

* saving work in progress

* saving work in progress

* saving work in progress

* progress

* saving progress

* cleanup

* cleanup

* cleanup

* cleanup

* ForceFlush

* cleanup

* change

* cleanup

* cleanup

* cleanup

* resolving conflicts

* new projects

* suppress warning NetSDK1005

* code review cleanup

* remove Microsoft.AspNetCore.App to support net461

* fix references

* cleanup

* addressing code review comments

* removing packagereference versions

* renaming projects

* cleanup controllers

* testing a fix for test project

* testing fix for webapp project

* testing fix for build error

* testing build fix

* rename

* fixing merge conflicts. tests are failing

* more renamings, investigating build fail

* fix namespace

* fix other test

* test to disable parallel tests

* testing fixes for POC

* refactor

* cleanup

* saving progress, tests working

* cleanup

* fix merge conflict

* cleanup

* cleanup
  • Loading branch information
TimothyMothra authored Oct 14, 2020
1 parent 3b4fe10 commit 2d44823
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ namespace OpenTelemetry.Exporter.AzureMonitor
{
public class AzureMonitorTraceExporter : ActivityExporter
{
private readonly AzureMonitorTransmitter AzureMonitorTransmitter;
private readonly ITransmitter Transmitter;
private readonly AzureMonitorExporterOptions options;
private readonly string instrumentationKey;

public AzureMonitorTraceExporter(AzureMonitorExporterOptions options)
public AzureMonitorTraceExporter(AzureMonitorExporterOptions options) : this(options, new AzureMonitorTransmitter(options))
{
}

internal AzureMonitorTraceExporter(AzureMonitorExporterOptions options, ITransmitter transmitter)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
ConnectionString.ConnectionStringParser.GetValues(this.options.ConnectionString, out this.instrumentationKey, out _);

this.AzureMonitorTransmitter = new AzureMonitorTransmitter(options);
this.Transmitter = transmitter;
}

/// <inheritdoc/>
Expand All @@ -34,7 +38,7 @@ public override ExportResult Export(in Batch<Activity> batch)

// TODO: Handle return value, it can be converted as metrics.
// TODO: Validate CancellationToken and async pattern here.
this.AzureMonitorTransmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
this.Transmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
return ExportResult.Success;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace OpenTelemetry.Exporter.AzureMonitor
/// <summary>
/// This class encapsulates transmitting a collection of <see cref="TelemetryItem"/> to the configured Ingestion Endpoint.
/// </summary>
internal class AzureMonitorTransmitter
internal class AzureMonitorTransmitter : ITransmitter
{
private readonly ApplicationInsightsRestClient applicationInsightsRestClient;
private readonly AzureMonitorExporterOptions options;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

using OpenTelemetry.Exporter.AzureMonitor.Models;

namespace OpenTelemetry.Exporter.AzureMonitor
{
internal interface ITransmitter
{
/// <summary>
/// Sent telemetry and return the number of items Accepted.
/// </summary>
ValueTask<int> TrackAsync(IEnumerable<TelemetryItem> telemetryItems, bool async, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.AzureMonitor.Integration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d15ddcb29688295338af4b7686603fe614abd555e09efba8fb88ee09e1f7b1ccaeed2e8f823fa9eef3fdd60217fc012ea67d2479751a0b8c087a4185541b851bd8b16f8d91b840e51b1cb0ba6fe647997e57429265e85ef62d565db50a69ae1647d54d7bd855e4db3d8a91510e5bcbd0edfbbecaa20a7bd9ae74593daa7b11b4")]

// Moq
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework;

using Xunit;
using Xunit.Abstractions;

namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests
{
public class OpenTelemetryTests : IClassFixture<OpenTelemetryWebApplicationFactory<AspNetCoreWebApp.Startup>>
{
private readonly OpenTelemetryWebApplicationFactory<AspNetCoreWebApp.Startup> factory;
private readonly ITestOutputHelper output;

public OpenTelemetryTests(OpenTelemetryWebApplicationFactory<AspNetCoreWebApp.Startup> factory, ITestOutputHelper output)
{
this.factory = factory;
this.output = output;
}

/// <summary>
/// This test validates that when an app instrumented with the AzureMonitorExporter receives an HTTP request,
/// A TelemetryItem is created matching that request.
/// </summary>
[Fact]
public async Task ProofOfConcept()
{
string testValue = Guid.NewGuid().ToString();

// Arrange
var client = this.factory.CreateClient();

//// Act
var response = await client.GetAsync($"api/home/{testValue}");

// Shutdown
response.EnsureSuccessStatusCode();
Task.Delay(100).Wait(); //TODO: HOW TO REMOVE THIS WAIT?
this.factory.ForceFlush();

// Assert
Assert.True(this.factory.TelemetryItems.Any(), "test project did not capture telemetry");

PrintTelemetryItems(this.factory.TelemetryItems);
var item = this.factory.TelemetryItems.Single();
var baseData = (Models.RequestData)item.Data.BaseData;
Assert.True(baseData.Url.EndsWith(testValue), "it is expected that the recorded TelemetryItem matches the value of testValue.");
}

/// <summary>
/// This uses the XUnit ITestOutputHelper to print details to the output of the test run.
/// </summary>
private void PrintTelemetryItems(IEnumerable<Models.TelemetryItem> telemetryItems)
{
foreach (var item in telemetryItems)
{
this.output.WriteLine(item.Name);

if (item.Data.BaseData is Models.RequestData requestData)
{
this.output.WriteLine($"\t{requestData.Url}");
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using OpenTelemetry.Exporter.AzureMonitor.Models;

namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework
{
public class MockTransmitter : ITransmitter
{
public ConcurrentBag<TelemetryItem> TelemetryItems = new ConcurrentBag<TelemetryItem>();

public ValueTask<int> TrackAsync(IEnumerable<TelemetryItem> telemetryItems, bool async, CancellationToken cancellationToken)
{
foreach (var telemetryItem in telemetryItems)
{
this.TelemetryItems.Add(telemetryItem);
}

return new ValueTask<int>(Task.FromResult(telemetryItems.Count()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Concurrent;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;

using OpenTelemetry.Exporter.AzureMonitor.Models;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Exporter.AzureMonitor.Integration.Tests.TestFramework
{
/// <summary>
/// This class implements <see cref="WebApplicationFactory"/> and will configure the <see cref="IServiceCollection"/> for OpenTelemetry and the <see cref="AzureMonitorTraceExporter"/>.
/// Here we mock the <see cref="ServiceRestClient"/> to capture telemetry that would have been sent to the ingestion service.
/// We also mock the <see cref="TrackResponse"/> which would have been received from the ingestion service.
/// </summary>
/// <typeparam name="TStartup">Startup class from the application to be used during this test.</typeparam>
public class OpenTelemetryWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
public ConcurrentBag<TelemetryItem> TelemetryItems => this.Transmitter.TelemetryItems;

private const string EmptyConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000";
private readonly MockTransmitter Transmitter = new MockTransmitter();
private ActivityProcessor ActivityProcessor;

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
this.ActivityProcessor = new BatchExportActivityProcessor(new AzureMonitorTraceExporter(
options: new AzureMonitorExporterOptions
{
ConnectionString = EmptyConnectionString,
},
transmitter: this.Transmitter));

builder.ConfigureServices(services => services.AddOpenTelemetryTracing((builder) => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddProcessor(this.ActivityProcessor)));
}

public void ForceFlush() => this.ActivityProcessor.ForceFlush();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private void GetInternalFields(AzureMonitorTraceExporter exporter, out string ik
.ToString();

var transmitter = typeof(AzureMonitorTraceExporter)
.GetField("AzureMonitorTransmitter", BindingFlags.Instance | BindingFlags.NonPublic)
.GetField("Transmitter", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(exporter);

var serviceRestClient = typeof(AzureMonitorTransmitter)
Expand Down

0 comments on commit 2d44823

Please sign in to comment.