| File: Tracing\EventSourceValidator.cs | Web Access |
| Project: src\aspnetcore\src\Testing\src\Microsoft.AspNetCore.InternalTesting.csproj (Microsoft.AspNetCore.InternalTesting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics.Tracing; using System.Reflection; using Xunit; namespace Microsoft.AspNetCore.InternalTesting.Tracing; /// <summary> /// Validates that <see cref="EventSource"/>-derived classes have consistent /// <see cref="EventAttribute.EventId"/> values and <c>WriteEvent</c> call arguments. /// This catches drift caused by bad merge resolution or missed updates that would /// otherwise surface only as runtime errors. /// </summary> public static class EventSourceValidator { /// <summary> /// Validates all <c>[Event]</c>-attributed methods on <typeparamref name="T"/>. /// </summary> /// <typeparam name="T">A type that derives from <see cref="EventSource"/>.</typeparam> public static void ValidateEventSourceIds<T>() where T : EventSource => ValidateEventSourceIds(typeof(T)); /// <summary> /// Validates all <c>[Event]</c>-attributed methods on the given <see cref="EventSource"/>-derived type. /// <para> /// Uses <see cref="EventSource.GenerateManifest(Type, string, EventManifestOptions)"/> with /// <see cref="EventManifestOptions.Strict"/> to perform IL-level validation that the integer /// argument passed to each <c>WriteEvent</c> call matches the <c>[Event(id)]</c> attribute /// on the calling method. This is the same validation the .NET runtime itself uses. /// </para> /// <para> /// Additionally checks for duplicate <see cref="EventAttribute.EventId"/> values across methods. /// </para> /// </summary> /// <param name="eventSourceType">A type that derives from <see cref="EventSource"/>.</param> public static void ValidateEventSourceIds(Type eventSourceType) { if (eventSourceType is null) { throw new ArgumentNullException(nameof(eventSourceType)); } Assert.True( typeof(EventSource).IsAssignableFrom(eventSourceType), $"Type '{eventSourceType.FullName}' does not derive from EventSource."); var errors = new List<string>(); // Check for duplicate Event IDs across methods. var seenIds = new Dictionary<int, string>(); var methods = eventSourceType.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var method in methods) { var eventAttr = method.GetCustomAttribute<EventAttribute>(); if (eventAttr is null) { continue; } if (seenIds.TryGetValue(eventAttr.EventId, out var existingMethod)) { errors.Add( $"Duplicate EventId {eventAttr.EventId}: methods '{existingMethod}' and '{method.Name}' share the same ID."); } else { seenIds[eventAttr.EventId] = method.Name; } } // Use GenerateManifest with Strict mode to validate that each method's // WriteEvent(id, ...) call uses an ID that matches its [Event(id)] attribute. // Internally this uses GetHelperCallFirstArg to IL-inspect the method body // and extract the integer constant passed to WriteEvent — the same validation // the .NET runtime performs when constructing an EventSource. try { var manifest = EventSource.GenerateManifest( eventSourceType, assemblyPathToIncludeInManifest: "assemblyPathForValidation", flags: EventManifestOptions.Strict); if (manifest is null) { errors.Add("GenerateManifest returned null, indicating the type is not a valid EventSource."); } } catch (ArgumentException ex) { errors.Add(ex.Message); } if (errors.Count > 0) { Assert.Fail( $"EventSource '{eventSourceType.FullName}' has event ID validation error(s):" + Environment.NewLine + string.Join(Environment.NewLine, errors)); } } }