From 2aa42bb7ab463cb73b802483656cd899d7f6e6bf Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 21:14:58 +0100 Subject: [PATCH] add tests --- .../ConfigurationKeys.Debugger.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 37 ++++-- tracer/src/Datadog.Trace/Tracer.cs | 2 +- .../Debugger/DebuggerSettingsTests.cs | 56 +++++++++ .../Debugger/SpanCodeOriginTests.cs | 118 ++++++++++++++++++ 5 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs index 4124f5f0e2ca..31d59bdbf6c2 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs @@ -159,7 +159,7 @@ internal static class Debugger /// Default value is 8. /// /// - public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_MAX_USER_FRAMES"; + public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES"; } } } diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 7f50fd09281f..0e6362bcb30c 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -7,33 +7,52 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading; using Datadog.Trace.Debugger.Symbols; using Datadog.Trace.Logging; using Datadog.Trace.VendoredMicrosoftCode.System.Buffers; +using Datadog.Trace.VendoredMicrosoftCode.System.Collections.Immutable; namespace Datadog.Trace.Debugger.SpanCodeOrigin { internal class SpanCodeOriginManager { private const string CodeOriginTag = "_dd.code_origin"; + private const string FramesPrefix = "frames"; - private static readonly DebuggerSettings Settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource(); + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager)); + private static object _globalInstanceLock = new(); + + private static bool _globalInstanceInitialized; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + private static SpanCodeOriginManager _instance; +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + private readonly DebuggerSettings _settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource(); + + internal static SpanCodeOriginManager Instance => + LazyInitializer.EnsureInitialized( + ref _instance, + ref _globalInstanceInitialized, + ref _globalInstanceLock); + [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SetCodeOrigin(Span? span) + internal void SetCodeOrigin(Span? span) { - if (span == null || !Settings.CodeOriginForSpansEnabled) + if (span == null || !this._settings.CodeOriginForSpansEnabled) { return; } - AddExitSpanTag(span); + Instance.AddExitSpanTag(span); } - private static void AddExitSpanTag(Span span) + private void AddExitSpanTag(Span span) { - var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); + var frames = ArrayPool.Shared.Rent(this._settings.CodeOriginMaxUserFrames); try { var framesLength = PopulateUserFrames(frames); @@ -94,7 +113,7 @@ private static void AddExitSpanTag(Span span) } } - private static int PopulateUserFrames(FrameInfo[] frames) + private int PopulateUserFrames(FrameInfo[] frames) { var stackTrace = new StackTrace(true); var stackFrames = stackTrace.GetFrames(); @@ -106,7 +125,7 @@ private static int PopulateUserFrames(FrameInfo[] frames) } var count = 0; - for (var walkIndex = 0; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) + for (var walkIndex = 0; walkIndex < stackFrames.Length && count < this._settings.CodeOriginMaxUserFrames; walkIndex++) { var frame = stackFrames[walkIndex]; @@ -116,7 +135,7 @@ private static int PopulateUserFrames(FrameInfo[] frames) continue; } - if (AssemblyFilter.ShouldSkipAssembly(assembly, LiveDebugger.Instance.Settings.ThirdPartyDetectionExcludes, LiveDebugger.Instance.Settings.ThirdPartyDetectionIncludes)) + if (AssemblyFilter.ShouldSkipAssembly(assembly, _settings.ThirdPartyDetectionExcludes, _settings.ThirdPartyDetectionIncludes)) { // use cache when this will be merged: https://github.com/DataDog/dd-trace-dotnet/pull/6093 continue; diff --git a/tracer/src/Datadog.Trace/Tracer.cs b/tracer/src/Datadog.Trace/Tracer.cs index 9234e3cb3cbd..b6cff2e67408 100644 --- a/tracer/src/Datadog.Trace/Tracer.cs +++ b/tracer/src/Datadog.Trace/Tracer.cs @@ -550,7 +550,7 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa // write them directly to the . TracerManager.GitMetadataTagsProvider.TryExtractGitMetadata(out _); - SpanCodeOriginManager.SetCodeOrigin(span); + SpanCodeOriginManager.Instance.SetCodeOrigin(span); return span; } diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs index e934d8f62297..a84cf7805724 100644 --- a/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs @@ -141,5 +141,61 @@ public void InvalidUploadFlushInterval_DefaultUsed(string value) settings.UploadFlushIntervalMilliseconds.Should().Be(0); } + + [Theory] + [InlineData("")] + [InlineData("False")] + [InlineData("false")] + [InlineData("0")] + [InlineData("2")] + [InlineData(null)] + public void CodeOriginEnabled_False(string value) + { + var settings = new DebuggerSettings( + new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }), + NullConfigurationTelemetry.Instance); + + settings.CodeOriginForSpansEnabled.Should().BeFalse(); + } + + [Theory] + [InlineData("True")] + [InlineData("true")] + [InlineData("1")] + public void CodeOriginEnabled_True(string value) + { + var settings = new DebuggerSettings( + new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }), + NullConfigurationTelemetry.Instance); + + settings.CodeOriginForSpansEnabled.Should().BeTrue(); + } + + [Theory] + [InlineData("8")] + [InlineData("1")] + [InlineData("1000")] + public void CodeOriginMaxUserFrames(string value) + { + var settings = new DebuggerSettings( + new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }), + NullConfigurationTelemetry.Instance); + + settings.CodeOriginMaxUserFrames.Should().Be(int.Parse(value)); + } + + [Theory] + [InlineData("-1")] + [InlineData("0")] + [InlineData("")] + [InlineData(null)] + public void InvalidCodeOriginMaxUserFrames_DefaultUsed(string value) + { + var settings = new DebuggerSettings( + new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }), + NullConfigurationTelemetry.Instance); + + settings.CodeOriginMaxUserFrames.Should().Be(8); + } } } diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs new file mode 100644 index 000000000000..4c59ebf6055b --- /dev/null +++ b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs @@ -0,0 +1,118 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Datadog.Trace.Configuration; +using Datadog.Trace.Configuration.Telemetry; +using Datadog.Trace.Debugger; +using Datadog.Trace.Debugger.SpanCodeOrigin; +using Datadog.Trace.VendoredMicrosoftCode.System.Collections.Immutable; +using Xunit; + +namespace Datadog.Trace.Tests.Debugger +{ + public class SpanCodeOriginTests + { + private const string CodeOriginTag = "_dd.code_origin"; + + [Fact] + public void SetCodeOrigin_WhenSpanIsNull_DoesNotThrow() + { + // Should not throw + SpanCodeOriginManager.Instance.SetCodeOrigin(null); + } + + [Fact] + public void SetCodeOrigin_WhenDisabled_DoesNotSetTags() + { + // Arrange + CreateCodeOriginManager(); + + var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); + + // Act + SpanCodeOriginManager.Instance.SetCodeOrigin(span); + + // Assert + Assert.Null(span.Tags.GetTag(CodeOriginTag + ".type")); + } + + [Fact] + public void SetCodeOrigin_WhenEnabled_SetsCorrectTags() + { + // Arrange + CreateCodeOriginManager(true); + + var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); + + // Act + TestMethod(span); + + // Assert + Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.type")); + Assert.Equal("exit", span.Tags.GetTag($"{CodeOriginTag}.type")); + + Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.frames.0.method")); + Assert.Equal(nameof(TestMethod), span.Tags.GetTag($"{CodeOriginTag}.frames.0.method")); + Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.frames.0.type")); + Assert.Contains(nameof(SpanCodeOriginTests), span.Tags.GetTag($"{CodeOriginTag}.frames.0.type")); + } + + [Fact] + public void SetCodeOrigin_WithMaxFramesLimit_RespectsLimit() + { + // Arrange + CreateCodeOriginManager(true, 2); + + var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); + + // Act + DeepTestMethod1(span); + + // Assert + var tags = ((List>)(typeof(Datadog.Trace.Tagging.TagsList).GetField("_tags", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(span.Tags))).Select(i => i.Key).ToList(); + Assert.Contains(tags, s => s.StartsWith($"{CodeOriginTag}.frames.0")); + Assert.Contains(tags, s => s.StartsWith($"{CodeOriginTag}.frames.1")); + Assert.DoesNotContain(tags, s => s.StartsWith($"{CodeOriginTag}.frames.2")); + } + + private static void CreateCodeOriginManager(bool isEnable = false, int numberOfFrames = 8, string excludeFromFilter = "Datadog.Trace.Tests") + { + var overrideSettings = DebuggerSettings.FromSource( + new NameValueConfigurationSource(new() + { + { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, isEnable.ToString() }, + { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, numberOfFrames.ToString() }, + { ConfigurationKeys.Debugger.ThirdPartyDetectionExcludes, excludeFromFilter } + }), + NullConfigurationTelemetry.Instance); + var instance = SpanCodeOriginManager.Instance; + instance.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(instance, overrideSettings); + } + + private void TestMethod(Span span) + { + SpanCodeOriginManager.Instance.SetCodeOrigin(span); + } + + private void DeepTestMethod1(Span span) + { + DeepTestMethod2(span); + } + + private void DeepTestMethod2(Span span) + { + DeepTestMethod3(span); + } + + private void DeepTestMethod3(Span span) + { + SpanCodeOriginManager.Instance.SetCodeOrigin(span); + } + } +}