Skip to content

Commit 6584f8a

Browse files
committed
User Story 37545: Create app context switch test helper
- Added a test helper to get/set app context switch values. - Updated existing tests to use the helper. - Added missing switches to LocalAppContextSwitches tests.
1 parent b8948f2 commit 6584f8a

File tree

8 files changed

+441
-169
lines changed

8 files changed

+441
-169
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
using Xunit;
6+
7+
namespace Microsoft.Data.SqlClient.Tests.Common;
8+
9+
// This class provides read/write access to LocalAppContextSwitches values
10+
// for the duration of a test. It is intended to be constructed at the start
11+
// of a test and disposed at the end. It captures the original values of
12+
// the switches and restores them when disposed.
13+
//
14+
// This follows the RAII pattern to ensure that the switches are always
15+
// restored, which is important for global state like LocalAppContextSwitches.
16+
//
17+
// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
18+
//
19+
public sealed class LocalAppContextSwitchesHelper : IDisposable
20+
{
21+
#region Public Types
22+
23+
// This enum is used to represent the state of a switch.
24+
//
25+
// It is a copy of the Tristate enum from LocalAppContextSwitches.
26+
//
27+
public enum Tristate : byte
28+
{
29+
NotInitialized = 0,
30+
False = 1,
31+
True = 2
32+
}
33+
34+
#endregion
35+
36+
#region Construction
37+
38+
// Construct to capture all existing switch values.
39+
//
40+
// Fails the test if any values cannot be captured.
41+
//
42+
public LocalAppContextSwitchesHelper()
43+
{
44+
// Acquire a handle to the LocalAppContextSwitches type.
45+
var assembly = typeof(SqlCommandBuilder).Assembly;
46+
var switchesType = assembly.GetType(
47+
"Microsoft.Data.SqlClient.LocalAppContextSwitches");
48+
if (switchesType == null)
49+
{
50+
Assert.Fail("Unable to find LocalAppContextSwitches type.");
51+
}
52+
53+
// A local helper to acquire a handle to a property.
54+
void InitProperty(string name, out PropertyInfo property)
55+
{
56+
var prop = switchesType.GetProperty(
57+
name, BindingFlags.Public | BindingFlags.Static);
58+
if (prop == null)
59+
{
60+
Assert.Fail($"Unable to find {name} property.");
61+
}
62+
property = prop;
63+
}
64+
65+
// Acquire handles to all of the public properties of
66+
// LocalAppContextSwitches.
67+
InitProperty(
68+
"LegacyRowVersionNullBehavior",
69+
out _legacyRowVersionNullBehaviorProperty);
70+
InitProperty(
71+
"SuppressInsecureTLSWarning",
72+
out _suppressInsecureTLSWarningProperty);
73+
InitProperty(
74+
"MakeReadAsyncBlocking",
75+
out _makeReadAsyncBlockingProperty);
76+
InitProperty(
77+
"UseMinimumLoginTimeout",
78+
out _useMinimumLoginTimeoutProperty);
79+
InitProperty(
80+
"LegacyVarTimeZeroScaleBehaviour",
81+
out _legacyVarTimeZeroScaleBehaviourProperty);
82+
InitProperty(
83+
"UseCompatibilityProcessSni",
84+
out _useCompatProcessSniProperty);
85+
InitProperty(
86+
"UseCompatibilityAsyncBehaviour",
87+
out _useCompatAsyncBehaviourProperty);
88+
#if NETFRAMEWORK
89+
InitProperty(
90+
"DisableTNIRByDefault",
91+
out _disableTNIRByDefaultProperty);
92+
#endif
93+
94+
// A local helper to capture the original value of a switch.
95+
void InitField(string name, out FieldInfo field, out Tristate value)
96+
{
97+
var fieldInfo =
98+
switchesType.GetField(
99+
name, BindingFlags.NonPublic | BindingFlags.Static);
100+
if (fieldInfo == null)
101+
{
102+
Assert.Fail($"Unable to find {name} field.");
103+
}
104+
field = fieldInfo;
105+
value = GetValue(field);
106+
}
107+
108+
// Capture the original value of each switch.
109+
InitField(
110+
"s_legacyRowVersionNullBehavior",
111+
out _legacyRowVersionNullBehaviorField,
112+
out _legacyRowVersionNullBehaviorOriginal);
113+
114+
InitField(
115+
"s_suppressInsecureTLSWarning",
116+
out _suppressInsecureTLSWarningField,
117+
out _suppressInsecureTLSWarningOriginal);
118+
119+
InitField(
120+
"s_makeReadAsyncBlocking",
121+
out _makeReadAsyncBlockingField,
122+
out _makeReadAsyncBlockingOriginal);
123+
124+
InitField(
125+
"s_useMinimumLoginTimeout",
126+
out _useMinimumLoginTimeoutField,
127+
out _useMinimumLoginTimeoutOriginal);
128+
129+
InitField(
130+
"s_legacyVarTimeZeroScaleBehaviour",
131+
out _legacyVarTimeZeroScaleBehaviourField,
132+
out _legacyVarTimeZeroScaleBehaviourOriginal);
133+
134+
InitField(
135+
"s_useCompatProcessSni",
136+
out _useCompatProcessSniField,
137+
out _useCompatProcessSniOriginal);
138+
139+
InitField(
140+
"s_useCompatAsyncBehaviour",
141+
out _useCompatAsyncBehaviourField,
142+
out _useCompatAsyncBehaviourOriginal);
143+
144+
#if NETFRAMEWORK
145+
InitField(
146+
switchesType,
147+
"s_disableTNIRByDefault",
148+
out _disableTNIRByDefaultField,
149+
out _disableTNIRByDefaultOriginal);
150+
#endif
151+
}
152+
153+
// Disposal restores all original switch values as a best effort.
154+
public void Dispose()
155+
{
156+
List<string> failedFields = new();
157+
158+
void RestoreField(FieldInfo field, Tristate value)
159+
{
160+
try
161+
{
162+
SetValue(field, value);
163+
}
164+
catch (Exception)
165+
{
166+
failedFields.Add(field.Name);
167+
}
168+
}
169+
170+
RestoreField(
171+
_legacyRowVersionNullBehaviorField,
172+
_legacyRowVersionNullBehaviorOriginal);
173+
RestoreField(
174+
_suppressInsecureTLSWarningField,
175+
_suppressInsecureTLSWarningOriginal);
176+
RestoreField(
177+
_makeReadAsyncBlockingField,
178+
_makeReadAsyncBlockingOriginal);
179+
RestoreField(
180+
_useMinimumLoginTimeoutField,
181+
_useMinimumLoginTimeoutOriginal);
182+
RestoreField(
183+
_legacyVarTimeZeroScaleBehaviourField,
184+
_legacyVarTimeZeroScaleBehaviourOriginal);
185+
RestoreField(
186+
_useCompatProcessSniField,
187+
_useCompatProcessSniOriginal);
188+
RestoreField(
189+
_useCompatAsyncBehaviourField,
190+
_useCompatAsyncBehaviourOriginal);
191+
#if NETFRAMEWORK
192+
RestoreField(
193+
_disableTNIRByDefaultField,
194+
_disableTNIRByDefaultOriginal);
195+
#endif
196+
if (failedFields.Count > 0)
197+
{
198+
Assert.Fail(
199+
$"Failed to restore the following fields: " +
200+
string.Join(", ", failedFields));
201+
}
202+
}
203+
204+
#endregion
205+
206+
#region Public Properties
207+
208+
// These properties expose the like-named LocalAppContextSwitches
209+
// properties.
210+
public bool LegacyRowVersionNullBehavior
211+
{
212+
get => (bool)_legacyRowVersionNullBehaviorProperty.GetValue(null);
213+
}
214+
public bool SuppressInsecureTLSWarning
215+
{
216+
get => (bool)_suppressInsecureTLSWarningProperty.GetValue(null);
217+
}
218+
public bool MakeReadAsyncBlocking
219+
{
220+
get => (bool)_makeReadAsyncBlockingProperty.GetValue(null);
221+
}
222+
public bool UseMinimumLoginTimeout
223+
{
224+
get => (bool)_useMinimumLoginTimeoutProperty.GetValue(null);
225+
}
226+
public bool LegacyVarTimeZeroScaleBehaviour
227+
{
228+
get => (bool)_legacyVarTimeZeroScaleBehaviourProperty.GetValue(null);
229+
}
230+
public bool UseCompatibilityProcessSni
231+
{
232+
get => (bool)_useCompatProcessSniProperty.GetValue(null);
233+
}
234+
public bool UseCompatibilityAsyncBehaviour
235+
{
236+
get => (bool)_useCompatAsyncBehaviourProperty.GetValue(null);
237+
}
238+
#if NETFRAMEWORK
239+
public bool DisableTNIRByDefault
240+
{
241+
get => (bool)_disableTNIRByDefaultProperty.GetValue(null);
242+
}
243+
#endif
244+
245+
// These properties get or set the like-named underlying switch field value.
246+
//
247+
// They all fail the test if the value cannot be retrieved or set.
248+
249+
public Tristate LegacyRowVersionNullBehaviorField
250+
{
251+
get => GetValue(_legacyRowVersionNullBehaviorField);
252+
set => SetValue(_legacyRowVersionNullBehaviorField, value);
253+
}
254+
255+
public Tristate SuppressInsecureTLSWarningField
256+
{
257+
get => GetValue(_suppressInsecureTLSWarningField);
258+
set => SetValue(_suppressInsecureTLSWarningField, value);
259+
}
260+
261+
public Tristate MakeReadAsyncBlockingField
262+
{
263+
get => GetValue(_makeReadAsyncBlockingField);
264+
set => SetValue(_makeReadAsyncBlockingField, value);
265+
}
266+
267+
public Tristate UseMinimumLoginTimeoutField
268+
{
269+
get => GetValue(_useMinimumLoginTimeoutField);
270+
set => SetValue(_useMinimumLoginTimeoutField, value);
271+
}
272+
273+
public Tristate LegacyVarTimeZeroScaleBehaviourField
274+
{
275+
get => GetValue(_legacyVarTimeZeroScaleBehaviourField);
276+
set => SetValue(_legacyVarTimeZeroScaleBehaviourField, value);
277+
}
278+
279+
public Tristate UseCompatProcessSniField
280+
{
281+
get => GetValue(_useCompatProcessSniField);
282+
set => SetValue(_useCompatProcessSniField, value);
283+
}
284+
285+
public Tristate UseCompatAsyncBehaviourField
286+
{
287+
get => GetValue(_useCompatAsyncBehaviourField);
288+
set => SetValue(_useCompatAsyncBehaviourField, value);
289+
}
290+
291+
#if NETFRAMEWORK
292+
public Tristate DisableTNIRByDefaultField
293+
{
294+
get => GetValue(_disableTNIRByDefaultField);
295+
set => SetValue(_disableTNIRByDefaultField, value);
296+
}
297+
#endif
298+
299+
#endregion
300+
301+
#region Private Helpers
302+
303+
private static Tristate GetValue(FieldInfo field)
304+
{
305+
var value = field.GetValue(null);
306+
if (value is null)
307+
{
308+
Assert.Fail($"Field {field.Name} has a null value.");
309+
}
310+
311+
return (Tristate)value;
312+
}
313+
314+
private static void SetValue(FieldInfo field, Tristate value)
315+
{
316+
field.SetValue(null, (byte)value);
317+
}
318+
319+
#endregion
320+
321+
#region Private Members
322+
323+
// These fields are used to expose LocalAppContextSwitches's properties.
324+
private readonly PropertyInfo _legacyRowVersionNullBehaviorProperty;
325+
private readonly PropertyInfo _suppressInsecureTLSWarningProperty;
326+
private readonly PropertyInfo _makeReadAsyncBlockingProperty;
327+
private readonly PropertyInfo _useMinimumLoginTimeoutProperty;
328+
private readonly PropertyInfo _legacyVarTimeZeroScaleBehaviourProperty;
329+
private readonly PropertyInfo _useCompatProcessSniProperty;
330+
private readonly PropertyInfo _useCompatAsyncBehaviourProperty;
331+
#if NETFRAMEWORK
332+
private readonly PropertyInfo _disableTNIRByDefaultProperty;
333+
#endif
334+
335+
// These fields are used to capture the original switch values.
336+
private readonly FieldInfo _legacyRowVersionNullBehaviorField;
337+
private readonly Tristate _legacyRowVersionNullBehaviorOriginal;
338+
private readonly FieldInfo _suppressInsecureTLSWarningField;
339+
private readonly Tristate _suppressInsecureTLSWarningOriginal;
340+
private readonly FieldInfo _makeReadAsyncBlockingField;
341+
private readonly Tristate _makeReadAsyncBlockingOriginal;
342+
private readonly FieldInfo _useMinimumLoginTimeoutField;
343+
private readonly Tristate _useMinimumLoginTimeoutOriginal;
344+
private readonly FieldInfo _legacyVarTimeZeroScaleBehaviourField;
345+
private readonly Tristate _legacyVarTimeZeroScaleBehaviourOriginal;
346+
private readonly FieldInfo _useCompatProcessSniField;
347+
private readonly Tristate _useCompatProcessSniOriginal;
348+
private readonly FieldInfo _useCompatAsyncBehaviourField;
349+
private readonly Tristate _useCompatAsyncBehaviourOriginal;
350+
#if NETFRAMEWORK
351+
private readonly FieldInfo _disableTNIRByDefaultField;
352+
private readonly Tristate _disableTNIRByDefaultOriginal;
353+
#endif
354+
355+
#endregion
356+
}

