Add a polling watcher

This commit is contained in:
moozzyk 2016-03-28 16:47:18 -07:00 committed by Victor Hurdugaci
parent 46d4c6edcb
commit 8f1f3c0772
10 changed files with 848 additions and 40 deletions

View File

@ -0,0 +1,136 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
namespace Microsoft.DotNet.Watcher.Core.Internal
{
internal class DotnetFileWatcher : IFileSystemWatcher
{
private readonly Func<string, FileSystemWatcher> _watcherFactory;
private readonly string _watchedDirectory;
private FileSystemWatcher _fileSystemWatcher;
private readonly object _createLock = new object();
public DotnetFileWatcher(string watchedDirectory)
: this(watchedDirectory, DefaultWatcherFactory)
{
}
internal DotnetFileWatcher(string watchedDirectory, Func<string, FileSystemWatcher> fileSystemWatcherFactory)
{
if (string.IsNullOrEmpty(watchedDirectory))
{
throw new ArgumentNullException(nameof(watchedDirectory));
}
_watchedDirectory = watchedDirectory;
_watcherFactory = fileSystemWatcherFactory;
CreateFileSystemWatcher();
}
public event EventHandler<string> OnFileChange;
public event EventHandler OnError;
private static FileSystemWatcher DefaultWatcherFactory(string watchedDirectory)
{
if (string.IsNullOrEmpty(watchedDirectory))
{
throw new ArgumentNullException(nameof(watchedDirectory));
}
return new FileSystemWatcher(watchedDirectory);
}
private void WatcherErrorHandler(object sender, ErrorEventArgs e)
{
// Recreate the watcher
CreateFileSystemWatcher();
if (OnError != null)
{
OnError(this, null);
}
}
private void WatcherRenameHandler(object sender, RenamedEventArgs e)
{
NotifyChange(e.OldFullPath);
NotifyChange(e.FullPath);
if (Directory.Exists(e.FullPath))
{
foreach (var newLocation in Directory.EnumerateFileSystemEntries(e.FullPath, "*", SearchOption.AllDirectories))
{
// Calculated previous path of this moved item.
var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1));
NotifyChange(oldLocation);
NotifyChange(newLocation);
}
}
}
private void WatcherChangeHandler(object sender, FileSystemEventArgs e)
{
NotifyChange(e.FullPath);
}
private void NotifyChange(string fullPath)
{
if (OnFileChange != null)
{
// Only report file changes
OnFileChange(this, fullPath);
}
}
private void CreateFileSystemWatcher()
{
lock (_createLock)
{
bool enableEvents = false;
if (_fileSystemWatcher != null)
{
enableEvents = _fileSystemWatcher.EnableRaisingEvents;
_fileSystemWatcher.EnableRaisingEvents = false;
_fileSystemWatcher.Created -= WatcherChangeHandler;
_fileSystemWatcher.Deleted -= WatcherChangeHandler;
_fileSystemWatcher.Changed -= WatcherChangeHandler;
_fileSystemWatcher.Renamed -= WatcherRenameHandler;
_fileSystemWatcher.Error -= WatcherErrorHandler;
_fileSystemWatcher.Dispose();
}
_fileSystemWatcher = _watcherFactory(_watchedDirectory);
_fileSystemWatcher.IncludeSubdirectories = true;
_fileSystemWatcher.Created += WatcherChangeHandler;
_fileSystemWatcher.Deleted += WatcherChangeHandler;
_fileSystemWatcher.Changed += WatcherChangeHandler;
_fileSystemWatcher.Renamed += WatcherRenameHandler;
_fileSystemWatcher.Error += WatcherErrorHandler;
_fileSystemWatcher.EnableRaisingEvents = enableEvents;
}
}
public bool EnableRaisingEvents
{
get { return _fileSystemWatcher.EnableRaisingEvents; }
set { _fileSystemWatcher.EnableRaisingEvents = value; }
}
public void Dispose()
{
_fileSystemWatcher.Dispose();
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.DotNet.Watcher.Core.Internal
{
public static class FileWatcherFactory
{
public static IFileSystemWatcher CreateWatcher(string watchedDirectory)
{
var envVar = Environment.GetEnvironmentVariable("USE_POLLING_FILE_WATCHER");
var usePollingWatcher =
envVar != null &&
(envVar.Equals("1", StringComparison.OrdinalIgnoreCase) ||
envVar.Equals("true", StringComparison.OrdinalIgnoreCase));
return CreateWatcher(watchedDirectory, usePollingWatcher);
}
public static IFileSystemWatcher CreateWatcher(string watchedDirectory, bool usePollingWatcher)
{
return usePollingWatcher ?
new PollingFileWatcher(watchedDirectory) :
new DotnetFileWatcher(watchedDirectory) as IFileSystemWatcher;
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.DotNet.Watcher.Core.Internal
{
public interface IFileSystemWatcher : IDisposable
{
event EventHandler<string> OnFileChange;
event EventHandler OnError;
bool EnableRaisingEvents { get; set; }
}
}

View File

@ -0,0 +1,243 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace Microsoft.DotNet.Watcher.Core.Internal
{
internal class PollingFileWatcher : IFileSystemWatcher
{
// The minimum interval to rerun the scan
private static readonly TimeSpan _minRunInternal = TimeSpan.FromSeconds(.5);
private readonly DirectoryInfo _watchedDirectory;
private Dictionary<string, FileMeta> _knownEntities = new Dictionary<string, FileMeta>();
private Dictionary<string, FileMeta> _tempDictionary = new Dictionary<string, FileMeta>();
private HashSet<string> _changes = new HashSet<string>();
private Thread _pollingThread;
private bool _raiseEvents;
private bool _disposed;
public PollingFileWatcher(string watchedDirectory)
{
if (string.IsNullOrEmpty(watchedDirectory))
{
throw new ArgumentNullException(nameof(watchedDirectory));
}
_watchedDirectory = new DirectoryInfo(watchedDirectory);
_pollingThread = new Thread(new ThreadStart(PollingLoop));
_pollingThread.IsBackground = true;
_pollingThread.Name = nameof(PollingFileWatcher);
_pollingThread.Start();
}
public event EventHandler<string> OnFileChange;
#pragma warning disable CS0067 // not used
public event EventHandler OnError;
#pragma warning restore
public bool EnableRaisingEvents
{
get
{
return _raiseEvents;
}
set
{
EnsureNotDisposed();
if (value == true)
{
CreateKnownFilesSnapshot();
if (_pollingThread.ThreadState == System.Threading.ThreadState.Unstarted)
{
// Start the loop the first time events are enabled
_pollingThread.Start();
}
}
_raiseEvents = value;
}
}
private void PollingLoop()
{
var stopwatch = Stopwatch.StartNew();
stopwatch.Start();
while (!_disposed)
{
if (stopwatch.Elapsed < _minRunInternal)
{
// Don't run too often
// The min wait time here can be double
// the value of the variable (FYI)
Thread.Sleep(_minRunInternal);
}
stopwatch.Reset();
if (!_raiseEvents)
{
continue;
}
CheckForChangedFiles();
}
stopwatch.Stop();
}
private void CreateKnownFilesSnapshot()
{
_knownEntities.Clear();
ForeachEntityInDirectory(_watchedDirectory, f =>
{
_knownEntities.Add(f.FullName, new FileMeta(f));
});
}
private void CheckForChangedFiles()
{
_changes.Clear();
ForeachEntityInDirectory(_watchedDirectory, f =>
{
var fullFilePath = f.FullName;
if (!_knownEntities.ContainsKey(fullFilePath))
{
// New file
RecordChange(f);
}
else
{
var fileMeta = _knownEntities[fullFilePath];
if (fileMeta.FileInfo.LastWriteTime != f.LastWriteTime)
{
// File changed
RecordChange(f);
}
_knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, true);
}
_tempDictionary.Add(f.FullName, new FileMeta(f));
});
foreach (var file in _knownEntities)
{
if (!file.Value.FoundAgain)
{
// File deleted
RecordChange(file.Value.FileInfo);
}
}
NotifyChanges();
// Swap the two dictionaries
var swap = _knownEntities;
_knownEntities = _tempDictionary;
_tempDictionary = swap;
_tempDictionary.Clear();
}
private void RecordChange(FileSystemInfo fileInfo)
{
if (_changes.Contains(fileInfo.FullName) ||
fileInfo.FullName.Equals(_watchedDirectory.FullName, StringComparison.Ordinal))
{
return;
}
_changes.Add(fileInfo.FullName);
if (fileInfo.FullName != _watchedDirectory.FullName)
{
var file = fileInfo as FileInfo;
if (file != null)
{
RecordChange(file.Directory);
}
else
{
var dir = fileInfo as DirectoryInfo;
if (dir != null)
{
RecordChange(dir.Parent);
}
}
}
}
private void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action<FileSystemInfo> fileAction)
{
var entities = dirInfo.EnumerateFileSystemInfos("*.*");
foreach (var entity in entities)
{
fileAction(entity);
var subdirInfo = entity as DirectoryInfo;
if (subdirInfo != null)
{
ForeachEntityInDirectory(subdirInfo, fileAction);
}
}
}
private void NotifyChanges()
{
foreach (var path in _changes)
{
if (_disposed || !_raiseEvents)
{
break;
}
if (OnFileChange != null)
{
OnFileChange(this, path);
}
}
}
private void EnsureNotDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(PollingFileWatcher));
}
}
public void Dispose()
{
EnableRaisingEvents = false;
_disposed = true;
}
private struct FileMeta
{
public FileMeta(FileSystemInfo fileInfo, bool foundAgain = false)
{
FileInfo = fileInfo;
FoundAgain = foundAgain;
}
public FileSystemInfo FileInfo;
public bool FoundAgain;
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Microsoft.DotNet.Watcher.Core.Internal
@ -12,7 +11,7 @@ namespace Microsoft.DotNet.Watcher.Core.Internal
{
private bool _disposed;
private readonly IDictionary<string, FileSystemWatcher> _watchers = new Dictionary<string, FileSystemWatcher>();
private readonly IDictionary<string, IFileSystemWatcher> _watchers = new Dictionary<string, IFileSystemWatcher>();
public event Action<string> OnFileChange;
@ -62,28 +61,16 @@ namespace Microsoft.DotNet.Watcher.Core.Internal
}
}
var newWatcher = new FileSystemWatcher(directory);
newWatcher.IncludeSubdirectories = true;
newWatcher.Changed += WatcherChangedHandler;
newWatcher.Created += WatcherChangedHandler;
newWatcher.Deleted += WatcherChangedHandler;
newWatcher.Renamed += WatcherRenamedHandler;
var newWatcher = FileWatcherFactory.CreateWatcher(directory);
newWatcher.OnFileChange += WatcherChangedHandler;
newWatcher.EnableRaisingEvents = true;
_watchers.Add(directory, newWatcher);
}
private void WatcherRenamedHandler(object sender, RenamedEventArgs e)
private void WatcherChangedHandler(object sender, string changedPath)
{
NotifyChange(e.OldFullPath);
NotifyChange(e.FullPath);
}
private void WatcherChangedHandler(object sender, FileSystemEventArgs e)
{
NotifyChange(e.FullPath);
NotifyChange(changedPath);
}
private void NotifyChange(string path)
@ -100,11 +87,7 @@ namespace Microsoft.DotNet.Watcher.Core.Internal
_watchers.Remove(directory);
watcher.EnableRaisingEvents = false;
watcher.Changed -= WatcherChangedHandler;
watcher.Created -= WatcherChangedHandler;
watcher.Deleted -= WatcherChangedHandler;
watcher.Renamed -= WatcherRenamedHandler;
watcher.OnFileChange -= WatcherChangedHandler;
watcher.Dispose();
}

