File tree Expand file tree Collapse file tree
TUnit.Assertions.Tests/Bugs
TUnit.Assertions/Conditions/Helpers Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ namespace TUnit . Assertions . Tests . Bugs ;
2+
3+ /// <summary>
4+ /// Regression tests for issue #5841.
5+ /// IsEquivalentTo must not invoke property/field getters whose return type is a ref struct
6+ /// (e.g. ReadOnlySpan<T>, reachable via ReadOnlyMemory<T>.Span). Ref structs cannot
7+ /// be boxed, so RuntimeMethodInfo.Invoke throws NotSupportedException.
8+ /// </summary>
9+ public class Tests5841
10+ {
11+ public class HasMemoryProperty
12+ {
13+ public ReadOnlyMemory < byte > Memory { get ; init ; }
14+ }
15+
16+ public class HasSpanProperty
17+ {
18+ public ReadOnlySpan < byte > Span => default ;
19+ public int Value { get ; init ; }
20+ }
21+
22+ [ Test ]
23+ public async Task IsEquivalentTo_with_ReadOnlyMemory_property_does_not_invoke_Span_getter ( )
24+ {
25+ var a = new HasMemoryProperty { Memory = new byte [ ] { 1 , 2 , 3 } } ;
26+ var b = new HasMemoryProperty { Memory = new byte [ ] { 1 , 2 , 3 } } ;
27+
28+ await Assert . That ( a ) . IsEquivalentTo ( b ) ;
29+ }
30+
31+ [ Test ]
32+ public async Task IsEquivalentTo_skips_ref_struct_returning_property ( )
33+ {
34+ var a = new HasSpanProperty { Value = 7 } ;
35+ var b = new HasSpanProperty { Value = 7 } ;
36+
37+ await Assert . That ( a ) . IsEquivalentTo ( b ) ;
38+ }
39+ }
Original file line number Diff line number Diff line change @@ -35,19 +35,54 @@ private static MemberInfo[] BuildMembersToCompare(Type type)
3535 var members = new List < MemberInfo > ( ) ;
3636
3737 // Filter out indexed properties (properties with parameters like this[int index])
38+ // and members whose type is a ref struct (IsByRefLike). Ref structs cannot be boxed,
39+ // so PropertyInfo.GetValue / FieldInfo.GetValue throws NotSupportedException on them.
40+ // Common offender: ReadOnlyMemory<T>.Span returns ReadOnlySpan<T>.
3841 var properties = type . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) ;
3942 foreach ( var prop in properties )
4043 {
41- if ( prop . GetIndexParameters ( ) . Length == 0 && prop . CanRead && prop . GetMethod ? . IsPublic == true )
44+ if ( prop . GetIndexParameters ( ) . Length == 0
45+ && prop . CanRead
46+ && prop . GetMethod ? . IsPublic == true
47+ && ! IsByRefLike ( prop . PropertyType ) )
4248 {
4349 members . Add ( prop ) ;
4450 }
4551 }
4652
47- members . AddRange ( type . GetFields ( BindingFlags . Public | BindingFlags . Instance ) ) ;
53+ foreach ( var field in type . GetFields ( BindingFlags . Public | BindingFlags . Instance ) )
54+ {
55+ if ( ! IsByRefLike ( field . FieldType ) )
56+ {
57+ members . Add ( field ) ;
58+ }
59+ }
60+
4861 return members . ToArray ( ) ;
4962 }
5063
64+ private static bool IsByRefLike ( Type type )
65+ {
66+ #if NET5_0_OR_GREATER
67+ return type . IsByRefLike ;
68+ #else
69+ if ( ! type . IsValueType )
70+ {
71+ return false ;
72+ }
73+
74+ foreach ( var attr in type . GetCustomAttributesData ( ) )
75+ {
76+ if ( attr . AttributeType . FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute" )
77+ {
78+ return true ;
79+ }
80+ }
81+
82+ return false ;
83+ #endif
84+ }
85+
5186 /// <summary>
5287 /// Gets the value of a member (property or field) from an object.
5388 /// </summary>
You can’t perform that action at this time.
0 commit comments