Skip to content

Commit 599623b

Browse files
authored
Move TestAccessors to extension properties (#14134)
Cleaner syntax. Also update exception handling to be more useful. This is to better align with what I'm putting in the SDK repo. Hopefully I can eventually move this upstream to some of our shared test infra. It does make it a little bit more awkward for VB, but not unusable.
1 parent 49f410b commit 599623b

File tree

194 files changed

+1162
-1140
lines changed

Some content is hidden

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

194 files changed

+1162
-1140
lines changed

src/Common/tests/TestUtilities/AppContextSwitchScope.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,6 @@ public static bool GetDefaultValueForSwitchInAssembly(string switchName, string
6363
Type type = Type.GetType($"{typeName}, {assemblyName}")
6464
?? throw new InvalidOperationException($"Could not find {typeName} type in {assemblyName} assembly.");
6565

66-
return type.TestAccessor().Dynamic.GetSwitchDefaultValue(switchName);
66+
return type.TestAccessor.Dynamic.GetSwitchDefaultValue(switchName);
6767
}
6868
}

src/Common/tests/TestUtilities/ITestAccessor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ public interface ITestAccessor
4646
///
4747
/// public int InternalGetDirectoryNameOffset(ReadOnlySpan<char> path)
4848
/// {
49-
/// var accessor = typeof(System.IO.Path).TestAccessor();
49+
/// var accessor = typeof(System.IO.Path).TestAccessor;
5050
/// return accessor.CreateDelegate<GetDirectoryNameOffset>()(@"C:\Foo");
5151
/// }
5252
///
5353
/// // Without ref structs you can just use Func/Action
54-
/// var accessor = typeof(Color).TestAccessor();
54+
/// var accessor = typeof(Color).TestAccessor;
5555
/// bool result = accessor.CreateDelegate<Func<KnownColor, bool>>("IsKnownColorSystem")(KnownColor.Window);
5656
/// ]]>
5757
/// </example>

src/Common/tests/TestUtilities/TestAccessor.cs

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Dynamic;
55
using System.Reflection;
6+
using System.Runtime.ExceptionServices;
67

78
namespace System;
89

@@ -63,7 +64,7 @@ public TDelegate CreateDelegate<TDelegate>(string? methodName = null)
6364
{
6465
Type type = typeof(TDelegate);
6566
MethodInfo? invokeMethodInfo = type.GetMethod("Invoke");
66-
Type[] types = invokeMethodInfo is null ? [] : invokeMethodInfo.GetParameters().Select(pi => pi.ParameterType).ToArray();
67+
Type[] types = invokeMethodInfo is null ? [] : [.. invokeMethodInfo.GetParameters().Select(pi => pi.ParameterType)];
6768

6869
// To make it easier to write a class wrapper with a number of delegates,
6970
// we'll take the name from the delegate itself when unspecified.
@@ -86,15 +87,15 @@ private sealed class DynamicWrapper : DynamicObject
8687
{
8788
private readonly object? _instance;
8889

89-
public DynamicWrapper(object? instance)
90-
=> _instance = instance;
90+
public DynamicWrapper(object? instance) => _instance = instance;
9191

9292
public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
9393
{
94-
result = null;
9594
ArgumentNullException.ThrowIfNull(args);
9695
ArgumentNullException.ThrowIfNull(binder);
9796

97+
result = null;
98+
9899
MethodInfo? methodInfo = null;
99100
Type? type = s_type;
100101

@@ -115,7 +116,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args,
115116
binder.Name,
116117
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static,
117118
binder: null,
118-
args.Select(a => a!.GetType()).ToArray(),
119+
[.. args.Select(a => a!.GetType())],
119120
modifiers: null);
120121
}
121122

@@ -131,7 +132,9 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args,
131132
while (true);
132133

133134
if (methodInfo is null)
135+
{
134136
return false;
137+
}
135138

136139
try
137140
{
@@ -140,31 +143,68 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args,
140143
catch (TargetInvocationException ex) when (ex.InnerException is not null)
141144
{
142145
// Unwrap the inner exception to make it easier for callers to handle.
143-
throw ex.InnerException;
146+
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
144147
}
145148

146149
return true;
147150
}
148151

149152
public override bool TrySetMember(SetMemberBinder binder, object? value)
150153
{
151-
MemberInfo? info = TestAccessor<T>.DynamicWrapper.GetFieldOrPropertyInfo(binder.Name);
152-
if (info is null)
154+
MemberInfo? memberInfo = TestAccessor<T>.DynamicWrapper.GetFieldOrPropertyInfo(binder.Name);
155+
if (memberInfo is null)
156+
{
153157
return false;
158+
}
159+
160+
try
161+
{
162+
switch (memberInfo)
163+
{
164+
case FieldInfo fieldInfo:
165+
fieldInfo.SetValue(_instance, value);
166+
break;
167+
case PropertyInfo propertyInfo:
168+
propertyInfo.SetValue(_instance, value);
169+
break;
170+
default:
171+
throw new InvalidOperationException();
172+
}
173+
}
174+
catch (TargetInvocationException ex) when (ex.InnerException is not null)
175+
{
176+
// Unwrap the inner exception to make it easier for callers to handle.
177+
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
178+
}
154179

155-
SetValue(info, value);
156180
return true;
157181
}
158182

159183
public override bool TryGetMember(GetMemberBinder binder, out object? result)
160184
{
161185
result = null;
162186

163-
MemberInfo? info = TestAccessor<T>.DynamicWrapper.GetFieldOrPropertyInfo(binder.Name);
164-
if (info is null)
187+
MemberInfo? memberInfo = TestAccessor<T>.DynamicWrapper.GetFieldOrPropertyInfo(binder.Name);
188+
if (memberInfo is null)
189+
{
165190
return false;
191+
}
192+
193+
try
194+
{
195+
result = memberInfo switch
196+
{
197+
FieldInfo fieldInfo => fieldInfo.GetValue(_instance),
198+
PropertyInfo propertyInfo => propertyInfo.GetValue(_instance),
199+
_ => throw new InvalidOperationException()
200+
};
201+
}
202+
catch (TargetInvocationException ex) when (ex.InnerException is not null)
203+
{
204+
// Unwrap the inner exception to make it easier for callers to handle.
205+
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
206+
}
166207

167-
result = GetValue(info);
168208
return true;
169209
}
170210

@@ -195,28 +235,5 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result)
195235

