Skip to content

Commit 5e90ad0

Browse files
authored
[Test Optimization] Add codeowners and test source file tag at suite level (#7295)
## Summary of changes This PR adds the codeowners and test source file tag at test suite level. ## Reason for change https://datadoghq.atlassian.net/browse/SDTEST-2374 and https://datadoghq.atlassian.net/browse/SDTEST-285 ## Implementation details Added two new methods to handle Tag's set and append for both SourceFile and CodeOwners tags. ## Test coverage - New assertions on the EVP tests (MSTest, NUnit, XUnit) - A new test for `Test.SetStringOrArray` - A new test for `Test.SetCodeOwnersOnTags` ## Other details <!-- Fixes #{issue} --> <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. -->
1 parent e0b68bc commit 5e90ad0

File tree

15 files changed

+317
-155
lines changed

15 files changed

+317
-155
lines changed

tracer/src/Datadog.Trace/Ci/Tagging/TestSpanTags.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,12 @@ public TestSpanTags(TestSuiteSpanTags suiteTags, string testName)
6464
[Tag(TestTags.Parameters)]
6565
public string? Parameters { get; set; }
6666

67-
[Tag(TestTags.SourceFile)]
68-
public string? SourceFile { get; set; }
69-
7067
[Metric(TestTags.SourceStart)]
7168
public double? SourceStart { get; set; }
7269

7370
[Metric(TestTags.SourceEnd)]
7471
public double? SourceEnd { get; set; }
7572

76-
[Tag(TestTags.CodeOwners)]
77-
public string? CodeOwners { get; set; }
78-
7973
[Tag(TestTags.Traits)]
8074
public string? Traits { get; set; }
8175

tracer/src/Datadog.Trace/Ci/Tagging/TestSuiteSpanTags.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@ public TestSuiteSpanTags(TestModuleSpanTags moduleTags, string suiteName)
6060

6161
[Tag(TestTags.Suite)]
6262
public string? Suite { get; set; }
63+
64+
[Tag(TestTags.SourceFile)]
65+
public string? SourceFile { get; set; }
66+
67+
[Tag(TestTags.CodeOwners)]
68+
public string? CodeOwners { get; set; }
6369
}

tracer/src/Datadog.Trace/Ci/Test.cs

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,104 @@ public void SetTestMethodInfo(MethodInfo methodInfo)
211211
tags.SourceFile = ciValues.MakeRelativePathFromSourceRoot(methodSymbol.File, false);
212212
tags.SourceStart = startLine;
213213
tags.SourceEnd = methodSymbol.EndLine;
214-
215214
_testOptimization.ImpactedTestsDetectionFeature?.ImpactedTestsAnalyzer.Analyze(this);
216215

