Skip to content

Commit cc814d2

Browse files
authored
[CI Visibility] Test session logical names (DataDog#6050)
## Summary of changes This PR implements a new CI Visibility feature called Test session logical names. In this feature a new DD_TEST_SESSION_NAME environment variable is introduced for customer to set their custom test session name. This name is sent to the backend inside the `metadata` object in the CI Visibility protocol. PR to change the intake json: DataDog/dd-go#150765 ## Reason for change New feature: https://docs.google.com/document/d/1XPBDDIMUxzfkko66vXIEjoi4xbCamTz-Bv6ChSR8jys/edit ## Implementation details - A new ConfigurationKey.CIVisibility environment variable - A new CIVisibility.Settings property - Changes to the `CIEventMessagePackFormatter` to include this new tag inside the metadata object. ## Test coverage Added a `metadata` validator in the EVP tests (CI Visibility protocol)
1 parent 53e09d3 commit cc814d2

File tree

12 files changed

+144
-6
lines changed

12 files changed

+144
-6
lines changed

Diff for: shared/src/Datadog.Trace.ClrProfiler.Native/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ add_compile_options(-Wno-invalid-noreturn -Wno-macro-redefined)
122122
if (UNIVERSAL)
123123
add_compile_options(-stdlib=libc++ -DUNIVERSAL)
124124
elseif (ISMACOS)
125-
add_compile_options(-stdlib=libc++ -DMACOS -Wno-pragma-pack -Wno-deprecated-declarations -fvisibility=hidden)
125+
add_compile_options(-stdlib=libc++ -DMACOS -Wno-pragma-pack -Wno-deprecated-declarations -fvisibility=hidden -Wno-deprecated-this-capture)
126126
else()
127127
add_compile_options(-stdlib=libstdc++)
128128
endif()

Diff for: tracer/build/_build/Build.Steps.cs

+8
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,14 @@ string NormalizedPath(AbsolutePath ap)
26252625
knownPatterns.Add(new(".*SingleStepGuardRails::ShouldForceInstrumentationOverride: Found incompatible runtime .NET Core 3.0 or lower", RegexOptions.Compiled));
26262626
knownPatterns.Add(new(".*SingleStepGuardRails::ShouldForceInstrumentationOverride: Found incompatible runtime .NET 6.0.12 and earlier have known crashing bugs", RegexOptions.Compiled));
26272627

2628+
// CI Visibility known errors
2629+
knownPatterns.Add(new(@".*The Git repository couldn't be automatically extracted.*", RegexOptions.Compiled));
2630+
knownPatterns.Add(new(@".*DD_GIT_REPOSITORY_URL is set with.*", RegexOptions.Compiled));
2631+
knownPatterns.Add(new(@".*The Git commit sha couldn't be automatically extracted.*", RegexOptions.Compiled));
2632+
knownPatterns.Add(new(@".*DD_GIT_COMMIT_SHA must be a full-length git SHA.*", RegexOptions.Compiled));
2633+
knownPatterns.Add(new(@".*Timeout occurred when flushing spans.*", RegexOptions.Compiled));
2634+
knownPatterns.Add(new(@".*ITR: .*", RegexOptions.Compiled));
2635+
26282636
CheckLogsForErrors(knownPatterns, allFilesMustExist: true, minLogLevel: LogLevel.Warning);
26292637
});
26302638

Diff for: tracer/src/Datadog.Trace/Ci/Agent/MessagePack/CIEventMessagePackFormatter.cs

+49-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Datadog.Trace.Ci.Agent.MessagePack
1616
internal class CIEventMessagePackFormatter : EventMessagePackFormatter<CIVisibilityProtocolPayload>
1717
{
1818
private readonly byte[] _metadataBytes = StringEncoding.UTF8.GetBytes("metadata");
19+
1920
private readonly byte[] _asteriskBytes = StringEncoding.UTF8.GetBytes("*");
2021
private readonly byte[] _runtimeIdBytes = StringEncoding.UTF8.GetBytes(Trace.Tags.RuntimeId);
2122
private readonly byte[] _runtimeIdValueBytes = StringEncoding.UTF8.GetBytes(Tracer.RuntimeId);
@@ -25,6 +26,14 @@ internal class CIEventMessagePackFormatter : EventMessagePackFormatter<CIVisibil
2526
private readonly byte[] _libraryVersionValueBytes = StringEncoding.UTF8.GetBytes(TracerConstants.AssemblyVersion);
2627
private readonly byte[] _environmentBytes = StringEncoding.UTF8.GetBytes("env");
2728
private readonly byte[]? _environmentValueBytes;
29+
30+
private readonly byte[] _testBytes = StringEncoding.UTF8.GetBytes(SpanTypes.Test);
31+
private readonly byte[] _testSuiteEndBytes = StringEncoding.UTF8.GetBytes(SpanTypes.TestSuite);
32+
private readonly byte[] _testModuleEndBytes = StringEncoding.UTF8.GetBytes(SpanTypes.TestModule);
33+
private readonly byte[] _testSessionEndBytes = StringEncoding.UTF8.GetBytes(SpanTypes.TestSession);
34+
private readonly byte[] _testSessionNameBytes = StringEncoding.UTF8.GetBytes("test_session.name");
35+
private readonly byte[]? _testSessionNameValueBytes;
36+
2837
private readonly byte[] _eventsBytes = StringEncoding.UTF8.GetBytes("events");
2938

3039
private readonly ArraySegment<byte> _envelopBytes;
@@ -36,6 +45,11 @@ public CIEventMessagePackFormatter(TracerSettings tracerSettings)
3645
_environmentValueBytes = StringEncoding.UTF8.GetBytes(tracerSettings.EnvironmentInternal);
3746
}
3847