196236
return info;
197237
}
198-
199-
private object? GetValue(MemberInfo memberInfo)
200-
=> memberInfo switch
201-
{
202-
FieldInfo fieldInfo => fieldInfo.GetValue(_instance),
203-
PropertyInfo propertyInfo => propertyInfo.GetValue(_instance),
204-
_ => throw new InvalidOperationException()
205-
};
206-
207-
private void SetValue(MemberInfo memberInfo, object? value)
208-
{
209-
switch (memberInfo)
210-
{
211-
case FieldInfo fieldInfo:
212-
fieldInfo.SetValue(_instance, value);
213-
break;
214-
case PropertyInfo propertyInfo:
215-
propertyInfo.SetValue(_instance, value);
216-
break;
217-
default:
218-
throw new InvalidOperationException();
219-
}
220-
}
221238
}
222239
}

src/Common/tests/TestUtilities/TestAccessors.cs

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,53 +17,53 @@ public static partial class TestAccessors
1717
// the array here.
1818
private static readonly object?[] s_nullObjectParam = [null];
1919

20-
/// <summary>
21-
/// Extension that creates a generic internals test accessor for a
22-
/// given instance or Type class (if only accessing statics).
23-
/// </summary>
2420
/// <param name="instanceOrType">
2521
/// Instance or Type class (if only accessing statics).
2622
/// </param>
27-
/// <remarks>
28-
/// <para>
29-
/// Use <see cref="ITestAccessor.CreateDelegate">CreateDelegate</see> to deal with methods that take spans or
30-
/// other ref structs. For other members, use the dynamic accessor:
31-
/// </para>
32-
/// <code>
33-
/// <![CDATA[
34-
/// Version version = new Version(4, 1);
35-
/// Assert.Equal(4, version.TestAccessor().Dynamic._Major));
36-
///
37-
/// // Or
38-
///
39-
/// dynamic accessor = version.TestAccessor().Dynamic;
40-
/// Assert.Equal(4, accessor._Major));
41-
///
42-
/// // Or
43-
///
44-
/// Version version2 = new Version("4.1");
45-
/// dynamic accessor = typeof(Version).TestAccessor().Dynamic;
46-
/// Assert.Equal(version2, accessor.Parse("4.1")));
47-
/// ]]>
48-
/// </code>
49-
/// <para>
50-
/// When attempting to get nested private types that are generic (nested types in a generic type
51-
/// are always generic, and inherit the type specifiers of the the parent type), use the extension
52-
/// <see cref="ReflectionHelper.GetFullNestedType(Type, string, Span{Type})"/> to get a fully
53-
/// instantiated type for the nested type, then pass that Type to this method.
54-
/// </para>
55-
/// </remarks>
56-
public static ITestAccessor TestAccessor(this object instanceOrType)
23+
extension(object instanceOrType)
5724
{
58-
ITestAccessor? testAccessor = instanceOrType is Type type
59-
? (ITestAccessor?)Activator.CreateInstance(
60-
typeof(TestAccessor<>).MakeGenericType(type),
61-
s_nullObjectParam)
62-
: (ITestAccessor?)Activator.CreateInstance(
63-
typeof(TestAccessor<>).MakeGenericType(instanceOrType.GetType()),
64-
instanceOrType);
25+
/// <summary>
26+
/// Extension that creates a generic internals test accessor for a
27+
/// given instance or Type class (if only accessing statics).
28+
/// </summary>
29+
/// <remarks>
30+
/// <para>
31+
/// Use <see cref="ITestAccessor.CreateDelegate">CreateDelegate</see> to deal with methods that take spans or
32+
/// other ref structs. For other members, use the dynamic accessor:
33+
/// </para>
34+
/// <code>
35+
/// <![CDATA[
36+
/// Version version = new Version(4, 1);
37+
/// Assert.Equal(4, version.TestAccessor.Dynamic._Major));
38+
///
39+
/// // Or
40+
///
41+
/// dynamic accessor = version.TestAccessor.Dynamic;
42+
/// Assert.Equal(4, accessor._Major));
43+
///
44+
/// // Or
45+
///
46+
/// Version version2 = new Version("4.1");
47+
/// dynamic accessor = typeof(Version).TestAccessor.Dynamic;
48+
/// Assert.Equal(version2, accessor.Parse("4.1")));
49+
/// ]]>
50+
/// </code>
51+
/// </remarks>
52+
public ITestAccessor TestAccessor
53+
{
54+
get
55+
{
56+
ITestAccessor? testAccessor = instanceOrType is Type type
57+
? (ITestAccessor?)Activator.CreateInstance(
58+
typeof(TestAccessor<>).MakeGenericType(type),
59+
s_nullObjectParam)
60+
: (ITestAccessor?)Activator.CreateInstance(
61+
typeof(TestAccessor<>).MakeGenericType(instanceOrType.GetType()),
62+
instanceOrType);
6563

66-
return testAccessor
67-
?? throw new ArgumentException("Cannot create TestAccessor for Nullable<T> instances with no value.");
64+
return testAccessor
65+
?? throw new ArgumentException("Cannot create TestAccessor for Nullable<T> instances with no value.");
66+
}
67+
}
6868
}
6969
}

