-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
FileSystemWatcherTest.cs
583 lines (509 loc) · 29.6 KB
/
FileSystemWatcherTest.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Xunit;
using Xunit.Sdk;
using Xunit.Abstractions;
using System.Linq;
namespace System.IO.Tests
{
public abstract partial class FileSystemWatcherTest : FileCleanupTestBase
{
// Events are reported asynchronously by the OS, so allow an amount of time for
// them to arrive before testing an assertion. If we expect an event to occur,
// we can wait for it for a relatively long time, as if it doesn't arrive, we're
// going to fail the test. If we don't expect an event to occur, then we need
// to keep the timeout short, as in a successful run we'll end up waiting for
// the entire timeout specified.
public const int WaitForExpectedEventTimeout = 500; // ms to wait for an event to happen
public const int LongWaitTimeout = 50000; // ms to wait for an event that takes a longer time than the average operation
public const int SubsequentExpectedWait = 10; // ms to wait for checks that occur after the first.
public const int WaitForExpectedEventTimeout_NoRetry = 3000;// ms to wait for an event that isn't surrounded by a retry.
public const int WaitForUnexpectedEventTimeout = 150; // ms to wait for a non-expected event.
public const int DefaultAttemptsForExpectedEvent = 3; // Number of times an expected event should be retried if failing.
public const int DefaultAttemptsForUnExpectedEvent = 2; // Number of times an unexpected event should be retried if failing.
public const int RetryDelayMilliseconds = 500; // ms to wait when retrying after failure
/// <summary>
/// Watches the Changed WatcherChangeType and unblocks the returned AutoResetEvent when a
/// Changed event is thrown by the watcher.
/// </summary>
public static (AutoResetEvent EventOccurred, FileSystemEventHandler Handler) WatchChanged(FileSystemWatcher watcher, string[] expectedPaths = null)
{
AutoResetEvent eventOccurred = new AutoResetEvent(false);
FileSystemEventHandler changeHandler = (o, e) =>
{
Assert.Equal(WatcherChangeTypes.Changed, e.ChangeType);
if (expectedPaths == null || expectedPaths.Contains(e.FullPath))
{
eventOccurred.Set();
}
};
watcher.Changed += changeHandler;
return (eventOccurred, changeHandler);
}
/// <summary>
/// Watches the Created WatcherChangeType and unblocks the returned AutoResetEvent when a
/// Created event is thrown by the watcher.
/// </summary>
public static (AutoResetEvent EventOccurred, FileSystemEventHandler Handler) WatchCreated(FileSystemWatcher watcher, string[] expectedPaths = null, ITestOutputHelper _output = null)
{
AutoResetEvent eventOccurred = new AutoResetEvent(false);
FileSystemEventHandler handler = (o, e) =>
{
if (e.ChangeType != WatcherChangeTypes.Created)
{
_output?.WriteLine("Unexpected event {0} while waiting for {1}", e.ChangeType, WatcherChangeTypes.Created);
Assert.Equal(WatcherChangeTypes.Created, e.ChangeType);
}
Assert.Equal(WatcherChangeTypes.Created, e.ChangeType);
if (expectedPaths == null || expectedPaths.Contains(e.FullPath))
{
eventOccurred.Set();
}
};
watcher.Created += handler;
return (eventOccurred, handler);
}
/// <summary>
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
/// Renamed event is thrown by the watcher.
/// </summary>
public static (AutoResetEvent EventOccurred, FileSystemEventHandler Handler) WatchDeleted(FileSystemWatcher watcher, string[] expectedPaths = null, ITestOutputHelper _output = null)
{
AutoResetEvent eventOccurred = new AutoResetEvent(false);
FileSystemEventHandler handler = (o, e) =>
{
if (e.ChangeType != WatcherChangeTypes.Deleted)
{
_output?.WriteLine("Unexpected event {0} while waiting for {1}", e.ChangeType, WatcherChangeTypes.Deleted);
Assert.Equal(WatcherChangeTypes.Deleted, e.ChangeType);
}
if (expectedPaths == null || expectedPaths.Contains(e.FullPath))
{
eventOccurred.Set();
}
};
watcher.Deleted += handler;
return (eventOccurred, handler);
}
/// <summary>
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
/// Renamed event is thrown by the watcher.
/// </summary>
public static (AutoResetEvent EventOccurred, RenamedEventHandler Handler) WatchRenamed(FileSystemWatcher watcher, string[] expectedPaths = null, ITestOutputHelper _output = null)
{
AutoResetEvent eventOccurred = new AutoResetEvent(false);
RenamedEventHandler handler = (o, e) =>
{
if (e.ChangeType != WatcherChangeTypes.Renamed)
{
_output?.WriteLine("Unexpected event {0} while waiting for {1}", e.ChangeType, WatcherChangeTypes.Renamed);
Assert.Equal(WatcherChangeTypes.Renamed, e.ChangeType);
}
if (expectedPaths == null || expectedPaths.Contains(e.FullPath))
{
eventOccurred.Set();
}
};
watcher.Renamed += handler;
return (eventOccurred, handler);
}
/// <summary>
/// Asserts that the given handle will be signaled within the default timeout.
/// </summary>
public static void ExpectEvent(WaitHandle eventOccurred, string eventName_NoRetry)
{
string message = string.Format("Didn't observe a {0} event within {1}ms", eventName_NoRetry, WaitForExpectedEventTimeout_NoRetry);
Assert.True(eventOccurred.WaitOne(WaitForExpectedEventTimeout_NoRetry), message);
}
/// <summary>
/// Does verification that the given watcher will throw exactly/only the events in "expectedEvents" when
/// "action" is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="expectedEvents">All of the events that are expected to be raised by this action</param>
/// <param name="action">The Action that will trigger events.</param>
/// <param name="cleanup">Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
public static void ExpectEvent(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, Action cleanup = null)
{
ExpectEvent(watcher, expectedEvents, action, cleanup, (string[])null);
}
/// <summary>
/// Does verification that the given watcher will throw exactly/only the events in "expectedEvents" when
/// "action" is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="expectedEvents">All of the events that are expected to be raised by this action</param>
/// <param name="action">The Action that will trigger events.</param>
/// <param name="cleanup">Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="expectedPath">Optional. Adds path verification to all expected events.</param>
/// <param name="attempts">Optional. Number of times the test should be executed if it's failing.</param>
public static void ExpectEvent(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, Action cleanup = null, string expectedPath = null, int attempts = DefaultAttemptsForExpectedEvent, int timeout = WaitForExpectedEventTimeout)
{
ExpectEvent(watcher, expectedEvents, action, cleanup, expectedPath == null ? null : new string[] { expectedPath }, attempts, timeout);
}
/// <summary>
/// Does verification that the given watcher will throw exactly/only the events in "expectedEvents" when
/// "action" is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="expectedEvents">All of the events that are expected to be raised by this action</param>
/// <param name="action">The Action that will trigger events.</param>
/// <param name="cleanup">Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="expectedPath">Optional. Adds path verification to all expected events.</param>
/// <param name="attempts">Optional. Number of times the test should be executed if it's failing.</param>
public static void ExpectEvent(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, Action cleanup = null, string[] expectedPaths = null, int attempts = DefaultAttemptsForExpectedEvent, int timeout = WaitForExpectedEventTimeout)
{
int attemptsCompleted = 0;
bool result = false;
FileSystemWatcher newWatcher = watcher;
while (!result && attemptsCompleted++ < attempts)
{
if (attemptsCompleted > 1)
{
// Re-create the watcher to get a clean iteration.
newWatcher = RecreateWatcher(newWatcher);
// Most intermittent failures in FSW are caused by either a shortage of resources (e.g. inotify instances)
// or by insufficient time to execute (e.g. CI gets bogged down). Immediately re-running a failed test
// won't resolve the first issue, so we wait a little while hoping that things clear up for the next run.
Thread.Sleep(RetryDelayMilliseconds);
}
result = ExecuteAndVerifyEvents(newWatcher, expectedEvents, action, attemptsCompleted == attempts, expectedPaths, timeout);
if (cleanup != null)
cleanup();
}
}
// Pasted from RetryHelper.cs in order to force FSW tests to log retries to the Helix console.
// We don't want to do that for tests in general.
// Once we've gotten enough data, delete this and go back to the regular RetryHelper.
private static readonly Func<int, int> s_defaultBackoffFunc = i => Math.Min(i * 100, 60_000);
private static readonly Predicate<Exception> s_defaultRetryWhenFunc = _ => true;
private static readonly bool s_debug = Environment.GetEnvironmentVariable("DEBUG_RETRYHELPER") == "1";
/// <summary>Executes the <paramref name="test"/> action up to a maximum of <paramref name="maxAttempts"/> times.</summary>
/// <param name="maxAttempts">The maximum number of times to invoke <paramref name="test"/>.</param>
/// <param name="test">The test to invoke.</param>
/// <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>
/// <param name="retryWhen">Invoked to select the exceptions to retry on. If not set, any exception will trigger a retry.</param>
public static void Execute(Action test, int maxAttempts = 5, Func<int, int> backoffFunc = null, Predicate<Exception> retryWhen = null, [CallerMemberName] string? testName = null)
{
// Validate arguments
if (maxAttempts < 1)
{
throw new ArgumentOutOfRangeException(nameof(maxAttempts));
}
if (test == null)
{
throw new ArgumentNullException(nameof(test));
}
retryWhen ??= s_defaultRetryWhenFunc;
// Execute the test until it either passes or we run it maxAttempts times
var exceptions = new List<Exception>();
for (int i = 1; i <= maxAttempts; i++)
{
Exception lastException;
try
{
test();
return;
}
catch (Exception e) when (retryWhen(e))
{
lastException = e;
exceptions.Add(e);
if (i == maxAttempts)
{
throw new AggregateException(exceptions);
}
}
if (PlatformDetection.IsInHelix || s_debug)
{
// Dump into the console output so we can mine it
Console.WriteLine($"RetryHelper: retrying {testName} {i}th time of {maxAttempts}: got {lastException.Message}");
}
if (s_debug)
{
Debug.WriteLine($"RetryHelper: retrying {testName} {i}th time of {maxAttempts}: got {lastException.Message}");
}
Thread.Sleep((backoffFunc ?? s_defaultBackoffFunc)(i));
}
}
/// <summary>
/// Does verification that the given watcher will not throw exactly/only the events in "expectedEvents" when
/// "action" is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="unExpectedEvents">All of the events that are expected to be raised by this action</param>
/// <param name="action">The Action that will trigger events.</param>
/// <param name="cleanup">Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="expectedPath">Optional. Adds path verification to all expected events.</param>
public static void ExpectNoEvent(FileSystemWatcher watcher, WatcherChangeTypes unExpectedEvents, Action action, Action cleanup = null, string expectedPath = null, int timeout = WaitForExpectedEventTimeout)
{
bool result = ExecuteAndVerifyEvents(watcher, unExpectedEvents, action, false, expectedPath == null ? null : new string[] { expectedPath }, timeout);
Assert.False(result, "Expected Event occurred");
if (cleanup != null)
cleanup();
}
/// <summary>
/// Helper for the ExpectEvent function.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="expectedEvents">All of the events that are expected to be raised by this action</param>
/// <param name="action">The Action that will trigger events.</param>
/// <param name="assertExpected">True if results should be asserted. Used if there is no retry.</param>
/// <param name="expectedPath"> Adds path verification to all expected events.</param>
/// <returns>True if the events raised correctly; else, false.</returns>
public static bool ExecuteAndVerifyEvents(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, bool assertExpected, string[] expectedPaths, int timeout)
{
bool result = true, verifyChanged = true, verifyCreated = true, verifyDeleted = true, verifyRenamed = true;
(AutoResetEvent EventOccurred, FileSystemEventHandler Handler) changed = default, created = default, deleted = default;
(AutoResetEvent EventOccurred, RenamedEventHandler Handler) renamed = default;
if (verifyChanged = ((expectedEvents & WatcherChangeTypes.Changed) > 0))
changed = WatchChanged(watcher, expectedPaths);
if (verifyCreated = ((expectedEvents & WatcherChangeTypes.Created) > 0))
created = WatchCreated(watcher, expectedPaths);
if (verifyDeleted = ((expectedEvents & WatcherChangeTypes.Deleted) > 0))
deleted = WatchDeleted(watcher, expectedPaths);
if (verifyRenamed = ((expectedEvents & WatcherChangeTypes.Renamed) > 0))
renamed = WatchRenamed(watcher, expectedPaths);
watcher.EnableRaisingEvents = true;
action();
// Verify Changed
if (verifyChanged)
{
bool Changed_expected = ((expectedEvents & WatcherChangeTypes.Changed) > 0);
bool Changed_actual = changed.EventOccurred.WaitOne(timeout);
watcher.Changed -= changed.Handler;
result = Changed_expected == Changed_actual;
if (assertExpected)
Assert.True(Changed_expected == Changed_actual, "Changed event did not occur as expected");
}
// Verify Created
if (verifyCreated)
{
bool Created_expected = ((expectedEvents & WatcherChangeTypes.Created) > 0);
bool Created_actual = created.EventOccurred.WaitOne(verifyChanged ? SubsequentExpectedWait : timeout);
watcher.Created -= created.Handler;
result = result && Created_expected == Created_actual;
if (assertExpected)
Assert.True(Created_expected == Created_actual, "Created event did not occur as expected");
}
// Verify Deleted
if (verifyDeleted)
{
bool Deleted_expected = ((expectedEvents & WatcherChangeTypes.Deleted) > 0);
bool Deleted_actual = deleted.EventOccurred.WaitOne(verifyChanged || verifyCreated ? SubsequentExpectedWait : timeout);
watcher.Deleted -= deleted.Handler;
result = result && Deleted_expected == Deleted_actual;
if (assertExpected)
Assert.True(Deleted_expected == Deleted_actual, "Deleted event did not occur as expected");
}
// Verify Renamed
if (verifyRenamed)
{
bool Renamed_expected = ((expectedEvents & WatcherChangeTypes.Renamed) > 0);
bool Renamed_actual = renamed.EventOccurred.WaitOne(verifyChanged || verifyCreated || verifyDeleted ? SubsequentExpectedWait : timeout);
watcher.Renamed -= renamed.Handler;
result = result && Renamed_expected == Renamed_actual;
if (assertExpected)
Assert.True(Renamed_expected == Renamed_actual, "Renamed event did not occur as expected");
}
watcher.EnableRaisingEvents = false;
return result;
}
/// <summary>
/// Does verification that the given watcher will throw an Error when the given action is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="action">The Action that will trigger a failure.</param>
/// <param name="cleanup">Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="attempts">Optional. Number of times the test should be executed if it's failing.</param>
public static void ExpectError(FileSystemWatcher watcher, Action action, Action cleanup, int attempts = DefaultAttemptsForExpectedEvent)
{
string message = string.Format("Did not observe an error event within {0}ms and {1} attempts.", WaitForExpectedEventTimeout, attempts);
Assert.True(TryErrorEvent(watcher, action, cleanup, attempts, expected: true), message);
}
/// <summary>
/// Does verification that the given watcher will <b>not</b> throw an Error when the given action is executed.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="action">The Action that will not trigger a failure.</param>
/// <param name="cleanup">Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="attempts">Optional. Number of times the test should be executed if it's failing.</param>
public static void ExpectNoError(FileSystemWatcher watcher, Action action, Action cleanup, int attempts = DefaultAttemptsForUnExpectedEvent)
{
string message = string.Format("Should not observe an error event within {0}ms. Attempted {1} times and received the event each time.", WaitForExpectedEventTimeout, attempts);
Assert.False(TryErrorEvent(watcher, action, cleanup, attempts, expected: true), message);
}
/// <summary>
/// Helper method for the ExpectError/ExpectNoError functions.
/// </summary>
/// <param name="watcher">The FileSystemWatcher to test</param>
/// <param name="action">The Action to execute.</param>
/// <param name="cleanup">Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
/// <param name="attempts">Number of times the test should be executed if it's failing.</param>
/// <param name="expected">Whether it is expected that an error event will be arisen.</param>
/// <returns>True if an Error event was raised by the watcher when the given action was executed; else, false.</returns>
public static bool TryErrorEvent(FileSystemWatcher watcher, Action action, Action cleanup, int attempts, bool expected)
{
int attemptsCompleted = 0;
bool result = !expected;
while (result != expected && attemptsCompleted++ < attempts)
{
if (attemptsCompleted > 1)
{
// Re-create the watcher to get a clean iteration.
watcher = RecreateWatcher(watcher);
// Most intermittent failures in FSW are caused by either a shortage of resources (e.g. inotify instances)
// or by insufficient time to execute (e.g. CI gets bogged down). Immediately re-running a failed test
// won't resolve the first issue, so we wait a little while hoping that things clear up for the next run.
Thread.Sleep(500);
}
AutoResetEvent errorOccurred = new AutoResetEvent(false);
watcher.Error += (o, e) =>
{
errorOccurred.Set();
};
// Enable raising events but be careful with the possibility of the max user inotify instances being reached already.
if (attemptsCompleted <= attempts)
{
try
{
watcher.EnableRaisingEvents = true;
}
catch (IOException) // Max User INotify instances. Isn't the type of error we're checking for.
{
continue;
}
}
else
{
watcher.EnableRaisingEvents = true;
}
action();
result = errorOccurred.WaitOne(WaitForExpectedEventTimeout);
watcher.EnableRaisingEvents = false;
cleanup();
}
return result;
}
public static IEnumerable<object[]> FilterTypes()
{
foreach (NotifyFilters filter in Enum.GetValues(typeof(NotifyFilters)))
yield return new object[] { filter };
}
// Linux and OSX systems have less precise filtering systems than Windows, so most
// metadata filters are effectively equivalent to each other on those systems. For example
// there isn't a way to filter only LastWrite events on either system; setting
// Filters to LastWrite will allow events from attribute change, creation time
// change, size change, etc.
public const NotifyFilters LinuxFiltersForAttribute = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Security |
NotifyFilters.Size;
public const NotifyFilters LinuxFiltersForModify = NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Security |
NotifyFilters.Size;
public const NotifyFilters OSXFiltersForModify = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Size;
private static FileSystemWatcher RecreateWatcher(FileSystemWatcher watcher)
{
FileSystemWatcher newWatcher = new FileSystemWatcher()
{
IncludeSubdirectories = watcher.IncludeSubdirectories,
NotifyFilter = watcher.NotifyFilter,
Path = watcher.Path,
InternalBufferSize = watcher.InternalBufferSize,
SynchronizingObject = watcher.SynchronizingObject,
};
foreach (string filter in watcher.Filters)
{
newWatcher.Filters.Add(filter);
}
return newWatcher;
}
internal readonly struct FiredEvent
{
public FiredEvent(WatcherChangeTypes eventType, string dir1, string dir2 = "") => (EventType, Dir1, Dir2) = (eventType, dir1, dir2);
public readonly WatcherChangeTypes EventType;
public readonly string Dir1;
public readonly string Dir2;
public override bool Equals(object obj) => obj is FiredEvent evt && Equals(evt);
public bool Equals(FiredEvent other) => EventType == other.EventType &&
Dir1 == other.Dir1 &&
Dir2 == other.Dir2;
public override int GetHashCode() => EventType.GetHashCode() ^ Dir1.GetHashCode() ^ Dir2.GetHashCode();
public override string ToString() => $"{EventType} {Dir1} {Dir2}";
}
// Observe until an expected count of events is triggered, otherwise fail. Return all collected events.
internal static List<FiredEvent> ExpectEvents(FileSystemWatcher watcher, int expectedEvents, Action action)
{
using var eventsOccurred = new AutoResetEvent(false);
var eventsOrrures = 0;
var events = new List<FiredEvent>();
ErrorEventArgs error = null;
FileSystemEventHandler fileWatcherEvent = (_, e) => AddEvent(e.ChangeType, e.FullPath);
RenamedEventHandler renameWatcherEvent = (_, e) => AddEvent(e.ChangeType, e.FullPath, e.OldFullPath);
ErrorEventHandler errorHandler = (_, e) => error ??= e ?? new ErrorEventArgs(null);
watcher.Changed += fileWatcherEvent;
watcher.Created += fileWatcherEvent;
watcher.Deleted += fileWatcherEvent;
watcher.Renamed += renameWatcherEvent;
watcher.Error += errorHandler;
bool raisingEvent = watcher.EnableRaisingEvents;
watcher.EnableRaisingEvents = true;
try
{
action();
eventsOccurred.WaitOne(new TimeSpan(0, 0, 5));
}
finally
{
watcher.Changed -= fileWatcherEvent;
watcher.Created -= fileWatcherEvent;
watcher.Deleted -= fileWatcherEvent;
watcher.Renamed -= renameWatcherEvent;
watcher.Error -= errorHandler;
watcher.EnableRaisingEvents = raisingEvent;
}
if (error != null)
{
Assert.Fail($"Filewatcher error event triggered: { error.GetException()?.Message ?? "Unknow error" }");
}
return events;
void AddEvent(WatcherChangeTypes eventType, string dir1, string dir2 = "")
{
events.Add(new FiredEvent(eventType, dir1, dir2));
if (Interlocked.Increment(ref eventsOrrures) == expectedEvents)
{
eventsOccurred.Set();
}
}
}
internal class TestISynchronizeInvoke : ISynchronizeInvoke
{
public bool BeginInvoke_Called;
public Delegate ExpectedDelegate;
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
if (ExpectedDelegate != null)
Assert.Equal(ExpectedDelegate, method);
BeginInvoke_Called = true;
method.DynamicInvoke(args[0], args[1]);
return null;
}
public bool InvokeRequired => true;
public object EndInvoke(IAsyncResult result) => null;
public object Invoke(Delegate method, object[] args) => null;
}
}
}