48+
if (!string.IsNullOrWhiteSpace(Ci.CIVisibility.Settings.TestSessionName))
49+
{
50+
_testSessionNameValueBytes = StringEncoding.UTF8.GetBytes(Ci.CIVisibility.Settings.TestSessionName);
51+
}
52+
3953
_envelopBytes = GetEnvelopeArraySegment();
4054
}
4155

@@ -73,7 +87,8 @@ public override int Serialize(ref byte[] bytes, int offset, CIVisibilityProtocol
7387
private ArraySegment<byte> GetEnvelopeArraySegment()
7488
{
7589
var offset = 0;
76-
var bytes = new byte[512];
90+
// Default size o the array, in case we don't have enough space MessagePackBinary will resize it
91+
var bytes = new byte[2048];
7792

7893
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 3);
7994

@@ -90,7 +105,8 @@ private ArraySegment<byte> GetEnvelopeArraySegment()
90105
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _metadataBytes);
91106

92107
// Value
93-
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 1);
108+
var metadataValuesCount = _testSessionNameValueBytes is not null ? 5 : 1;
109+
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, metadataValuesCount);
94110

95111
// -> * : {}
96112

@@ -121,6 +137,37 @@ private ArraySegment<byte> GetEnvelopeArraySegment()
121137
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _environmentValueBytes);
122138
}
123139

140+
if (_testSessionNameValueBytes is not null)
141+
{
142+
// -> test : {}
143+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testBytes);
144+
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 1);
145+
// -> test_session.name : "value"
146+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameBytes);
147+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameValueBytes);
148+
149+
// -> test_suite_end : {}
150+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSuiteEndBytes);
151+
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 1);
152+
// -> test_session.name : "value"
153+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameBytes);
154+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameValueBytes);
155+
156+
// -> test_module_end : {}
157+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testModuleEndBytes);
158+
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 1);
159+
// -> test_session.name : "value"
160+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameBytes);
161+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameValueBytes);
162+
163+
// -> test_session_end : {}
164+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionEndBytes);
165+
offset += MessagePackBinary.WriteMapHeader(ref bytes, offset, 1);
166+
// -> test_session.name : "value"
167+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameBytes);
168+
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _testSessionNameValueBytes);
169+
}
170+
124171
// # Events
125172

126173
// Key

Diff for: tracer/src/Datadog.Trace/Ci/CIVisibility.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ public static void Initialize()
127127
LifetimeManager.Instance.AddAsyncShutdownTask(ShutdownAsync);
128128

129129
var tracerSettings = settings.TracerSettings;
130+
Log.Debug("Setting up the test session name to: {TestSessionName}", settings.TestSessionName);
130131