src/Microsoft.VisualBasic.Forms/tests/UnitTests/System/Windows/Forms/FileLogTraceListenerTests.vb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ Namespace Microsoft.VisualBasic.Forms.Tests
7373
listener.TraceOutputOptions = TraceOptions.Callstack
7474
listener.TraceOutputOptions.Should.Be(TraceOptions.Callstack)
7575

76-
CStr(listener.TestAccessor().Dynamic.HostName).Should.NotBeEmpty()
76+
CStr(TestAccessors.get_TestAccessor(listener).Dynamic.HostName).Should.NotBeEmpty()
7777

78-
Dim listenerStream As FileLogTraceListener.ReferencedStream = CType(listener.TestAccessor().Dynamic.ListenerStream, FileLogTraceListener.ReferencedStream)
78+
Dim listenerStream As FileLogTraceListener.ReferencedStream = CType(TestAccessors.get_TestAccessor(listener).Dynamic.ListenerStream, FileLogTraceListener.ReferencedStream)
7979
listenerStream.Should.NotBeNull()
8080
listenerStream.IsInUse.Should.BeTrue()
8181
listenerStream.FileSize.Should.Be(0)

src/Microsoft.VisualBasic.Forms/tests/UnitTests/System/Windows/Forms/InteractionTests.vb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ Namespace Microsoft.VisualBasic.Forms.Tests
1919
Dim xPos As Integer = -1
2020
Dim yPos As Integer = -1
2121
Dim inputHandler As New InputBoxHandler(prompt, title, defaultResponse, xPos, yPos, ParentWindow:=Nothing)
22-
CType(inputHandler.TestAccessor.Dynamic()._prompt, String).Should.Be(prompt)
23-
CType(inputHandler.TestAccessor.Dynamic()._title, String).Should.Be(title)
24-
CType(inputHandler.TestAccessor.Dynamic()._defaultResponse, String).Should.Be(defaultResponse)
25-
CType(inputHandler.TestAccessor.Dynamic()._xPos, String).Should.Be(xPos)
26-
CType(inputHandler.TestAccessor.Dynamic()._yPos, String).Should.Be(yPos)
27-
CType(inputHandler.TestAccessor.Dynamic()._parentWindow, IWin32Window).Should.Be(Nothing)
22+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._prompt, String).Should.Be(prompt)
23+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._title, String).Should.Be(title)
24+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._defaultResponse, String).Should.Be(defaultResponse)
25+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._xPos, String).Should.Be(xPos)
26+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._yPos, String).Should.Be(yPos)
27+
CType(TestAccessors.get_TestAccessor(inputHandler).Dynamic()._parentWindow, IWin32Window).Should.Be(Nothing)
2828
inputHandler.Exception.Should.BeNull()
2929
inputHandler.Result.Should.BeNull()
3030
End Sub

