Skip to content

Commit a11266d

Browse files
authored
Add retries to some FSW tests (#68368)
1 parent 8d2196d commit a11266d

File tree

12 files changed

+296
-191
lines changed

12 files changed

+296
-191
lines changed

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs

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

44
using System.Diagnostics;
55
using System.IO;
6+
using System.Linq;
67
using System.Linq.Expressions;
78
using System.Security;
89
using System.Security.Authentication;
@@ -22,6 +23,8 @@ public static partial class PlatformDetection
2223
// do it in a way that failures don't cascade.
2324
//
2425

26+
public static readonly bool IsInHelix = Environment.GetEnvironmentVariables().Keys.Cast<string>().Where(key => key.StartsWith("HELIX")).Any();
27+
2528
public static bool IsNetCore => Environment.Version.Major >= 5 || RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase);
2629
public static bool IsMonoRuntime => Type.GetType("Mono.RuntimeStructs") != null;
2730
public static bool IsNotMonoRuntime => !IsMonoRuntime;

src/libraries/Common/tests/TestUtilities/System/RetryHelper.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Runtime.CompilerServices;
58
using System.Threading;
69
using System.Threading.Tasks;
710

@@ -11,13 +14,14 @@ public static partial class RetryHelper
1114
{
1215
private static readonly Func<int, int> s_defaultBackoffFunc = i => Math.Min(i * 100, 60_000);
1316
private static readonly Predicate<Exception> s_defaultRetryWhenFunc = _ => true;
17+
private static readonly bool s_debug = Environment.GetEnvironmentVariable("DEBUG_RETRYHELPER") == "1";
1418

1519
/// <summary>Executes the <paramref name="test"/> action up to a maximum of <paramref name="maxAttempts"/> times.</summary>
1620
/// <param name="maxAttempts">The maximum number of times to invoke <paramref name="test"/>.</param>
1721
/// <param name="test">The test to invoke.</param>
1822
/// <param name="backoffFunc">After a failure, invoked to determine how many milliseconds to wait before the next attempt. It's passed the number of iterations attempted.</param>
1923
/// <param name="retryWhen">Invoked to select the exceptions to retry on. If not set, any exception will trigger a retry.</param>
20-
public static void Execute(Action test, int maxAttempts = 5, Func<int, int> backoffFunc = null, Predicate<Exception> retryWhen = null)
24+
public static void Execute(Action test, int maxAttempts = 5, Func<int, int> backoffFunc = null, Predicate<Exception> retryWhen = null, [CallerMemberName] string? testName = null)
2125
{
2226
// Validate arguments
2327
if (maxAttempts < 1)
@@ -35,20 +39,29 @@ public static void Execute(Action test, int maxAttempts = 5, Func<int, int> back
3539
var exceptions = new List<Exception>();
3640
for (int i = 1; i <= maxAttempts; i++)
3741
{
42+
Exception lastException;
3843
try
3944
{
4045
test();
4146
return;
4247
}
4348
catch (Exception e) when (retryWhen(e))
4449
{
50+
lastException = e;
4551
exceptions.Add(e);
4652
if (i == maxAttempts)
4753
{
4854
throw new AggregateException(exceptions);
4955
}
5056
}
5157

58+
if (s_debug)
59+
{
60+
string diagnostic = $"RetryHelper: retrying {testName} {i}th time of {maxAttempts}: got {lastException.Message}";
61+
Console.WriteLine(diagnostic);
62+
Debug.WriteLine(diagnostic);
63+
}
64+
5265
Thread.Sleep((backoffFunc ?? s_defaultBackoffFunc)(i));
5366
}
5467
}
@@ -58,7 +71,7 @@ public static void Execute(Action test, int maxAttempts = 5, Func<int, int> back
5871
/// <param name="test">The test to invoke.</param>
5972
/// <param name="backoffFunc">After a failure, invoked to determine how many milliseconds to wait before the next attempt. It's passed the number of iterations attempted.</param>
6073
/// <param name="retryWhen">Invoked to select the exceptions to retry on. If not set, any exception will trigger a retry.</param>
61-
public static async Task ExecuteAsync(Func<Task> test, int maxAttempts = 5, Func<int, int> backoffFunc = null, Predicate<Exception> retryWhen = null)
74+
public static async Task ExecuteAsync(Func<Task> test, int maxAttempts = 5, Func<int, int> backoffFunc = null, Predicate<Exception> retryWhen = null, [CallerMemberName] string? testName = null)
6275
{
6376
// Validate arguments
6477
if (maxAttempts < 1)

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.Changed.cs

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

44
using System.Runtime.InteropServices;
55
using Xunit;
6+
using Xunit.Sdk;
67

78
namespace System.IO.Tests
89
{
@@ -11,15 +12,18 @@ public class Directory_Changed_Tests : FileSystemWatcherTest
1112
[Fact]
1213
public void FileSystemWatcher_Directory_Changed_LastWrite()
1314
{
14-
using (var testDirectory = new TempDirectory(GetTestFilePath()))
15-
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
16-
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(dir.Path)))
15+
FileSystemWatcherTest.Execute(() =>
1716
{
18-
Action action = () => Directory.SetLastWriteTime(dir.Path, DateTime.Now + TimeSpan.FromSeconds(10));
17+
using (var testDirectory = new TempDirectory(GetTestFilePath()))
18+
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
19+
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(dir.Path)))
20+
{
21+
Action action = () => Directory.SetLastWriteTime(dir.Path, DateTime.Now + TimeSpan.FromSeconds(10));
1922

20-
WatcherChangeTypes expected = WatcherChangeTypes.Changed;
21-
ExpectEvent(watcher, expected, action, expectedPath: dir.Path);
22-
}
23+
WatcherChangeTypes expected = WatcherChangeTypes.Changed;
24+
ExpectEvent(watcher, expected, action, expectedPath: dir.Path);
25+
}
26+
}, maxAttempts: DefaultAttemptsForExpectedEvent, backoffFunc: (iteration) => RetryDelayMilliseconds, retryWhen: e => e is XunitException);
2327
}
2428

