Skip to content

Commit c10ae2a

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 1eabf80 commit c10ae2a

File tree

8 files changed

+439
-169
lines changed

8 files changed

+439
-169
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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+
"s_disableTNIRByDefault",
147+
out _disableTNIRByDefaultField,
148+
out _disableTNIRByDefaultOriginal);
149+
#endif
150+
}
151+
152+
// Disposal restores all original switch values as a best effort.
153+
public void Dispose()
154+
{
155+
List<string> failedFields = new();
156+
157+
void RestoreField(FieldInfo field, Tristate value)
158+
{
159+
try
160+
{
161+
SetValue(field, value);
162+
}
163+
catch (Exception)
164+
{
165+
failedFields.Add(field.Name);
166+
}
167+
}
168+
169+
RestoreField(
170+
_legacyRowVersionNullBehaviorField,
171+
_legacyRowVersionNullBehaviorOriginal);
172+
RestoreField(
173+
_suppressInsecureTLSWarningField,
174+
_suppressInsecureTLSWarningOriginal);
175+
RestoreField(
176+
_makeReadAsyncBlockingField,
177+
_makeReadAsyncBlockingOriginal);
178+
RestoreField(
179+
_useMinimumLoginTimeoutField,
180+
_useMinimumLoginTimeoutOriginal);
181+
RestoreField(
182+
_legacyVarTimeZeroScaleBehaviourField,
183+
_legacyVarTimeZeroScaleBehaviourOriginal);
184+
RestoreField(
185+
_useCompatProcessSniField,
186+
_useCompatProcessSniOriginal);
187+
RestoreField(
188+
_useCompatAsyncBehaviourField,
189+
_useCompatAsyncBehaviourOriginal);
190+
#if NETFRAMEWORK
191+
RestoreField(
192+
_disableTNIRByDefaultField,
193+
_disableTNIRByDefaultOriginal);
194+
#endif
195+
if (failedFields.Count > 0)
196+
{
197+
Assert.Fail(
198+
$"Failed to restore the following fields: " +
199+
string.Join(", ", failedFields));
200+
}
201+
}
202+
203+
#endregion
204+
205+
#region Public Properties
206+
207+
// These properties expose the like-named LocalAppContextSwitches
208+
// properties.
209+
public bool LegacyRowVersionNullBehavior
210+
{
211+
get => (bool)_legacyRowVersionNullBehaviorProperty.GetValue(null);
212+
}
213+
public bool SuppressInsecureTLSWarning
214+
{
215+
get => (bool)_suppressInsecureTLSWarningProperty.GetValue(null);
216+
}
217+
public bool MakeReadAsyncBlocking
218+
{
219+
get => (bool)_makeReadAsyncBlockingProperty.GetValue(null);
220+
}
221+
public bool UseMinimumLoginTimeout
222+
{
223+
get => (bool)_useMinimumLoginTimeoutProperty.GetValue(null);
224+
}
225+
public bool LegacyVarTimeZeroScaleBehaviour
226+
{
227+
get => (bool)_legacyVarTimeZeroScaleBehaviourProperty.GetValue(null);
228+
}
229+
public bool UseCompatibilityProcessSni
230+
{
231+
get => (bool)_useCompatProcessSniProperty.GetValue(null);
232+
}
233+
public bool UseCompatibilityAsyncBehaviour
234+
{
235+
get => (bool)_useCompatAsyncBehaviourProperty.GetValue(null);
236+
}
237+
#if NETFRAMEWORK
238+
public bool DisableTNIRByDefault
239+
{
240+
get => (bool)_disableTNIRByDefaultProperty.GetValue(null);
241+
}
242+
#endif
243+
244+
// These properties get or set the like-named underlying switch field value.
245+
//
246+
// They all fail the test if the value cannot be retrieved or set.
247+
248+
public Tristate LegacyRowVersionNullBehaviorField
249+
{
250+
get => GetValue(_legacyRowVersionNullBehaviorField);
251+
set => SetValue(_legacyRowVersionNullBehaviorField, value);
252+
}
253+
254+
public Tristate SuppressInsecureTLSWarningField
255+
{
256+
get => GetValue(_suppressInsecureTLSWarningField);
257+
set => SetValue(_suppressInsecureTLSWarningField, value);
258+
}
259+
260+
public Tristate MakeReadAsyncBlockingField
261+
{
262+
get => GetValue(_makeReadAsyncBlockingField);
263+
set => SetValue(_makeReadAsyncBlockingField, value);
264+
}
265+
266+
public Tristate UseMinimumLoginTimeoutField
267+
{
268+
get => GetValue(_useMinimumLoginTimeoutField);
269+
set => SetValue(_useMinimumLoginTimeoutField, value);
270+
}
271+
272+
public Tristate LegacyVarTimeZeroScaleBehaviourField
273+
{
274+
get => GetValue(_legacyVarTimeZeroScaleBehaviourField);
275+
set => SetValue(_legacyVarTimeZeroScaleBehaviourField, value);
276+
}
277+
278+
public Tristate UseCompatProcessSniField
279+
{
280+
get => GetValue(_useCompatProcessSniField);
281+
set => SetValue(_useCompatProcessSniField, value);
282+
}
283+
284+
public Tristate UseCompatAsyncBehaviourField
285+
{
286+
get => GetValue(_useCompatAsyncBehaviourField);
287+
set => SetValue(_useCompatAsyncBehaviourField, value);
288+
}
289+
290+
#if NETFRAMEWORK
291+
public Tristate DisableTNIRByDefaultField
292+
{
293+
get => GetValue(_disableTNIRByDefaultField);
294+
set => SetValue(_disableTNIRByDefaultField, value);
295+
}
296+
#endif
297+
298+
#endregion
299+
300+
#region Private Helpers
301+
302+
private static Tristate GetValue(FieldInfo field)
303+
{
304+
var value = field.GetValue(null);
305+
if (value is null)
306+
{
307+
Assert.Fail($"Field {field.Name} has a null value.");
308+
}
309+
310+
return (Tristate)value;
311+
}
312+
313+
private static void SetValue(FieldInfo field, Tristate value)
314+
{
315+
field.SetValue(null, (byte)value);
316+
}
317+
318+
#endregion
319+
320+
#region Private Members
321+
322+
// These fields are used to expose LocalAppContextSwitches's properties.
323+
private readonly PropertyInfo _legacyRowVersionNullBehaviorProperty;
324+
private readonly PropertyInfo _suppressInsecureTLSWarningProperty;
325+
private readonly PropertyInfo _makeReadAsyncBlockingProperty;
326+
private readonly PropertyInfo _useMinimumLoginTimeoutProperty;
327+
private readonly PropertyInfo _legacyVarTimeZeroScaleBehaviourProperty;
328+
private readonly PropertyInfo _useCompatProcessSniProperty;
329+
private readonly PropertyInfo _useCompatAsyncBehaviourProperty;
330+
#if NETFRAMEWORK
331+
private readonly PropertyInfo _disableTNIRByDefaultProperty;
332+
#endif
333+
334+
// These fields are used to capture the original switch values.
335+
private readonly FieldInfo _legacyRowVersionNullBehaviorField;
336+
private readonly Tristate _legacyRowVersionNullBehaviorOriginal;
337+
private readonly FieldInfo _suppressInsecureTLSWarningField;
338+
private readonly Tristate _suppressInsecureTLSWarningOriginal;
339+
private readonly FieldInfo _makeReadAsyncBlockingField;
340+
private readonly Tristate _makeReadAsyncBlockingOriginal;
341+
private readonly FieldInfo _useMinimumLoginTimeoutField;
342+
private readonly Tristate _useMinimumLoginTimeoutOriginal;
343+
private readonly FieldInfo _legacyVarTimeZeroScaleBehaviourField;
344+
private readonly Tristate _legacyVarTimeZeroScaleBehaviourOriginal;
345+
private readonly FieldInfo _useCompatProcessSniField;
346+
private readonly Tristate _useCompatProcessSniOriginal;
347+
private readonly FieldInfo _useCompatAsyncBehaviourField;
348+
private readonly Tristate _useCompatAsyncBehaviourOriginal;
349+
#if NETFRAMEWORK
350+
private readonly FieldInfo _disableTNIRByDefaultField;
351+
private readonly Tristate _disableTNIRByDefaultOriginal;
352+
#endif
353+
354+
#endregion
355+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ namespace Microsoft.Data.SqlClient.Tests
1111
public class LocalAppContextSwitchesTests
1212
{
1313
[Theory]
14-
[InlineData("SuppressInsecureTLSWarning", false)]
1514
[InlineData("LegacyRowVersionNullBehavior", false)]
15+
[InlineData("SuppressInsecureTLSWarning", false)]
1616
[InlineData("MakeReadAsyncBlocking", false)]
1717
[InlineData("UseMinimumLoginTimeout", true)]
18+
[InlineData("LegacyVarTimeZeroScaleBehaviour", true)]
1819
[InlineData("UseCompatibilityProcessSni", false)]
1920
[InlineData("UseCompatibilityAsyncBehaviour", false)]
2021
[InlineData("UseConnectionPoolV2", false)]
22+
#if NETFRAMEWORK
23+
[InlineData("DisableTNIRByDefault", false)]
24+
#endif
2125
public void DefaultSwitchValue(string property, bool expectedDefaultValue)
2226
{
2327
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)