src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ namespace Microsoft.Data.SqlClient.Tests
1010
public class LocalAppContextSwitchesTests
1111
{
1212
[Theory]
13-
[InlineData("SuppressInsecureTLSWarning", false)]
1413
[InlineData("LegacyRowVersionNullBehavior", false)]
14+
[InlineData("SuppressInsecureTLSWarning", false)]
1515
[InlineData("MakeReadAsyncBlocking", false)]
1616
[InlineData("UseMinimumLoginTimeout", true)]
17+
[InlineData("LegacyVarTimeZeroScaleBehaviour", true)]
1718
[InlineData("UseCompatibilityProcessSni", false)]
19+
[InlineData("UseCompatibilityAsyncBehaviour", false)]
20+
#if NETFRAMEWORK
21+
[InlineData("DisableTNIRByDefault", false)]
22+
#endif
1823
public void DefaultSwitchValue(string property, bool expectedDefaultValue)
1924
{
2025
var switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");

src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<OutputPath>$(BinFolder)$(Configuration).$(Platform).$(AssemblyName)</OutputPath>
1111
<IsTestProject>true</IsTestProject>
1212
</PropertyGroup>
13+
<ItemGroup>
14+
<Compile Include="..\Common\LocalAppContextSwitchesHelper.cs" />
15+
</ItemGroup>
1316
<ItemGroup>
1417
<Compile Include="AlwaysEncryptedTests\ExceptionsAlgorithmErrors.cs" />
1518
<Compile Include="AlwaysEncryptedTests\ConnectionStringBuilderShould.cs" />

0 commit comments

Comments
 (0)