Skip to content

Commit 27a978e

Browse files
authored
+semver:minor - Improve AOT annotations + Various fixes (#3485)
* refactor(formatting): escape dots in strings to prevent namespace interpretation in VS Test Explorer * test(aot): add tests for AOT compatibility with enum arguments to prevent IL3050 warnings * refactor(assertions): simplify assertion calls by removing unnecessary casts * refactor(matrix): streamline enum handling and remove unnecessary dynamic code attributes * refactor(attributes): replace RequiresDynamicCode with RequiresUnreferencedCode for AOT compatibility * test(aot): enhance AOT compatibility with property injection and dynamic test handling * refactor(tests): remove unused methods and improve AOT compatibility annotations * chore(dependencies): update TUnit package version to 0.75.38-PullRequest3485.0 * refactor(attributes): skip compiler-internal attributes during attribute processing * refactor(PropertyInjection): use non-nullable type names for improved clarity * refactor(CastHelper): remove unnecessary suppress message for AOT compilation * refactor(CastHelper): simplify casting logic by removing unnecessary suppress messages and redundant code * refactor(CastHelper): enhance casting logic with AOT-safe conversions and improved type handling * refactor(CastHelper): optimize casting method by adding direct type check for improved performance * refactor(CastHelper): improve AOT handling in TryReflectionConversion method to skip reflection-based conversions in AOT mode * feat: enhance AOT converter by scanning for conversion operators in source code and referenced assemblies * feat: improve AOT converter by scanning all types in compilation for conversion operators * feat: add support for scanning closed generic types in method parameters for conversion operators * feat: add AOT and single file publishing steps to CI pipeline * feat: update TUnit package version references to use TUnitVersion variable * feat: add additional suppression message for trimming in reflection mode * feat: introduce IMemberMetadata interface and update related metadata classes * fix: refine DynamicallyAccessedMembers attributes in Cast methods for AOT compatibility * fix: update DynamicallyAccessedMembers attributes in Cast methods for improved AOT compatibility
1 parent c74d6a7 commit 27a978e

File tree

94 files changed

+1267
-1771
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+1267
-1771
lines changed

.github/workflows/dotnet.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ jobs:
6666
- name: Build
6767
run: dotnet build -c Release
6868

69+
- name: Publish AOT
70+
run: dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --use-current-runtime -p:Aot=true -o TESTPROJECT_AOT --framework net8.0
71+
72+
- name: Publish Single File
73+
run: dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --use-current-runtime -p:SingleFile=true -o TESTPROJECT_SINGLEFILE --framework net8.0
74+
6975
- name: Run Pipeline
7076
uses: ./.github/actions/execute-pipeline
7177
with:

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.8.1" />
8181
<PackageVersion Include="Testcontainers.Redis" Version="4.8.1" />
8282
<PackageVersion Include="trxparser" Version="0.5.0" />
83+
<PackageVersion Include="TUnit.Assertions.FSharp" Version="0.75.38-PullRequest3485.0" />
8384
<PackageVersion Include="Verify" Version="31.0.4" />
8485
<PackageVersion Include="Verify.NUnit" Version="31.0.4" />
8586
<PackageVersion Include="TUnit" Version="0.75.30" />
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
### New Rules
22

3-
Rule ID | Category | Severity | Notes
4-
--------|----------|----------|-------
5-
TUnit0052 | Usage | Warning | Multiple constructors found without [TestConstructor] attribute
3+
Rule ID | Category | Severity | Notes
4+
--------|----------|----------|-------

TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9292
return null;
9393
}
9494

95-
// Check for RequiresDynamicCode attribute
96-
var requiresDynamicCodeAttr = classSymbol.GetAttributes()
97-
.FirstOrDefault(attr => attr.AttributeClass?.Name == "RequiresDynamicCodeAttribute");
98-
string? requiresDynamicCodeMessage = null;
99-
if (requiresDynamicCodeAttr != null && requiresDynamicCodeAttr.ConstructorArguments.Length > 0)
95+
// Check for RequiresUnreferencedCode attribute
96+
var RequiresUnreferencedCodeAttr = classSymbol.GetAttributes()
97+
.FirstOrDefault(attr => attr.AttributeClass?.Name == "RequiresUnreferencedCodeAttribute");
98+
string? RequiresUnreferencedCodeMessage = null;
99+
if (RequiresUnreferencedCodeAttr != null && RequiresUnreferencedCodeAttr.ConstructorArguments.Length > 0)
100100
{
101-
requiresDynamicCodeMessage = requiresDynamicCodeAttr.ConstructorArguments[0].Value?.ToString();
101+
RequiresUnreferencedCodeMessage = RequiresUnreferencedCodeAttr.ConstructorArguments[0].Value?.ToString();
102102
}
103103

104104
return new AssertionExtensionData(
@@ -108,7 +108,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
108108
assertionBaseType,
109109
constructors,
110110
overloadPriority,
111-
requiresDynamicCodeMessage
111+
RequiresUnreferencedCodeMessage
112112
);
113113
}
114114