View File

@ -0,0 +1,349 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.DotNet.Watcher.Core.Internal;
using Xunit;
namespace Microsoft.DotNet.Watcher.FunctionalTests
{
public class FileWatcherTests
{
private const int DefaultTimeout = 10 * 1000; // 10 sec
[Theory]
[InlineData(true)]
[InlineData(false)]
public void NewFile(bool usePolling)
{
UsingTempDirectory(dir =>
{
using (var changedEv = new ManualResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
changedEv.Set();
};
watcher.EnableRaisingEvents = true;
var testFileFullPath = Path.Combine(dir, "foo");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ChangeFile(bool usePolling)
{
UsingTempDirectory(dir =>
{
var testFileFullPath = Path.Combine(dir, "foo");
File.WriteAllText(testFileFullPath, string.Empty);
using (var changedEv = new ManualResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
changedEv.Set();
};
watcher.EnableRaisingEvents = true;
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MoveFile(bool usePolling)
{
UsingTempDirectory(dir =>
{
var srcFile = Path.Combine(dir, "foo");
var dstFile = Path.Combine(dir, "foo2");
File.WriteAllText(srcFile, string.Empty);
using (var changedEv = new ManualResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
var changeCount = 0;
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
changeCount++;
if (changeCount >= 2)
{
changedEv.Set();
}
};
watcher.EnableRaisingEvents = true;
File.Move(srcFile, dstFile);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.True(filesChanged.Contains(srcFile));
Assert.True(filesChanged.Contains(dstFile));
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void FileInSubdirectory(bool usePolling)
{
UsingTempDirectory(dir =>
{
var subdir = Path.Combine(dir, "subdir");
Directory.CreateDirectory(subdir);
var testFileFullPath = Path.Combine(subdir, "foo");
File.WriteAllText(testFileFullPath, string.Empty);
using (var changedEv = new ManualResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
var totalChanges = 0;
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
totalChanges++;
if (totalChanges >= 2)
{
changedEv.Set();
}
};
watcher.EnableRaisingEvents = true;
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.True(filesChanged.Contains(subdir));
Assert.True(filesChanged.Contains(testFileFullPath));
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void NoNotificationIfDisabled(bool usePolling)
{
UsingTempDirectory(dir =>
{
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
using (var changedEv = new ManualResetEvent(false))
{
watcher.OnFileChange += (_, f) => changedEv.Set();
// Disable
watcher.EnableRaisingEvents = false;
var testFileFullPath = Path.Combine(dir, "foo");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.False(changedEv.WaitOne(DefaultTimeout / 2));
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void DisposedNoEvents(bool usePolling)
{
UsingTempDirectory(dir =>
{
using (var changedEv = new ManualResetEvent(false))
{
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
watcher.OnFileChange += (_, f) => changedEv.Set();
watcher.EnableRaisingEvents = true;
}
var testFileFullPath = Path.Combine(dir, "foo");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.False(changedEv.WaitOne(DefaultTimeout / 2));
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MultipleFiles(bool usePolling)
{
UsingTempDirectory(dir =>
{
File.WriteAllText(Path.Combine(dir, "foo1"), string.Empty);
File.WriteAllText(Path.Combine(dir, "foo2"), string.Empty);
File.WriteAllText(Path.Combine(dir, "foo3"), string.Empty);
File.WriteAllText(Path.Combine(dir, "foo4"), string.Empty);
File.WriteAllText(Path.Combine(dir, "foo4"), string.Empty);
var testFileFullPath = Path.Combine(dir, "foo3");
using (var changedEv = new ManualResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
changedEv.Set();
};
watcher.EnableRaisingEvents = true;
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MultipleTriggers(bool usePolling)
{
UsingTempDirectory(dir =>
{
using (var changedEv = new AutoResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
changedEv.Set();
};
watcher.EnableRaisingEvents = true;
var testFileFullPath = Path.Combine(dir, "foo1");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
filesChanged.Clear();
testFileFullPath = Path.Combine(dir, "foo2");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
filesChanged.Clear();
testFileFullPath = Path.Combine(dir, "foo3");
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
filesChanged.Clear();
File.WriteAllText(testFileFullPath, string.Empty);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.Equal(testFileFullPath, filesChanged.Single());
}
});
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void DeleteSubfolder(bool usePolling)
{
UsingTempDirectory(dir =>
{
var subdir = Path.Combine(dir, "subdir");
Directory.CreateDirectory(subdir);
var f1 = Path.Combine(subdir, "foo1");
var f2 = Path.Combine(subdir, "foo2");
var f3 = Path.Combine(subdir, "foo3");
File.WriteAllText(f1, string.Empty);
File.WriteAllText(f2, string.Empty);
File.WriteAllText(f3, string.Empty);
using (var changedEv = new AutoResetEvent(false))
using (var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling))
{
var filesChanged = new HashSet<string>();
var totalChanges = 0;
watcher.OnFileChange += (_, f) =>
{
filesChanged.Add(f);
totalChanges++;
if (totalChanges >= 4)
{
changedEv.Set();
}
};
watcher.EnableRaisingEvents = true;
Directory.Delete(subdir, recursive: true);
Assert.True(changedEv.WaitOne(DefaultTimeout));
Assert.True(filesChanged.Contains(f1));
Assert.True(filesChanged.Contains(f2));
Assert.True(filesChanged.Contains(f3));
Assert.True(filesChanged.Contains(subdir));
}
});
}
private static void UsingTempDirectory(Action<string> action)
{
var tempFolder = Path.Combine(Path.GetTempPath(), $"{nameof(FileWatcherTests)}-{Guid.NewGuid().ToString("N")}");
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, recursive: true);
}
Directory.CreateDirectory(tempFolder);
try
{
action(tempFolder);
}
finally
{
Directory.Delete(tempFolder, recursive: true);
}
}
}
}

View File

@ -15,13 +15,26 @@ namespace Microsoft.DotNet.Watcher.FunctionalTests
private static readonly TimeSpan _negativeTestWaitTime = TimeSpan.FromSeconds(10);
// Change a file included in compilation
[Fact]
public void ChangeCompiledFile()
public void ChangeCompiledFile_PollingWatcher()
{
ChangeCompiledFile(usePollingWatcher: true);
}
[Fact]
public void ChangeCompiledFile_DotNetWatcher()
{
ChangeCompiledFile(usePollingWatcher: false);
}
// Change a file included in compilation
private void ChangeCompiledFile(bool usePollingWatcher)
{
using (var scenario = new GlobbingAppScenario())
using (var wait = new WaitForFileToChange(scenario.StartedFile))
{
scenario.UsePollingWatcher = usePollingWatcher;
scenario.Start();
var fileToChange = Path.Combine(scenario.TestAppFolder, "include", "Foo.cs");
@ -91,12 +104,25 @@ namespace Microsoft.DotNet.Watcher.FunctionalTests
}
}
// Add a file that's in a included folder but not matching the globbing pattern
[Fact]
public void ChangeNonCompiledFile()
public void ChangeNonCompiledFile_PollingWatcher()
{
ChangeNonCompiledFile(usePollingWatcher: true);
}
[Fact]
public void ChangeNonCompiledFile_DotNetWatcher()
{
ChangeNonCompiledFile(usePollingWatcher: false);
}
// Add a file that's in a included folder but not matching the globbing pattern
private void ChangeNonCompiledFile(bool usePollingWatcher)
{
using (var scenario = new GlobbingAppScenario())
{
scenario.UsePollingWatcher = usePollingWatcher;
scenario.Start();
var ids = File.ReadAllLines(scenario.StatusFile);

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Extensions.Internal;
@ -25,9 +26,20 @@ namespace Microsoft.DotNet.Watcher.FunctionalTests
public Process WatcherProcess { get; private set; }
public bool UsePollingWatcher { get; set; }
protected void RunDotNetWatch(string arguments, string workingFolder)
{
WatcherProcess = _scenario.ExecuteDotnet("watch " + arguments, workingFolder);
IDictionary<string, string> envVariables = null;
if (UsePollingWatcher)
{
envVariables = new Dictionary<string, string>()
{
["USE_POLLING_FILE_WATCHER"] = "true"
};
}
WatcherProcess = _scenario.ExecuteDotnet("watch " + arguments, workingFolder, envVariables);
}
public virtual void Dispose()

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@ -114,16 +115,31 @@ namespace Microsoft.DotNet.Watcher.FunctionalTests
File.Copy(nugetConfigFilePath, tempNugetConfigFile);
}
public Process ExecuteDotnet(string arguments, string workDir)
public Process ExecuteDotnet(string arguments, string workDir, IDictionary<string, string> environmentVariables = null)
{
Console.WriteLine($"Running dotnet {arguments} in {workDir}");
var psi = new ProcessStartInfo("dotnet", arguments)
{
UseShellExecute = false,
WorkingDirectory = workDir
WorkingDirectory = workDir,
};
if (environmentVariables != null)
{
foreach (var newEnvVar in environmentVariables)
{
if (psi.Environment.ContainsKey(newEnvVar.Key))
{
psi.Environment[newEnvVar.Key] = newEnvVar.Value;
}
else
{
psi.Environment.Add(newEnvVar.Key, newEnvVar.Value);
}
}
}
return Process.Start(psi);
}

View File

@ -4,30 +4,30 @@
using System;
using System.IO;
using System.Threading;
using Microsoft.DotNet.Watcher.Core.Internal;
namespace Microsoft.DotNet.Watcher.FunctionalTests
{
public class WaitForFileToChange : IDisposable
{
private readonly FileSystemWatcher _watcher;
private readonly IFileSystemWatcher _watcher;
private readonly string _expectedFile;
private ManualResetEvent _changed = new ManualResetEvent(false);
public WaitForFileToChange(string file)
{
_watcher = new FileSystemWatcher(Path.GetDirectoryName(file), "*" + Path.GetExtension(file));
_watcher = FileWatcherFactory.CreateWatcher(Path.GetDirectoryName(file), usePollingWatcher: true);
_expectedFile = file;
_watcher.Changed += WatcherEvent;
_watcher.Created += WatcherEvent;
_watcher.OnFileChange += WatcherEvent;
_watcher.EnableRaisingEvents = true;
}
private void WatcherEvent(object sender, FileSystemEventArgs e)
private void WatcherEvent(object sender, string file)
{
if (e.FullPath.Equals(_expectedFile, StringComparison.Ordinal))
if (file.Equals(_expectedFile, StringComparison.Ordinal))
{
Waiters.WaitForFileToBeReadable(_expectedFile, TimeSpan.FromSeconds(10));
_changed?.Set();
@ -50,9 +50,8 @@ namespace Microsoft.DotNet.Watcher.FunctionalTests
{
_watcher.EnableRaisingEvents = false;
_watcher.Changed -= WatcherEvent;
_watcher.Created -= WatcherEvent;
_watcher.OnFileChange -= WatcherEvent;
_watcher.Dispose();
_changed.Dispose();