src/Microsoft.VisualBasic/tests/UnitTests/Microsoft/VisualBasic/ApplicationServices/SingleInstanceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private static dynamic GetTestHelper()
3535
{
3636
var assembly = typeof(WindowsFormsApplicationBase).Assembly;
3737
var type = assembly.GetType("Microsoft.VisualBasic.ApplicationServices.SingleInstanceHelpers");
38-
return type.TestAccessor().Dynamic;
38+
return type.TestAccessor.Dynamic;
3939
}
4040

4141
private bool TryCreatePipeServer(string pipeName, out NamedPipeServerStream pipeServer)

src/Microsoft.VisualBasic/tests/UnitTests/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBaseTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class WindowsFormsApplicationBaseTests
1111
{
1212
private static string GetAppID(Assembly assembly)
1313
{
14-
var testAccessor = typeof(WindowsFormsApplicationBase).TestAccessor();
14+
var testAccessor = typeof(WindowsFormsApplicationBase).TestAccessor;
1515
return testAccessor.Dynamic.GetApplicationInstanceID(assembly);
1616
}
1717

src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void ReadStringFromHGLOBAL_InvalidHGLOBAL_Throws(bool unicode)
120120

121121
Action action = () =>
122122
{
123-
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(HGLOBAL.Null, unicode);
123+
string result = type.TestAccessor.Dynamic.ReadStringFromHGLOBAL(HGLOBAL.Null, unicode);
124124
};
125125

126126
action.Should().Throw<Win32Exception>().And.HResult.Should().Be((int)HRESULT.E_FAIL);
@@ -145,7 +145,7 @@ public void ReadStringFromHGLOBAL_NoTerminator_ReturnsEmptyString(bool unicode)
145145
span.Fill(0x20);
146146
}
147147

148-
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(global, unicode);
148+
string result = type.TestAccessor.Dynamic.ReadStringFromHGLOBAL(global, unicode);
149149
result.Should().BeEmpty();
150150
}
151151
finally
@@ -173,7 +173,7 @@ public void ReadStringFromHGLOBAL_Terminator_ReturnsString(bool unicode)
173173
span[..^2].Fill(0x20);
174174
}
175175

176-
string result = type.TestAccessor().Dynamic.ReadStringFromHGLOBAL(global, unicode);
176+
string result = type.TestAccessor.Dynamic.ReadStringFromHGLOBAL(global, unicode);
177177
result.Should().NotBeEmpty();
178178
}
179179
finally

src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/ProjectFileReaderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace System.Windows.Forms.Analyzers.Tests;
1414
public class ProjectFileReaderTests
1515
{
1616
public static readonly char s_separator = CultureInfo.CurrentCulture.TextInfo.ListSeparator[0];
17-
private static readonly dynamic s_static = typeof(ProjectFileReader).TestAccessor().Dynamic;
17+
private static readonly dynamic s_static = typeof(ProjectFileReader).TestAccessor.Dynamic;
1818
private readonly ITestOutputHelper _output;
1919

2020
private static bool TryReadBool(AnalyzerConfigOptionsProvider configOptions, string propertyName, bool defaultValue, out bool value, out Diagnostic? diagnostic)

0 commit comments

Comments
 (0)