2529
[Fact]

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.NotifyFilter.cs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Runtime.InteropServices;
67
using Xunit;
8+
using Xunit.Sdk;
79

810
namespace System.IO.Tests
911
{
@@ -21,27 +23,30 @@ private static partial uint SetSecurityInfoByHandle( string name, uint objectTyp
2123
[MemberData(nameof(FilterTypes))]
2224
public void FileSystemWatcher_Directory_NotifyFilter_Attributes(NotifyFilters filter)
2325
{
24-
using (var testDirectory = new TempDirectory(GetTestFilePath()))
25-
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
26-
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(dir.Path)))
26+
FileSystemWatcherTest.Execute(() =>
2727
{
28-
watcher.NotifyFilter = filter;
29-
var attributes = File.GetAttributes(dir.Path);
28+
using (var testDirectory = new TempDirectory(GetTestFilePath()))
29+
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
30+
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(dir.Path)))
31+
{
32+
watcher.NotifyFilter = filter;
33+
var attributes = File.GetAttributes(dir.Path);
3034

31-
Action action = () => File.SetAttributes(dir.Path, attributes | FileAttributes.ReadOnly);
32-
Action cleanup = () => File.SetAttributes(dir.Path, attributes);
35+
Action action = () => File.SetAttributes(dir.Path, attributes | FileAttributes.ReadOnly);
36+
Action cleanup = () => File.SetAttributes(dir.Path, attributes);
3337

34-
WatcherChangeTypes expected = 0;
35-
if (filter == NotifyFilters.Attributes)
36-
expected |= WatcherChangeTypes.Changed;
37-
else if (OperatingSystem.IsLinux() && ((filter & LinuxFiltersForAttribute) > 0))
38-
expected |= WatcherChangeTypes.Changed;
39-
else if (OperatingSystem.IsMacOS() && ((filter & OSXFiltersForModify) > 0))
40-
expected |= WatcherChangeTypes.Changed;
41-
else if (OperatingSystem.IsMacOS() && ((filter & NotifyFilters.Security) > 0))
42-
expected |= WatcherChangeTypes.Changed; // Attribute change on OSX is a ChangeOwner operation which passes the Security NotifyFilter.
43-
ExpectEvent(watcher, expected, action, cleanup, dir.Path);
44-
}
38+
WatcherChangeTypes expected = 0;
39+
if (filter == NotifyFilters.Attributes)
40+
expected |= WatcherChangeTypes.Changed;
41+
else if (OperatingSystem.IsLinux() && ((filter & LinuxFiltersForAttribute) > 0))
42+
expected |= WatcherChangeTypes.Changed;
43+
else if (OperatingSystem.IsMacOS() && ((filter & OSXFiltersForModify) > 0))
44+
expected |= WatcherChangeTypes.Changed;
45+
else if (OperatingSystem.IsMacOS() && ((filter & NotifyFilters.Security) > 0))
46+
expected |= WatcherChangeTypes.Changed; // Attribute change on OSX is a ChangeOwner operation which passes the Security NotifyFilter.
47+
ExpectEvent(watcher, expected, action, cleanup, dir.Path);
48+
}
49+
}, maxAttempts: DefaultAttemptsForExpectedEvent, backoffFunc: (iteration) => RetryDelayMilliseconds, retryWhen: e => e is XunitException);
4550
}
4651

