Skip to content

Commit 2c105d9

Browse files
authored
+semver:minor - Allow multiple property values with the same key (#2485)
* Allow multiple property values with the same key * Update Public API
1 parent 62ae162 commit 2c105d9

14 files changed

+116
-24
lines changed

TUnit.Core/DiscoveredTestContext.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ internal DiscoveredTestContext(TestContext testContext)
1919

2020
public void AddProperty(string key, string value)
2121
{
22-
TestContext.TestDetails.InternalCustomProperties.Add(key, value);
22+
TestContext.TestDetails.InternalCustomProperties
23+
.GetOrAdd(key, [])
24+
.Add(value);
2325
}
2426

2527
public void AddCategory(string category)

TUnit.Core/TestDetails.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System.ComponentModel;
1+
using System.Collections.Concurrent;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Runtime.CompilerServices;
34
using System.Text.Json.Serialization;
45
using TUnit.Core.Interfaces;
56

@@ -114,9 +115,12 @@ public abstract record TestDetails
114115
/// <summary>
115116
/// Gets the custom properties for the test.
116117
/// </summary>
117-
public IReadOnlyDictionary<string, string> CustomProperties => InternalCustomProperties;
118+
[field: AllowNull, MaybeNull]
119+
public IReadOnlyDictionary<string, IReadOnlyList<string>> CustomProperties => field ??= InternalCustomProperties.ToDictionary(
120+
kvp => kvp.Key, IReadOnlyList<string> (kvp) => kvp.Value.AsReadOnly()
121+
);
118122

119-
internal Dictionary<string, string> InternalCustomProperties { get; } = [];
123+
internal ConcurrentDictionary<string, List<string>> InternalCustomProperties { get; } = [];
120124

121125
/// <summary>
122126
/// Gets the attributes for the test assembly.

TUnit.Engine.Tests/Bugs/Bug2481.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Shouldly;
2+
using TUnit.Engine.Tests.Enums;
3+
4+
namespace TUnit.Engine.Tests.Bugs;
5+
6+
public class Bug2481(TestMode testMode) : InvokableTestBase(testMode)
7+
{
8+
[Test]
9+
public async Task Test()
10+
{
11+
await RunTestsWithFilter(
12+
"/*/TUnit.TestProject.Bugs._2481/*/*[Group=Bugs]",
13+
[
14+
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
15+
result => result.ResultSummary.Counters.Total.ShouldBe(1),
16+
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
17+
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
18+
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
19+
]);
20+
}
21+
22+
[Test]
23+
public async Task Test2()
24+
{
25+
await RunTestsWithFilter(
26+
"/*/TUnit.TestProject.Bugs._2481/*/*[Group=2481]",
27+
[
28+
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
29+
result => result.ResultSummary.Counters.Total.ShouldBe(1),
30+
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
31+
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
32+
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
33+
]);
34+
}
35+
36+
[Test]
37+
public async Task Test3()
38+
{
39+
await RunTestsWithFilter(
40+
"/*/TUnit.TestProject.Bugs._2481/*/*[Group=TUnit]",
41+
[
42+
result => result.ResultSummary.Outcome.ShouldBe("Completed"),
43+
result => result.ResultSummary.Counters.Total.ShouldBe(1),
44+
result => result.ResultSummary.Counters.Passed.ShouldBe(1),
45+
result => result.ResultSummary.Counters.Failed.ShouldBe(0),
46+
result => result.ResultSummary.Counters.NotExecuted.ShouldBe(0)
47+
]);
48+
}
49+
}

TUnit.Engine/Extensions/TestExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ internal static TestNode ToTestNode(this TestContext testContext)
3333
),
3434

3535
// Custom TUnit Properties
36-
..testDetails.Categories.Select(category => new TestMetadataProperty(category, string.Empty)),
37-
..testDetails.CustomProperties.Select(x => new TestMetadataProperty(x.Key, x.Value)),
36+
..testDetails.Categories.Select(category => new TestMetadataProperty(category)),
37+
..ExtractProperties(testDetails),
3838

3939
// Artifacts
4040
..testContext.Artifacts.Select(x => new FileArtifactProperty(x.File, x.DisplayName, x.Description)),
@@ -48,6 +48,17 @@ internal static TestNode ToTestNode(this TestContext testContext)
4848
return testNode;
4949
}
5050

