Skip to content

Commit 8c154ec

Browse files
authored
Add async local override for build server detector (#678)
* Add async-local override for BuildServerDetector Introduce an AsyncLocal-backed override for BuildServerDetector.Detected so tests (and other async-scoped code) can set the detected value without leaking to other threads. The static initialization now stores the computed detection in a private 'detected' field, and the Detected property reads from overrideDetected.Value ?? detected and sets overrideDetected.Value. Added unit tests to verify the override persists in the async context and doesn't leak, and updated readme/source docs with a snippet explaining how to override Detected in tests. * Update Directory.Build.props
1 parent 354d7bc commit 8c154ec

File tree

5 files changed

+99
-3
lines changed

5 files changed

+99
-3
lines changed

readme.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ DiffEngine manages launching and cleanup of diff tools. It is designed to be use
4646
* [Closing a tool](#closing-a-tool)
4747
* [File type detection](#file-type-detection)
4848
* [BuildServerDetector](#buildserverdetector)
49+
* [Override in tests](#override-in-tests)
4950
* [AiCliDetector](#aiclidetector)
5051
* [Disable for a machine/process](#disable-for-a-machineprocess)
5152
* [Disable in code](#disable-in-code)
@@ -162,6 +163,50 @@ var isAppVeyor = BuildServerDetector.IsAppVeyor;
162163
<!-- endSnippet -->
163164

164165

166+
### Override in tests
167+
168+
`BuildServerDetector.Detected` can be set at test time. The value is stored in an `AsyncLocal`, so it is scoped to the current async context and does not leak to other threads or tests running in parallel.
169+
170+
<!-- snippet: BuildServerDetectorDetectedOverride -->
171+
<a id='snippet-BuildServerDetectorDetectedOverride'></a>
172+
```cs
173+
[Fact]
174+
public async Task SetDetectedPersistsInAsyncContext()
175+
{
176+
var original = BuildServerDetector.Detected;
177+
try
178+
{
179+
BuildServerDetector.Detected = true;
180+
Assert.True(BuildServerDetector.Detected);
181+
182+
await Task.Delay(1);
183+
184+
Assert.True(BuildServerDetector.Detected);
185+
}
186+
finally
187+
{
188+
BuildServerDetector.Detected = original;
189+
}
190+
}
191+
192+
[Fact]
193+
public async Task SetDetectedDoesNotLeakToOtherContexts()
194+
{
195+
var parentValue = BuildServerDetector.Detected;
196+
197+
await Task.Run(() =>
198+
{
199+
BuildServerDetector.Detected = true;
200+
Assert.True(BuildServerDetector.Detected);
201+
});
202+
203+
Assert.Equal(parentValue, BuildServerDetector.Detected);
204+
}
205+
```
206+
<sup><a href='/src/DiffEngine.Tests/BuildServerDetectorTest.cs#L28-L63' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildServerDetectorDetectedOverride' title='Start of snippet'>anchor</a></sup>
207+
<!-- endSnippet -->
208+
209+
165210
## AiCliDetector
166211

167212
`AiCliDetector.Detected` returns true if the current code is running in an AI-powered CLI environment.

readme.source.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ There are also individual properties to check for each specific build system
8484
snippet: BuildServerDetectorProps
8585

8686

87+
### Override in tests
88+
89+
`BuildServerDetector.Detected` can be set at test time. The value is stored in an `AsyncLocal`, so it is scoped to the current async context and does not leak to other threads or tests running in parallel.
90+
91+
snippet: BuildServerDetectorDetectedOverride
92+
93+
8794
## AiCliDetector
8895

8996
`AiCliDetector.Detected` returns true if the current code is running in an AI-powered CLI environment.

src/DiffEngine.Tests/BuildServerDetectorTest.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,41 @@ public void Props()
2424

2525
// ReSharper restore UnusedVariable
2626
}
27+
28+
#region BuildServerDetectorDetectedOverride
29+
30+
[Fact]
31+
public async Task SetDetectedPersistsInAsyncContext()
32+
{
33+
var original = BuildServerDetector.Detected;
34+
try
35+
{
36+
BuildServerDetector.Detected = true;
37+
Assert.True(BuildServerDetector.Detected);
38+
39+
await Task.Delay(1);
40+
41+
Assert.True(BuildServerDetector.Detected);
42+
}
43+
finally
44+
{
45+
BuildServerDetector.Detected = original;
46+
}
47+
}
48+
49+
[Fact]
50+
public async Task SetDetectedDoesNotLeakToOtherContexts()
51+
{
52+
var parentValue = BuildServerDetector.Detected;
53+
54+
await Task.Run(() =>
55+
{
56+
BuildServerDetector.Detected = true;
57+
Assert.True(BuildServerDetector.Detected);
58+
});
59+
60+
Assert.Equal(parentValue, BuildServerDetector.Detected);
61+
}
62+
63+
#endregion
2764
}

src/DiffEngine/BuildServerDetector.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ static BuildServerDetector()
5252
// https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#access-variables-through-the-environment
5353
IsAzureDevops = ValueEquals(variables, "TF_BUILD", "True");
5454

55-
Detected = IsTravis ||
55+
detected = IsTravis ||
5656
IsJenkins ||
5757
IsGithubAction ||
5858
IsAzureDevops ||
@@ -65,6 +65,9 @@ static BuildServerDetector()
6565
IsAppVeyor;
6666
}
6767

68+
static bool detected;
69+
static AsyncLocal<bool?> overrideDetected = new();
70+
6871
static bool ValueEquals(IDictionary variables, string key, string value)
6972
{
7073
var variable = variables[key];
@@ -98,5 +101,9 @@ static bool ValueEquals(IDictionary variables, string key, string value)
98101

99102
public static bool IsJenkins { get; }
100103

101-
public static bool Detected { get; set; }
104+
public static bool Detected
105+
{
106+
get => overrideDetected.Value ?? detected;
107+
set => overrideDetected.Value = value;
108+
}
102109
}

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33
<PropertyGroup>
44
<NoWarn>CS1591;CS0649;NU1608;NU1109</NoWarn>
5-
<Version>18.3.0</Version>
5+
<Version>18.4.0</Version>
66
<AssemblyVersion>1.0.0</AssemblyVersion>
77
<PackageTags>Testing, Snapshot, Diff, Compare</PackageTags>
88
<Description>Launches diff tools based on file extensions. Designed to be consumed by snapshot testing libraries.</Description>

0 commit comments

Comments
 (0)