217-
if (ciValues.CodeOwners is { } codeOwners)
216+
SetStringOrArray(
217+
tags,
218+
Suite.Tags,
219+
static testTags => testTags.SourceFile,
220+
static suiteTags => suiteTags.SourceFile,
221+
static (suiteTags, value) => suiteTags.SourceFile = value);
222+
223+
if (ciValues.CodeOwners is { } codeOwners &&
224+
codeOwners.Match("/" + tags.SourceFile) is { } match)
225+
{
226+
SetCodeOwnersOnTags(tags, Suite.Tags, match);
227+
}
228+
}
229+
}
230+
231+
internal static void SetStringOrArray(TestSpanTags testTags, TestSuiteSpanTags suiteTags, Func<TestSpanTags, string?> getTestTag, Func<TestSuiteSpanTags, string?> getSuiteTag, Action<TestSuiteSpanTags, string?> setSuiteTag)
232+
{
233+
// If the value is not set, we set it to the current test tag
234+
// If it is set, we check if it is an array and add the current test tag to it
235+
// If it is not an array, we create a new array with both values
236+
// This is to support multiple values in a single tag
237+
var suiteTagValue = getSuiteTag(suiteTags);
238+
var testTagValue = getTestTag(testTags);
239+
if (StringUtil.IsNullOrEmpty(testTagValue))
240+
{
241+
return;
242+
}
243+
244+
if (StringUtil.IsNullOrEmpty(suiteTagValue))
245+
{
246+
setSuiteTag(suiteTags, testTagValue);
247+
}
248+
else if (!string.Equals(suiteTagValue, testTagValue, StringComparison.Ordinal))
249+
{
250+
if (suiteTagValue.StartsWith("[", StringComparison.Ordinal) &&
251+
suiteTagValue.EndsWith("]", StringComparison.Ordinal))
252+
{
253+
// If the source file is an array, we add the new source file to it
254+
List<string>? files;
255+
try
256+
{
257+
files = Vendors.Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(suiteTagValue);
258+
}
259+
catch (Exception ex)
260+
{
261+
TestOptimization.Instance.Log.Warning(ex, "Error deserializing '{SuiteTagValue}' environment variable.", suiteTagValue);
262+
files = [];
263+
}
264+
265+
if (files is not null && !files.Contains(testTagValue, StringComparer.Ordinal))
266+
{
267+
files.Add(testTagValue);
268+
setSuiteTag(suiteTags, Vendors.Newtonsoft.Json.JsonConvert.SerializeObject(files));
269+
}
270+
}
271+
else
218272
{
219-
var match = codeOwners.Match("/" + ciValues.MakeRelativePathFromSourceRoot(methodSymbol.File, false));
220-
if (match is not null)
273+
// If the source file is not an array, we create a new one with both values
274+
try
221275
{
222-
tags.CodeOwners = "[\"" + string.Join("\",\"", match) + "\"]";
276+
setSuiteTag(suiteTags, Vendors.Newtonsoft.Json.JsonConvert.SerializeObject(new List<string> { suiteTagValue, testTagValue }));
277+
}
278+
catch (Exception ex)
279+
{
280+
TestOptimization.Instance.Log.Warning(ex, "Error serializing '{SuiteTagValue}' environment variable.", suiteTagValue);
281+
setSuiteTag(suiteTags, $"[\"{suiteTagValue}\",\"{testTagValue}\"]");
223282
}
224283
}
225284
}
226285
}
227286

287+
internal static void SetCodeOwnersOnTags(TestSpanTags testTags, TestSuiteSpanTags suiteTags, IEnumerable<string> codeOwners)
288+
{
289+
testTags.CodeOwners = "[\"" + string.Join("\",\"", codeOwners) + "\"]";
290+
if (StringUtil.IsNullOrEmpty(suiteTags.CodeOwners))
291+
{
292+
suiteTags.CodeOwners = testTags.CodeOwners;
293+
}
294+
else if (!string.Equals(suiteTags.CodeOwners, testTags.CodeOwners, StringComparison.Ordinal))
295+
{
296+
List<string> suiteCodeOwners;
297+
try
298+
{
299+
suiteCodeOwners = Vendors.Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(suiteTags.CodeOwners) ?? [];
300+
}
301+
catch (Exception ex)
302+
{
303+
TestOptimization.Instance.Log.Warning(ex, "Error deserializing '{SuiteCodeOwners}' environment variable.", suiteTags.CodeOwners);
304+
suiteCodeOwners = [];
305+
}
306+
307+
suiteCodeOwners.AddRange(codeOwners);
308+
suiteTags.CodeOwners = "[\"" + string.Join("\",\"", suiteCodeOwners.Distinct(StringComparer.Ordinal)) + "\"]";
309+
}
310+
}
311+
228312
/// <summary>
229313
/// Set Test traits
230314
/// </summary>

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/TestSpanTags.g.cs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ partial class TestSpanTags
2222
private static ReadOnlySpan<byte> NameBytes => new byte[] { 169, 116, 101, 115, 116, 46, 110, 97, 109, 101 };
2323
// ParametersBytes = MessagePack.Serialize("test.parameters");
2424
private static ReadOnlySpan<byte> ParametersBytes => new byte[] { 175, 116, 101, 115, 116, 46, 112, 97, 114, 97, 109, 101, 116, 101, 114, 115 };
25-
// SourceFileBytes = MessagePack.Serialize("test.source.file");
26-
private static ReadOnlySpan<byte> SourceFileBytes => new byte[] { 176, 116, 101, 115, 116, 46, 115, 111, 117, 114, 99, 101, 46, 102, 105, 108, 101 };
27-
// CodeOwnersBytes = MessagePack.Serialize("test.codeowners");
28-
private static ReadOnlySpan<byte> CodeOwnersBytes => new byte[] { 175, 116, 101, 115, 116, 46, 99, 111, 100, 101, 111, 119, 110, 101, 114, 115 };
2925
// TraitsBytes = MessagePack.Serialize("test.traits");
3026
private static ReadOnlySpan<byte> TraitsBytes => new byte[] { 171, 116, 101, 115, 116, 46, 116, 114, 97, 105, 116, 115 };
3127
// SkipReasonBytes = MessagePack.Serialize("test.skip_reason");
@@ -83,8 +79,6 @@ partial class TestSpanTags
8379
{
8480
"test.name" => Name,
8581
"test.parameters" => Parameters,
86-
"test.source.file" => SourceFile,
87-
"test.codeowners" => CodeOwners,
8882
"test.traits" => Traits,
8983
"test.skip_reason" => SkipReason,
9084
"test.skipped_by_itr" => SkippedByIntelligentTestRunner,
@@ -124,12 +118,6 @@ public override void SetTag(string key, string value)
124118
case "test.parameters":
125119
Parameters = value;
126120
break;
127-
case "test.source.file":
128-
SourceFile = value;
129-
break;
130-
case "test.codeowners":
131-
CodeOwners = value;
132-
break;
133121
case "test.traits":
134122
Traits = value;
135123
break;
@@ -223,16 +211,6 @@ public override void EnumerateTags<TProcessor>(ref TProcessor processor)
223211
processor.Process(new TagItem<string>("test.parameters", Parameters, ParametersBytes));
224212
}
225213