4752
[Theory]

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Threading;
67
using System.Threading.Tasks;
78
using Xunit;
9+
using Xunit.Sdk;
810

911
namespace System.IO.Tests
1012
{
@@ -30,7 +32,7 @@ public void FileSystemWatcher_File_Create()
3032
[OuterLoop]
3133
public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent()
3234
{
33-
ExecuteWithRetry(() =>
35+
FileSystemWatcherTest.Execute(() =>
3436
{
3537
using (var testDirectory = new TempDirectory(GetTestFilePath()))
3638
using (var watcher = new FileSystemWatcher(testDirectory.Path))
@@ -62,7 +64,7 @@ public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent
6264
Assert.False(autoResetEvent.WaitOne(SubsequentExpectedWait));
6365
Assert.True(numberOfRaisedEvents == 1);
6466
}
65-
});
67+
}, DefaultAttemptsForExpectedEvent, (iteration) => RetryDelayMilliseconds);
6668
}
6769

6870
[Fact]
@@ -144,21 +146,24 @@ public void FileSystemWatcher_File_Create_SymLink()
144146
[Fact]
145147
public void FileSystemWatcher_File_Create_SynchronizingObject()
146148
{
147-
using (var testDirectory = new TempDirectory(GetTestFilePath()))
148-
using (var watcher = new FileSystemWatcher(testDirectory.Path))
149+
FileSystemWatcherTest.Execute(() =>
149150
{
150-
TestISynchronizeInvoke invoker = new TestISynchronizeInvoke();
151-
watcher.SynchronizingObject = invoker;
151+
using (var testDirectory = new TempDirectory(GetTestFilePath()))
152+
using (var watcher = new FileSystemWatcher(testDirectory.Path))
153+
{
154+
TestISynchronizeInvoke invoker = new TestISynchronizeInvoke();
155+
watcher.SynchronizingObject = invoker;
152156

153-
string fileName = Path.Combine(testDirectory.Path, "file");
154-
watcher.Filter = Path.GetFileName(fileName);
157+
string fileName = Path.Combine(testDirectory.Path, "file");
158+
watcher.Filter = Path.GetFileName(fileName);
155159

156-
Action action = () => File.Create(fileName).Dispose();
157-
Action cleanup = () => File.Delete(fileName);
160+
Action action = () => File.Create(fileName).Dispose();
161+
Action cleanup = () => File.Delete(fileName);
158162

159-
ExpectEvent(watcher, WatcherChangeTypes.Created, action, cleanup, fileName);
160-
Assert.True(invoker.BeginInvoke_Called);
161-
}
163+
ExpectEvent(watcher, WatcherChangeTypes.Created, action, cleanup, fileName);
164+
Assert.True(invoker.BeginInvoke_Called);
165+
}
166+
}, maxAttempts: DefaultAttemptsForExpectedEvent, backoffFunc: (iteration) => RetryDelayMilliseconds, retryWhen: e => e is XunitException);
162167
}
163168
}
164169
}

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Delete.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Threading;
66
using Xunit;
7+
using Xunit.Sdk;
78

