| File: TestEventListener.cs | Web Access |
| Project: src\test\Libraries\Microsoft.Extensions.Caching.Hybrid.Tests\Microsoft.Extensions.Caching.Hybrid.Tests.csproj (Microsoft.Extensions.Caching.Hybrid.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.Tracing; using System.Globalization; using Microsoft.Extensions.Caching.Hybrid.Internal; namespace Microsoft.Extensions.Caching.Hybrid.Tests; public sealed class TestEventListener : EventListener { // captures both event and counter data // this is used as a class fixture from HybridCacheEventSourceTests, because there // seems to be some unpredictable behaviours if multiple event sources/listeners are // casually created etc private const double EventCounterIntervalSec = 0.25; private readonly List<(int id, string name, EventLevel level)> _events = []; private readonly Dictionary<string, (string? displayName, double value)> _counters = []; private object SyncLock => _events; internal HybridCacheEventSource Source { get; } = new(); public TestEventListener Reset(bool resetCounters = true) { lock (SyncLock) { _events.Clear(); _counters.Clear(); if (resetCounters) { Source.ResetCounters(); } } Assert.True(Source.IsEnabled(), "should report as enabled"); return this; } protected override void OnEventSourceCreated(EventSource eventSource) { if (ReferenceEquals(eventSource, Source)) { var args = new Dictionary<string, string?> { ["EventCounterIntervalSec"] = EventCounterIntervalSec.ToString("G", CultureInfo.InvariantCulture), }; EnableEvents(Source, EventLevel.LogAlways, EventKeywords.All, args); } base.OnEventSourceCreated(eventSource); } protected override void OnEventWritten(EventWrittenEventArgs eventData) { if (ReferenceEquals(eventData.EventSource, Source)) { // capture counters/events lock (SyncLock) { if (eventData.EventName == "EventCounters" && eventData.Payload is { Count: > 0 }) { foreach (var payload in eventData.Payload) { if (payload is IDictionary<string, object> map) { string? name = null; string? displayName = null; double? value = null; bool isIncrement = false; foreach (var pair in map) { switch (pair.Key) { case "Name" when pair.Value is string: name = (string)pair.Value; break; case "DisplayName" when pair.Value is string s: displayName = s; break; case "Mean": isIncrement = false; value = Convert.ToDouble(pair.Value); break; case "Increment": isIncrement = true; value = Convert.ToDouble(pair.Value); break; } } if (name is not null && value is not null) { if (isIncrement && _counters.TryGetValue(name, out var oldPair)) { value += oldPair.value; // treat as delta from old } Debug.WriteLine($"{name}={value}"); _counters[name] = (displayName, value.Value); } } } } else { _events.Add((eventData.EventId, eventData.EventName ?? "", eventData.Level)); } } } base.OnEventWritten(eventData); } public (int id, string name, EventLevel level) SingleEvent() { (int id, string name, EventLevel level) evt; lock (SyncLock) { evt = Assert.Single(_events); } return evt; } public void AssertSingleEvent(int id, string name, EventLevel level) { var evt = SingleEvent(); Assert.Equal(name, evt.name); Assert.Equal(id, evt.id); Assert.Equal(level, evt.level); } public double AssertCounter(string name, string displayName) { lock (SyncLock) { Assert.True(_counters.TryGetValue(name, out var pair), $"counter not found: {name}"); Assert.Equal(displayName, pair.displayName); _counters.Remove(name); // count as validated return pair.value; } } public void AssertCounter(string name, string displayName, double expected) { var actual = AssertCounter(name, displayName); if (!Equals(expected, actual)) { Assert.Fail($"{name}: expected {expected}, actual {actual}"); } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Bug", "S1244:Floating point numbers should not be tested for equality", Justification = "Test expects exact zero")] public void AssertRemainingCountersZero() { lock (SyncLock) { foreach (var pair in _counters) { if (pair.Value.value != 0) { Assert.Fail($"{pair.Key}: expected 0, actual {pair.Value.value}"); } } } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Clarity and usability")] public async Task<int> TryAwaitCountersAsync() { // allow 2 cycles because if we only allow 1, we run the risk of a // snapshot being captured mid-cycle when we were setting up the test // (ok, that's an unlikely race condition, but!) await Task.Delay(TimeSpan.FromSeconds(EventCounterIntervalSec * 2)); lock (SyncLock) { return _counters.Count; } } }