226-
if (SourceFile is not null)
227-
{
228-
processor.Process(new TagItem<string>("test.source.file", SourceFile, SourceFileBytes));
229-
}
230-
231-
if (CodeOwners is not null)
232-
{
233-
processor.Process(new TagItem<string>("test.codeowners", CodeOwners, CodeOwnersBytes));
234-
}
235-
236214
if (Traits is not null)
237215
{
238216
processor.Process(new TagItem<string>("test.traits", Traits, TraitsBytes));
@@ -377,20 +355,6 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb)
377355
.Append(',');
378356
}
379357

380-
if (SourceFile is not null)
381-
{
382-
sb.Append("test.source.file (tag):")
383-
.Append(SourceFile)
384-
.Append(',');
385-
}
386-
387-
if (CodeOwners is not null)
388-
{
389-
sb.Append("test.codeowners (tag):")
390-
.Append(CodeOwners)
391-
.Append(',');
392-
}
393-
394358
if (Traits is not null)
395359
{
396360
sb.Append("test.traits (tag):")

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/TestSuiteSpanTags.g.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ partial class TestSuiteSpanTags
1616
{
1717
// SuiteBytes = MessagePack.Serialize("test.suite");
1818
private static ReadOnlySpan<byte> SuiteBytes => new byte[] { 170, 116, 101, 115, 116, 46, 115, 117, 105, 116, 101 };
19+
// SourceFileBytes = MessagePack.Serialize("test.source.file");
20+
private static ReadOnlySpan<byte> SourceFileBytes => new byte[] { 176, 116, 101, 115, 116, 46, 115, 111, 117, 114, 99, 101, 46, 102, 105, 108, 101 };
21+
// CodeOwnersBytes = MessagePack.Serialize("test.codeowners");
22+
private static ReadOnlySpan<byte> CodeOwnersBytes => new byte[] { 175, 116, 101, 115, 116, 46, 99, 111, 100, 101, 111, 119, 110, 101, 114, 115 };
1923

2024
public override string? GetTag(string key)
2125
{
2226
return key switch
2327
{
2428
"test.suite" => Suite,
29+
"test.source.file" => SourceFile,
30+
"test.codeowners" => CodeOwners,
2531
_ => base.GetTag(key),
2632
};
2733
}
@@ -33,6 +39,12 @@ public override void SetTag(string key, string value)
3339
case "test.suite":
3440
Suite = value;
3541
break;
42+
case "test.source.file":
43+
SourceFile = value;
44+
break;
45+
case "test.codeowners":
46+
CodeOwners = value;
47+
break;
3648
default:
3749
base.SetTag(key, value);
3850
break;
@@ -46,6 +58,16 @@ public override void EnumerateTags<TProcessor>(ref TProcessor processor)
4658
processor.Process(new TagItem<string>("test.suite", Suite, SuiteBytes));
4759
}
4860

61+
if (SourceFile is not null)
62+
{
63+
processor.Process(new TagItem<string>("test.source.file", SourceFile, SourceFileBytes));
64+
}
65+
66+
if (CodeOwners is not null)
67+
{
68+
processor.Process(new TagItem<string>("test.codeowners", CodeOwners, CodeOwnersBytes));
69+
}
70+
4971
base.EnumerateTags(ref processor);
5072
}
5173