89
namespace System.IO.Tests
910
{
@@ -91,19 +92,22 @@ public void FileSystemWatcher_File_Delete_DeepDirectoryStructure()
9192
[ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
9293
public void FileSystemWatcher_File_Delete_SymLink()
9394
{
94-
using (var testDirectory = new TempDirectory(GetTestFilePath()))
95-
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
96-
using (var temp = new TempFile(GetTestFilePath()))
97-
using (var watcher = new FileSystemWatcher(dir.Path, "*"))
95+
FileSystemWatcherTest.Execute(() =>
9896
{
99-
// Make the symlink in our path (to the temp file) and make sure an event is raised
100-
string symLinkPath = Path.Combine(dir.Path, GetRandomLinkName());
101-
Action action = () => File.Delete(symLinkPath);
102-
Action cleanup = () => Assert.True(MountHelper.CreateSymbolicLink(symLinkPath, temp.Path, false));
103-
cleanup();
104-
105-
ExpectEvent(watcher, WatcherChangeTypes.Deleted, action, cleanup, symLinkPath);
106-
}
97+
using (var testDirectory = new TempDirectory(GetTestFilePath()))
98+
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
99+
using (var temp = new TempFile(GetTestFilePath()))
100+
using (var watcher = new FileSystemWatcher(dir.Path, "*"))
101+
{
102+
// Make the symlink in our path (to the temp file) and make sure an event is raised
103+
string symLinkPath = Path.Combine(dir.Path, GetRandomLinkName());
104+
Action action = () => File.Delete(symLinkPath);
105+
Action cleanup = () => Assert.True(MountHelper.CreateSymbolicLink(symLinkPath, temp.Path, false));
106+
cleanup();
107+
108+
ExpectEvent(watcher, WatcherChangeTypes.Deleted, action, cleanup, symLinkPath);
109+
}
110+
}, maxAttempts: DefaultAttemptsForExpectedEvent, backoffFunc: (iteration) => RetryDelayMilliseconds, retryWhen: e => e is XunitException);
107111
}
108112

109113
[Fact]

src/libraries/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.NotifyFilter.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Runtime.InteropServices;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Xunit;
10+
using Xunit.Sdk;
911

1012
namespace System.IO.Tests
1113
{
@@ -51,23 +53,26 @@ public void FileSystemWatcher_File_NotifyFilter_Attributes(NotifyFilters filter)
5153
[MemberData(nameof(FilterTypes))]
5254
public void FileSystemWatcher_File_NotifyFilter_CreationTime(NotifyFilters filter)
5355
{
54-
using (var testDirectory = new TempDirectory(GetTestFilePath()))
55-
using (var file = new TempFile(Path.Combine(testDirectory.Path, "file")))
56-
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
56+
FileSystemWatcherTest.Execute(() =>
5757
{
58-
watcher.NotifyFilter = filter;
59-
Action action = () => File.SetCreationTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10));
58+
using (var testDirectory = new TempDirectory(GetTestFilePath()))
59+
using (var file = new TempFile(Path.Combine(testDirectory.Path, "file")))
60+
using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
61+
{
62+
watcher.NotifyFilter = filter;
63+
Action action = () => File.SetCreationTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10));
6064

61-
WatcherChangeTypes expected = 0;
62-
if (filter == NotifyFilters.CreationTime)
63-
expected |= WatcherChangeTypes.Changed;
64-
else if (OperatingSystem.IsLinux() && ((filter & LinuxFiltersForAttribute) > 0))
65-
expected |= WatcherChangeTypes.Changed;
66-
else if (OperatingSystem.IsMacOS() && ((filter & OSXFiltersForModify) > 0))
67-
expected |= WatcherChangeTypes.Changed;
65+
WatcherChangeTypes expected = 0;
66+
if (filter == NotifyFilters.CreationTime)
67+
expected |= WatcherChangeTypes.Changed;
68+
else if (OperatingSystem.IsLinux() && ((filter & LinuxFiltersForAttribute) > 0))
69+
expected |= WatcherChangeTypes.Changed;
70+
else if (OperatingSystem.IsMacOS() && ((filter & OSXFiltersForModify) > 0))
71+
expected |= WatcherChangeTypes.Changed;
6872

69-
ExpectEvent(watcher, expected, action, expectedPath: file.Path);
70-
}
73+
ExpectEvent(watcher, expected, action, expectedPath: file.Path);
74+
}
75+
}, maxAttempts: DefaultAttemptsForExpectedEvent, backoffFunc: (iteration) => RetryDelayMilliseconds, retryWhen: e => e is XunitException);
7176
}
7277

7378
[Theory]

0 commit comments

Comments
 (0)