51+
public static IEnumerable<KeyValuePairStringProperty> ExtractProperties(this TestDetails testDetails)
52+
{
53+
foreach (var propertyGroup in testDetails.CustomProperties)
54+
{
55+
foreach (var propertyValue in propertyGroup.Value)
56+
{
57+
yield return new KeyValuePairStringProperty(propertyGroup.Key, propertyValue);
58+
}
59+
}
60+
}
61+
5162
internal static TestNode WithProperty(this TestNode testNode, IProperty property)
5263
{
5364
testNode.Properties.Add(property);

TUnit.Engine/Json/TestJson.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public record TestJson
2121

2222
public required TimeSpan? Timeout { get; init; }
2323

24-
public required IReadOnlyDictionary<string, string> CustomProperties { get; init; }
24+
public required IReadOnlyDictionary<string, IReadOnlyList<string>> CustomProperties { get; init; }
2525

2626
public required string? ReturnType { get; init; }
2727

TUnit.Engine/Services/TestFilterService.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Testing.Platform.Logging;
55
using Microsoft.Testing.Platform.Requests;
66
using TUnit.Core;
7+
using TUnit.Engine.Extensions;
78

89
namespace TUnit.Engine.Services;
910

@@ -73,9 +74,14 @@ private string BuildPath(TestDetails testDetails)
7374

7475
private PropertyBag BuildPropertyBag(TestDetails testDetails)
7576
{
77+
var properties = testDetails.ExtractProperties();
78+
79+
var categories = testDetails.Categories.Select(x => new TestMetadataProperty(x));
80+
7681
return new PropertyBag(
7782
[
78-
..testDetails.CustomProperties.Select(x => new KeyValuePairStringProperty(x.Key, x.Value)),
83+
..properties,
84+
..categories,
7985
..testDetails.Categories.Select(x => new KeyValuePairStringProperty("Category", x))
8086
]
8187
);
@@ -86,4 +92,4 @@ private bool UnhandledFilter(ITestExecutionFilter testExecutionFilter)
8692
_logger.LogWarning($"Filter is Unhandled Type: {testExecutionFilter.GetType().FullName}");
8793
return true;
8894
}
89-
}
95+
}

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet2_0.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ namespace TUnit.Core
843843
public System.Attribute[] ClassAttributes { get; }
844844
public abstract object ClassInstance { get; }
845845
public required int CurrentRepeatAttempt { get; init; }
846-
public System.Collections.Generic.IReadOnlyDictionary<string, string> CustomProperties { get; }
846+
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IReadOnlyList<string>> CustomProperties { get; }
847847
[System.Text.Json.Serialization.JsonIgnore]
848848
public required System.Attribute[] DataAttributes { get; init; }
849849
[System.Text.Json.Serialization.JsonIgnore]

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ namespace TUnit.Core
890890
public System.Attribute[] ClassAttributes { get; }
891891
public abstract object ClassInstance { get; }
892892
public required int CurrentRepeatAttempt { get; init; }
893-
public System.Collections.Generic.IReadOnlyDictionary<string, string> CustomProperties { get; }
893+
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IReadOnlyList<string>> CustomProperties { get; }
894894
[System.Text.Json.Serialization.JsonIgnore]
895895
public required System.Attribute[] DataAttributes { get; init; }
896896
[System.Text.Json.Serialization.JsonIgnore]

TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ namespace TUnit.Core
890890
public System.Attribute[] ClassAttributes { get; }
891891
public abstract object ClassInstance { get; }
892892
public required int CurrentRepeatAttempt { get; init; }
893-
public System.Collections.Generic.IReadOnlyDictionary<string, string> CustomProperties { get; }
893+
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IReadOnlyList<string>> CustomProperties { get; }
894894
[System.Text.Json.Serialization.JsonIgnore]
895895
public required System.Attribute[] DataAttributes { get; init; }
896896
[System.Text.Json.Serialization.JsonIgnore]

TUnit.PublicAPI/Tests.Engine_Library_Has_No_API_Changes.DotNet2_0.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ namespace TUnit.Engine.Json
9696
public TestJson() { }
9797
public required System.Collections.Generic.IReadOnlyList<string> Categories { get; init; }
9898
public required string? ClassType { get; init; }
99-
public required System.Collections.Generic.IReadOnlyDictionary<string, string> CustomProperties { get; init; }
99+
public required System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IReadOnlyList<string>> CustomProperties { get; init; }
100100
public required string DisplayName { get; set; }
101101
public required System.Collections.Generic.Dictionary<string, object?> ObjectBag { get; init; }
102102
public required TUnit.Engine.Json.TestResultJson? Result { get; set; }

0 commit comments

Comments
 (0)