@@ -58,6 +80,20 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb)
5880
.Append(',');
5981
}
6082

83+
if (SourceFile is not null)
84+
{
85+
sb.Append("test.source.file (tag):")
86+
.Append(SourceFile)
87+
.Append(',');
88+
}
89+
90+
if (CodeOwners is not null)
91+
{
92+
sb.Append("test.codeowners (tag):")
93+
.Append(CodeOwners)
94+
.Append(',');
95+
}
96+
6197
base.WriteAdditionalTags(sb);
6298
}
6399
}

tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/TestSpanTags.g.cs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ partial class TestSpanTags
2222
private static ReadOnlySpan<byte> NameBytes => new byte[] { 169, 116, 101, 115, 116, 46, 110, 97, 109, 101 };
2323
// ParametersBytes = MessagePack.Serialize("test.parameters");
2424
private static ReadOnlySpan<byte> ParametersBytes => new byte[] { 175, 116, 101, 115, 116, 46, 112, 97, 114, 97, 109, 101, 116, 101, 114, 115 };
25-
// SourceFileBytes = MessagePack.Serialize("test.source.file");
26-
private static ReadOnlySpan<byte> SourceFileBytes => new byte[] { 176, 116, 101, 115, 116, 46, 115, 111, 117, 114, 99, 101, 46, 102, 105, 108, 101 };
27-
// CodeOwnersBytes = MessagePack.Serialize("test.codeowners");
28-
private static ReadOnlySpan<byte> CodeOwnersBytes => new byte[] { 175, 116, 101, 115, 116, 46, 99, 111, 100, 101, 111, 119, 110, 101, 114, 115 };
2925
// TraitsBytes = MessagePack.Serialize("test.traits");
3026
private static ReadOnlySpan<byte> TraitsBytes => new byte[] { 171, 116, 101, 115, 116, 46, 116, 114, 97, 105, 116, 115 };
3127
// SkipReasonBytes = MessagePack.Serialize("test.skip_reason");
@@ -83,8 +79,6 @@ partial class TestSpanTags
8379
{
8480
"test.name" => Name,
8581
"test.parameters" => Parameters,
86-
"test.source.file" => SourceFile,
87-
"test.codeowners" => CodeOwners,
8882
"test.traits" => Traits,
8983
"test.skip_reason" => SkipReason,
9084
"test.skipped_by_itr" => SkippedByIntelligentTestRunner,
@@ -124,12 +118,6 @@ public override void SetTag(string key, string value)
124118
case "test.parameters":
125119
Parameters = value;
126120
break;
127-
case "test.source.file":
128-
SourceFile = value;
129-
break;
130-
case "test.codeowners":
131-
CodeOwners = value;
132-
break;
133121
case "test.traits":
134122
Traits = value;
135123
break;
@@ -223,16 +211,6 @@ public override void EnumerateTags<TProcessor>(ref TProcessor processor)
223211
processor.Process(new TagItem<string>("test.parameters", Parameters, ParametersBytes));
224212
}
225213

226-
if (SourceFile is not null)
227-
{
228-
processor.Process(new TagItem<string>("test.source.file", SourceFile, SourceFileBytes));
229-
}
230-
231-
if (CodeOwners is not null)
232-
{
233-
processor.Process(new TagItem<string>("test.codeowners", CodeOwners, CodeOwnersBytes));
234-
}
235-
236214
if (Traits is not null)
237215
{
238216
processor.Process(new TagItem<string>("test.traits", Traits, TraitsBytes));
@@ -377,20 +355,6 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb)
377355
.Append(',');
378356
}
379357

380-
if (SourceFile is not null)
381-
{
382-
sb.Append("test.source.file (tag):")
383-
.Append(SourceFile)
384-
.Append(',');
385-
}
386-
387-
if (CodeOwners is not null)
388-
{
389-
sb.Append("test.codeowners (tag):")
390-
.Append(CodeOwners)
391-
.Append(',');
392-
}
393-
394358
if (Traits is not null)
395359
{
396360
sb.Append("test.traits (tag):")

0 commit comments

Comments
 (0)