Skip to content

Commit

Permalink
Fixed event source test randomly failing. (dotnet#35753)
Browse files Browse the repository at this point in the history
* Filtered events in listener by the service provider hash.
This prevents events from other test classes to impact the event source tests.
* Reused the listener to improve performance.
  • Loading branch information
koenigst committed Sep 6, 2024
1 parent 3b04bbe commit 46ee68e
Showing 1 changed file with 75 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,17 @@
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.Extensions.DependencyInjection.Tests
{
[CollectionDefinition(nameof(EventSourceTests), DisableParallelization = true)]
public class EventSourceTests : ICollectionFixture<EventSourceTests>
public class DependencyInjectionEventSourceTests(
DependencyInjectionEventSourceTests.TestEventListenerFixture fixture)
: IClassFixture<DependencyInjectionEventSourceTests.TestEventListenerFixture>
{
}

[Collection(nameof(EventSourceTests))]
public class DependencyInjectionEventSourceTests : IDisposable
{
private readonly TestEventListener _listener = new TestEventListener();

public DependencyInjectionEventSourceTests()
{
// clear the provider list in between tests
typeof(DependencyInjectionEventSource).GetField("_providers", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(DependencyInjectionEventSource.Log, new List<WeakReference<ServiceProvider>>());

_listener.EnableEvents(DependencyInjectionEventSource.Log, EventLevel.Verbose);
}
private TestEventListener Listener => fixture.Listener;

[Fact]
public void ExistsWithCorrectId()
Expand Down Expand Up @@ -58,9 +44,10 @@ public void EmitsCallSiteBuiltEvent()
serviceCollection.AddTransient<IFakeMultipleService, FakeDisposableCallbackInnerService>();
serviceCollection.AddSingleton<IFakeService, FakeDisposableCallbackInnerService>();

serviceCollection.BuildServiceProvider().GetService<IEnumerable<IFakeOuterService>>();
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<IEnumerable<IFakeOuterService>>();

var callsiteBuiltEvent = _listener.EventData.Single(e => e.EventName == "CallSiteBuilt");
var callsiteBuiltEvent = Listener.EventDataFor(serviceProvider).Single(e => e.EventName == "CallSiteBuilt");


Assert.Equal(
Expand Down Expand Up @@ -168,7 +155,7 @@ public void EmitsServiceResolvedEvent()
serviceProvider.GetService<IFakeService>();
serviceProvider.GetService<IFakeService>();

var serviceResolvedEvents = _listener.EventData.Where(e => e.EventName == "ServiceResolved").ToArray();
var serviceResolvedEvents = Listener.EventDataFor(serviceProvider).Where(e => e.EventName == "ServiceResolved").ToArray();

Assert.Equal(3, serviceResolvedEvents.Length);
foreach (var serviceResolvedEvent in serviceResolvedEvents)
Expand All @@ -189,15 +176,14 @@ public void EmitsExpressionTreeBuiltEvent()

serviceProvider.GetService<IFakeService>();

var expressionTreeGeneratedEvent = _listener.EventData.Single(e => e.EventName == "ExpressionTreeGenerated");
var expressionTreeGeneratedEvent = Listener.EventDataFor(serviceProvider).Single(e => e.EventName == "ExpressionTreeGenerated");

Assert.Equal("Microsoft.Extensions.DependencyInjection.Specification.Fakes.IFakeService", GetProperty<string>(expressionTreeGeneratedEvent, "serviceType"));
Assert.Equal(9, GetProperty<int>(expressionTreeGeneratedEvent, "nodeCount"));
Assert.Equal(3, expressionTreeGeneratedEvent.EventId);
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/35753", TestPlatforms.Windows)]
public void EmitsDynamicMethodBuiltEvent()
{
// Arrange
Expand All @@ -208,7 +194,7 @@ public void EmitsDynamicMethodBuiltEvent()

serviceProvider.GetService<IFakeService>();

var expressionTreeGeneratedEvent = _listener.EventData.Single(e => e.EventName == "DynamicMethodBuilt");
var expressionTreeGeneratedEvent = Listener.EventDataFor(serviceProvider).Single(e => e.EventName == "DynamicMethodBuilt");

Assert.Equal("Microsoft.Extensions.DependencyInjection.Specification.Fakes.IFakeService", GetProperty<string>(expressionTreeGeneratedEvent, "serviceType"));
Assert.Equal(12, GetProperty<int>(expressionTreeGeneratedEvent, "methodSize"));
Expand All @@ -228,7 +214,7 @@ public void EmitsScopeDisposedEvent()
scope.ServiceProvider.GetService<IFakeService>();
}

var scopeDisposedEvent = _listener.EventData.Single(e => e.EventName == "ScopeDisposed");
var scopeDisposedEvent = Listener.EventDataFor(serviceProvider).Single(e => e.EventName == "ScopeDisposed");

Assert.Equal(1, GetProperty<int>(scopeDisposedEvent, "scopedServicesResolved"));
Assert.Equal(1, GetProperty<int>(scopeDisposedEvent, "disposableServices"));
Expand All @@ -239,13 +225,15 @@ public void EmitsScopeDisposedEvent()
public void EmitsServiceRealizationFailedEvent()
{
var exception = new Exception("Test error.");
DependencyInjectionEventSource.Log.ServiceRealizationFailed(exception, 1234);
var serviceProvider = new ServiceCollection().BuildServiceProvider();
int hashCode = serviceProvider.GetHashCode();
DependencyInjectionEventSource.Log.ServiceRealizationFailed(exception, hashCode);

var eventName = nameof(DependencyInjectionEventSource.Log.ServiceRealizationFailed);
var serviceRealizationFailedEvent = _listener.EventData.Single(e => e.EventName == eventName);
var serviceRealizationFailedEvent = Listener.EventDataFor(serviceProvider).Single(e => e.EventName == eventName);

Assert.Equal("System.Exception: Test error.", GetProperty<string>(serviceRealizationFailedEvent, "exceptionMessage"));
Assert.Equal(1234, GetProperty<int>(serviceRealizationFailedEvent, "serviceProviderHashCode"));
Assert.Equal(hashCode, GetProperty<int>(serviceRealizationFailedEvent, "serviceProviderHashCode"));
Assert.Equal(6, serviceRealizationFailedEvent.EventId);
}

Expand All @@ -266,7 +254,7 @@ public void EmitsServiceProviderBuilt()

using ServiceProvider provider = serviceCollection.BuildServiceProvider();

EventWrittenEventArgs serviceProviderBuiltEvent = _listener.EventData.Single(e => e.EventName == "ServiceProviderBuilt");
EventWrittenEventArgs serviceProviderBuiltEvent = Listener.EventDataFor(provider).Single(e => e.EventName == "ServiceProviderBuilt");
GetProperty<int>(serviceProviderBuiltEvent, "serviceProviderHashCode"); // assert hashcode exists as an int
Assert.Equal(4, GetProperty<int>(serviceProviderBuiltEvent, "singletonServices"));
Assert.Equal(2, GetProperty<int>(serviceProviderBuiltEvent, "scopedServices"));
Expand All @@ -275,7 +263,7 @@ public void EmitsServiceProviderBuilt()
Assert.Equal(1, GetProperty<int>(serviceProviderBuiltEvent, "openGenericsServices"));
Assert.Equal(7, serviceProviderBuiltEvent.EventId);

EventWrittenEventArgs serviceProviderDescriptorsEvent = _listener.EventData.Single(e => e.EventName == "ServiceProviderDescriptors");
EventWrittenEventArgs serviceProviderDescriptorsEvent = Listener.EventDataFor(provider).Single(e => e.EventName == "ServiceProviderDescriptors");
Assert.Equal(
string.Join(Environment.NewLine,
"{",
Expand Down Expand Up @@ -343,33 +331,77 @@ public void EmitsServiceProviderBuilt()
[Fact]
public void EmitsServiceProviderBuiltOnAttach()
{
_listener.DisableEvents(DependencyInjectionEventSource.Log);

ServiceCollection serviceCollection = new();
serviceCollection.AddSingleton(new FakeDisposeCallback());

using ServiceProvider provider = serviceCollection.BuildServiceProvider();

Assert.Empty(_listener.EventData);
using var listener = new TestEventListener();
listener.EnableEvents(DependencyInjectionEventSource.Log, EventLevel.Verbose);
try
{
EventWrittenEventArgs serviceProviderBuiltEvent = listener.EventDataFor(provider).Single(e => e.EventName == "ServiceProviderBuilt");
Assert.Equal(1, GetProperty<int>(serviceProviderBuiltEvent, "singletonServices"));

_listener.EnableEvents(DependencyInjectionEventSource.Log, EventLevel.Verbose);
EventWrittenEventArgs serviceProviderDescriptorsEvent = listener.EventDataFor(provider).Single(e => e.EventName == "ServiceProviderDescriptors");
Assert.NotNull(JObject.Parse(GetProperty<string>(serviceProviderDescriptorsEvent, "descriptors")));
}
finally
{
listener.DisableEvents(DependencyInjectionEventSource.Log);
}
}

EventWrittenEventArgs serviceProviderBuiltEvent = _listener.EventData.Single(e => e.EventName == "ServiceProviderBuilt");
Assert.Equal(1, GetProperty<int>(serviceProviderBuiltEvent, "singletonServices"));
private static bool TryGetProperty<T>(EventWrittenEventArgs data, string propName, out T result)
{
if (data.PayloadNames is { } names &&
data.Payload is { } payload &&
names.IndexOf(propName) is var index and >= 0 &&
payload[index] is T value)
{
result = value;
return true;
}

EventWrittenEventArgs serviceProviderDescriptorsEvent = _listener.EventData.Single(e => e.EventName == "ServiceProviderDescriptors");
Assert.NotNull(JObject.Parse(GetProperty<string>(serviceProviderDescriptorsEvent, "descriptors")));
result = default;
return false;
}

private T GetProperty<T>(EventWrittenEventArgs data, string propName)
=> (T)data.Payload[data.PayloadNames.IndexOf(propName)];
private static T GetProperty<T>(EventWrittenEventArgs data, string propName) =>
TryGetProperty(data, propName, out T result)
? result
: throw new ArgumentException($"No property {propName} with type {typeof(T)} found.");

public class TestEventListenerFixture : IDisposable
{
internal TestEventListener Listener { get; }

public TestEventListenerFixture()
{
Listener = new();
Listener.EnableEvents(DependencyInjectionEventSource.Log, EventLevel.Verbose);
}

public void Dispose()
{
Listener.DisableEvents(DependencyInjectionEventSource.Log);
Listener.Dispose();
}
}

private class TestEventListener : EventListener
internal class TestEventListener : EventListener
{
private volatile bool _disposed;
private ConcurrentQueue<EventWrittenEventArgs> _events = new ConcurrentQueue<EventWrittenEventArgs>();
private readonly ConcurrentQueue<EventWrittenEventArgs> _events = new ConcurrentQueue<EventWrittenEventArgs>();

public IEnumerable<EventWrittenEventArgs> EventDataFor(IServiceProvider serviceProvider)
{
int hashCode = serviceProvider.GetHashCode();

public IEnumerable<EventWrittenEventArgs> EventData => _events;
return _events.Where(e =>
e.PayloadNames?.IndexOf("serviceProviderHashCode") is { } index and >= 0 &&
e.Payload?[index] as int? == hashCode);
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
Expand All @@ -385,10 +417,5 @@ public override void Dispose()
base.Dispose();
}
}

public void Dispose()
{
_listener.Dispose();
}
}
}

0 comments on commit 46ee68e

Please sign in to comment.