@@ -153,7 +153,7 @@ private static void GenerateExtensionMethods(SourceProductionContext context, As
153153

154154
sourceBuilder.AppendLine($"namespace TUnit.Assertions.Extensions;");
155155
sourceBuilder.AppendLine();
156-
156+
157157
// Extension class
158158
var extensionClassName = $"{data.ClassSymbol.Name}Extensions";
159159
sourceBuilder.AppendLine($"/// <summary>");
@@ -309,11 +309,11 @@ private static void GenerateExtensionMethod(
309309
sourceBuilder.AppendLine($" /// Extension method for {assertionType.Name}.");
310310
sourceBuilder.AppendLine(" /// </summary>");
311311

312-
// Add RequiresDynamicCode attribute if present
313-
if (!string.IsNullOrEmpty(data.RequiresDynamicCodeMessage))
312+
// Add RequiresUnreferencedCode attribute if present
313+
if (!string.IsNullOrEmpty(data.RequiresUnreferencedCodeMessage))
314314
{
315-
var escapedMessage = data.RequiresDynamicCodeMessage.Replace("\"", "\\\"");
316-
sourceBuilder.AppendLine($" [global::System.Diagnostics.CodeAnalysis.RequiresDynamicCode(\"{escapedMessage}\")]");
315+
var escapedMessage = data.RequiresUnreferencedCodeMessage!.Replace("\"", "\\\"");
316+
sourceBuilder.AppendLine($" [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(\"{escapedMessage}\")]");
317317
}
318318

319319
// Add OverloadResolutionPriority attribute only if priority > 0
@@ -454,6 +454,6 @@ private record AssertionExtensionData(
454454
INamedTypeSymbol AssertionBaseType,
455455
ImmutableArray<IMethodSymbol> Constructors,
456456
int OverloadResolutionPriority,
457-
string? RequiresDynamicCodeMessage
457+
string? RequiresUnreferencedCodeMessage
458458
);
459459
}

TUnit.Assertions.Tests/DictionaryCollectionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public async Task Dictionary_IsEquivalentTo_Works()
184184
// IsEquivalentTo works on collections regardless of order
185185
// Cast both to IEnumerable to use collection equivalency
186186
await Assert.That((IEnumerable<KeyValuePair<string, int>>)dictionary1)
187-
.IsEquivalentTo((IEnumerable<KeyValuePair<string, int>>)dictionary2);
187+
.IsEquivalentTo(dictionary2);
188188
}
189189

190190
[Test]

TUnit.Assertions.Tests/Old/DictionaryAssertionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public async Task String_ReadOnlyDictionary_Contains_Key()
3232
{
3333
var dictionary = new ReadDictionary();
3434

35-
await TUnitAssert.That((IReadOnlyDictionary<string, string>)dictionary).ContainsKey("Blah");
35+
await TUnitAssert.That(dictionary).ContainsKey("Blah");
3636
}
3737

3838
[Test]

TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public async Task Different_Dictionaries_Are_Equivalent_With_Different_Ordered_K
9898
// Dictionaries are equivalent regardless of key order by default
9999
// Cast both to IEnumerable to use collection equivalency
100100
await TUnitAssert.That((IEnumerable<KeyValuePair<string, string>>)dict1)
101-
.IsEquivalentTo((IEnumerable<KeyValuePair<string, string>>)dict2);
101+
.IsEquivalentTo(dict2);
102102
}
103103

104104
[Test]

TUnit.Assertions.Tests/TypeOfTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public async Task CollectionAssertion_NullableByteArray_CanUseCollectionMethods(
242242

243243
// Should be able to use collection assertion methods
244244
await Assert.That(nullableBytes).HasCount(5);
245-
await Assert.That(nullableBytes).Contains((byte)3);
245+
await Assert.That(nullableBytes).Contains(3);
246246
await Assert.That(nullableBytes).IsInOrder();
247247

248248
}

TUnit.Assertions/Assertions/Enums/EnumAssertions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TEnum> me
3030
}
3131

3232
// Use HasFlag method for enum flag checking
33-
var enumValue = (Enum)(object)value;
34-
var enumFlag = (Enum)(object)_expectedFlag;
33+
var enumValue = (Enum)value;
34+
var enumFlag = (Enum)_expectedFlag;
3535

3636
if (enumValue.HasFlag(enumFlag))
3737
{
@@ -70,8 +70,8 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TEnum> me
7070
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}"));
7171
}
7272

73-
var enumValue = (Enum)(object)value;
74-
var enumFlag = (Enum)(object)_unexpectedFlag;
73+
var enumValue = (Enum)value;
74+
var enumFlag = (Enum)_unexpectedFlag;
7575

7676
if (!enumValue.HasFlag(enumFlag))
7777
{
@@ -106,7 +106,11 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TEnum> me
106106
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}"));
107107
}
108108

109+
#if NET
110+
if (Enum.IsDefined(value))
111+
#else
109112
if (Enum.IsDefined(typeof(TEnum), value))
113+
#endif
110114
{
111115
return Task.FromResult(AssertionResult.Passed);
112116
}
@@ -139,7 +143,11 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TEnum> me
139143
return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}"));
140144
}
141145

146+
#if NET
147+
if (!Enum.IsDefined(value))
148+
#else
142149
if (!Enum.IsDefined(typeof(TEnum), value))
150+
#endif
143151
{
144152
return Task.FromResult(AssertionResult.Passed);
145153
}

TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace TUnit.Assertions.Conditions.Helpers;
1010
/// For complex objects, performs deep comparison of properties and fields.
1111
/// </summary>
1212
/// <typeparam name="T">The type of objects to compare</typeparam>
13-
[RequiresDynamicCode("Structural equality comparison uses reflection to access object members and is not compatible with AOT")]
13+
[RequiresUnreferencedCode("Structural equality comparison uses reflection to access object members and is not compatible with AOT")]
1414
public sealed class StructuralEqualityComparer<T> : IEqualityComparer<T>
1515
{
1616
/// <summary>

0 commit comments

Comments
 (0)