131132
// Set the service name if empty
132-
Log.Debug("Setting up the service name");
133133
if (string.IsNullOrEmpty(tracerSettings.ServiceNameInternal))
134134
{
135135
// Extract repository name from the git url and use it as a default service name.
@@ -138,6 +138,7 @@ public static void Initialize()
138138

139139
// Normalize the service name
140140
tracerSettings.ServiceNameInternal = NormalizerTraceProcessor.NormalizeService(tracerSettings.ServiceNameInternal);
141+
Log.Debug("Setting up the service name to: {ServiceName}", tracerSettings.ServiceNameInternal);
141142

142143
// Initialize Tracer
143144
Log.Information("Initialize Test Tracer instance");
@@ -195,9 +196,9 @@ internal static void InitializeFromRunner(CIVisibilitySettings settings, IDiscov
195196
LifetimeManager.Instance.AddAsyncShutdownTask(ShutdownAsync);
196197

197198
var tracerSettings = settings.TracerSettings;
199+
Log.Debug("Setting up the test session name to: {TestSessionName}", settings.TestSessionName);
198200

199201
// Set the service name if empty
200-
Log.Debug("Setting up the service name");
201202
if (string.IsNullOrEmpty(tracerSettings.ServiceNameInternal))
202203
{
203204
// Extract repository name from the git url and use it as a default service name.
@@ -206,6 +207,7 @@ internal static void InitializeFromRunner(CIVisibilitySettings settings, IDiscov
206207

207208
// Normalize the service name
208209
tracerSettings.ServiceNameInternal = NormalizerTraceProcessor.NormalizeService(tracerSettings.ServiceNameInternal);
210+
Log.Debug("Setting up the service name to: {ServiceName}", tracerSettings.ServiceNameInternal);
209211

210212
// Initialize Tracer
211213
Log.Information("Initialize Test Tracer instance");

Diff for: tracer/src/Datadog.Trace/Ci/Configuration/CIVisibilitySettings.cs

+42
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66

77
using System;
88
using System.Collections.Specialized;
9+
using System.Linq;
10+
using System.Runtime.CompilerServices;
911
using System.Threading;
12+
using Datadog.Trace.Ci.Tags;
1013
using Datadog.Trace.Configuration;
1114
using Datadog.Trace.Configuration.ConfigurationSources.Telemetry;
1215
using Datadog.Trace.Configuration.Telemetry;
16+
using Datadog.Trace.ExtensionMethods;
1317
using Datadog.Trace.Telemetry;
1418
using Datadog.Trace.Util;
1519

@@ -63,6 +67,39 @@ public CIVisibilitySettings(IConfigurationSource source, IConfigurationTelemetry
6367

6468
// RUM flush milliseconds
6569
RumFlushWaitMillis = config.WithKeys(ConfigurationKeys.CIVisibility.RumFlushWaitMillis).AsInt32(500);
70+
71+
// Test session name
72+
TestSessionName = config.WithKeys(ConfigurationKeys.CIVisibility.TestSessionName).AsString(
73+
getDefaultValue: () =>
74+
{
75+
// We try to get the command from the active test session or test module
76+
var command = TestSession.ActiveTestSessions.FirstOrDefault()?.Command ??
77+
TestModule.ActiveTestModules.FirstOrDefault()?.Tags.Command ??
78+
string.Empty;
79+
80+
if (string.IsNullOrEmpty(command))
81+
{
82+
// If there's no active test session or test module we try to get the command from the environment (sent by dd-trace session)
83+
var environmentVariables = EnvironmentHelpers.GetEnvironmentVariables();
84+
if (environmentVariables.TryGetValue<string>(TestSuiteVisibilityTags.TestSessionCommandEnvironmentVariable, out var testSessionCommand) && !string.IsNullOrEmpty(testSessionCommand))
85+
{
86+
command = testSessionCommand;
87+
}
88+
else
89+
{
90+
// As last resort we use the command line that started this process
91+
command = Environment.CommandLine;
92+
}
93+
}
94+
95+
if (CiEnvironment.CIEnvironmentValues.Instance.JobName is { } jobName)
96+
{
97+
return $"{jobName}-{command}";
98+
}
99+
100+
return command;
101+
},
102+
validator: null);
66103
}
67104

68105
/// <summary>
@@ -170,6 +207,11 @@ public CIVisibilitySettings(IConfigurationSource source, IConfigurationTelemetry
170207
/// </summary>
171208
public int RumFlushWaitMillis { get; }
172209

210+
/// <summary>
211+
/// Gets the test session name
212+
/// </summary>
213+
public string TestSessionName { get; }
214+
173215
/// <summary>
174216
/// Gets the tracer settings
175217
/// </summary>

Diff for: tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs

+5
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ public static class CIVisibility
548548
/// Configuration key for set the rum flushing wait in milliseconds
549549
/// </summary>
550550
public const string RumFlushWaitMillis = "DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS";
551+
552+
/// <summary>
553+
/// Configuration key for set the test session name
554+
/// </summary>
555+
public const string TestSessionName = "DD_TEST_SESSION_NAME";
551556
}
552557

553558
/// <summary>

Diff for: tracer/src/Datadog.Tracer.Native/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ add_compile_options(-std=c++20 -fPIC -fms-extensions -fvisibility=hidden -g)
119119
add_compile_options(-DPAL_STDCPP_COMPAT -DPLATFORM_UNIX -DUNICODE)
120120
add_compile_options(-Wno-invalid-noreturn -Wno-macro-redefined)
121121
if (ISMACOS)
122-
add_compile_options(-stdlib=libc++ -DMACOS -Wno-pragma-pack -Wno-deprecated-declarations)
122+
add_compile_options(-stdlib=libc++ -DMACOS -Wno-pragma-pack -Wno-deprecated-declarations -Wno-deprecated-this-capture)
123123
elseif(ISLINUX)
124124
add_compile_options(-stdlib=libstdc++ -DLINUX -Wno-pragmas)
125125
endif()

Diff for: tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/MsTestV2EvpTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public async Task SubmitTraces(string packageVersion, string evpVersionToRemove,
127127
e.Value.Headers["Content-Encoding"].Should().Be(expectedGzip ? "gzip" : null);
128128

129129
var payload = JsonConvert.DeserializeObject<MockCIVisibilityProtocol>(e.Value.BodyInJson);
130+
ValidateMetadata(payload.Metadata, sessionCommand);
130131
if (payload.Events?.Length > 0)
131132
{
132133
foreach (var @event in payload.Events)

Diff for: tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/NUnitEvpTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public async Task SubmitTraces(string packageVersion, string evpVersionToRemove,
148148
e.Value.Headers["Content-Encoding"].Should().Be(expectedGzip ? "gzip" : null);
149149

150150
var payload = JsonConvert.DeserializeObject<MockCIVisibilityProtocol>(e.Value.BodyInJson);
151+
ValidateMetadata(payload.Metadata, sessionCommand);
151152
if (payload.Events?.Length > 0)
152153
{
153154
foreach (var @event in payload.Events)

Diff for: tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/TestingFrameworkEvpTest.cs

+29
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using System;
88
using System.Collections.Generic;
9+
using System.Linq.Expressions;
910
using Datadog.Trace.Ci;
1011
using Datadog.Trace.Ci.CiEnvironment;
1112
using Datadog.Trace.Ci.Tags;
@@ -300,4 +301,32 @@ protected void SetCIEnvironmentValues()
300301

301302
CIValues = CIEnvironmentValues.Create(ciDictionaryValues);
302303
}
304+
305+
protected void ValidateMetadata(Dictionary<string, Dictionary<string, object>>? metadata, string testCommand)
306+
{
307+
metadata.Should().NotBeNull();
308+
metadata ??= new Dictionary<string, Dictionary<string, object>>();
309+
metadata.Should().ContainKey("*");
310+
metadata["*"].Should().ContainKeys(Trace.Tags.RuntimeId, "language", CommonTags.LibraryVersion, "env");
311+
312+
var jobName = ((CIEnvironmentValues?)CIValues)?.JobName;
313+
var valueKey = string.IsNullOrEmpty(jobName) ? testCommand : $"{jobName}-{testCommand}";
314+
315+
Expression<Func<KeyValuePair<string, object>, bool>> selector =
316+
kv =>
317+
kv.Key == "test_session.name" &&
318+
kv.Value.ToString() == valueKey;
319+
320+
metadata.Should().ContainKey(SpanTypes.Test);
321+
metadata[SpanTypes.Test].Should().Contain(selector);
322+
323+
metadata.Should().ContainKey(SpanTypes.TestSuite);
324+
metadata[SpanTypes.TestSuite].Should().Contain(selector);
325+
326+
metadata.Should().ContainKey(SpanTypes.TestModule);
327+
metadata[SpanTypes.TestModule].Should().Contain(selector);
328+
329+
metadata.Should().ContainKey(SpanTypes.TestSession);
330+
metadata[SpanTypes.TestSession].Should().Contain(selector);
331+
}
303332
}

Diff for: tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/XUnitEvpTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.Globalization;
99
using System.Linq;
10+
using System.Linq.Expressions;
1011
using System.Runtime.CompilerServices;
1112
using System.Threading.Tasks;
1213
using Datadog.Trace.Ci;
@@ -138,6 +139,7 @@ public virtual async Task SubmitTraces(string packageVersion, string evpVersionT
138139
e.Value.Headers["Content-Encoding"].Should().Be(expectedGzip ? "gzip" : null);
139140

140141
var payload = JsonConvert.DeserializeObject<MockCIVisibilityProtocol>(e.Value.BodyInJson);
142+
ValidateMetadata(payload.Metadata, sessionCommand);
141143
if (payload.Events?.Length > 0)
142144
{
143145
foreach (var @event in payload.Events)

Diff for: tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json

+1
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@
372372
"DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED": "ci_visibility_early_flake_detection_enabled",
373373
"DD_CIVISIBILITY_CODE_COVERAGE_COLLECTORPATH": "ci_visibility_code_coverage_collectorpath",
374374
"DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS": "ci_visibility_rum_flush_wait_millis",
375+
"DD_TEST_SESSION_NAME": "test_session_name",
375376
"DD_PROXY_HTTPS": "proxy_https",
376377
"DD_PROXY_NO_PROXY": "proxy_no_proxy",
377378
"DD_TRACE_DEBUG_LOOKUP_MDTOKEN": "trace_lookup_mdtoken_enabled",

0 commit comments

Comments
 (0)