Refactor tests to fix false-positive tests passes
This commit is contained in:
parent
15a877e7fb
commit
a6f4a38202
|
|
@ -19,7 +19,6 @@ mono:
|
|||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode7.1
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
|
|
|||
|
|
@ -3,80 +3,51 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class AppWithDepsTests
|
||||
public class AppWithDepsTests : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
private readonly ITestOutputHelper _logger;
|
||||
private readonly AppWithDeps _app;
|
||||
|
||||
public AppWithDepsTests(ITestOutputHelper logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_app = new AppWithDeps(logger);
|
||||
_app.Prepare();
|
||||
}
|
||||
|
||||
// Change a file included in compilation
|
||||
[Fact]
|
||||
public void ChangeFileInDependency()
|
||||
public async Task ChangeFileInDependency()
|
||||
{
|
||||
using (var scenario = new AppWithDepsScenario(_logger))
|
||||
{
|
||||
scenario.Start();
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
var fileToChange = Path.Combine(scenario.DependencyFolder, "Foo.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
}
|
||||
var fileToChange = Path.Combine(_app.DependencyFolder, "Foo.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
}
|
||||
|
||||
private class AppWithDepsScenario : DotNetWatchScenario
|
||||
public void Dispose()
|
||||
{
|
||||
_app.Dispose();
|
||||
}
|
||||
|
||||
private class AppWithDeps : WatchableApp
|
||||
{
|
||||
private const string AppWithDeps = "AppWithDeps";
|
||||
private const string Dependency = "Dependency";
|
||||
|
||||
public AppWithDepsScenario(ITestOutputHelper logger)
|
||||
: base(logger)
|
||||
public AppWithDeps(ITestOutputHelper logger)
|
||||
: base("AppWithDeps", logger)
|
||||
{
|
||||
StatusFile = Path.Combine(Scenario.TempFolder, "status");
|
||||
StartedFile = StatusFile + ".started";
|
||||
|
||||
Scenario.AddTestProjectFolder(AppWithDeps);
|
||||
Scenario.AddTestProjectFolder(Dependency);
|
||||
|
||||
Scenario.Restore(AppWithDeps); // restore3 should be transitive
|
||||
|
||||
AppWithDepsFolder = Path.Combine(Scenario.WorkFolder, AppWithDeps);
|
||||
DependencyFolder = Path.Combine(Scenario.WorkFolder, Dependency);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Wait for the process to start
|
||||
using (var wait = new WaitForFileToChange(StatusFile))
|
||||
{
|
||||
RunDotNetWatch(new[] { "run", StatusFile }, Path.Combine(Scenario.WorkFolder, AppWithDeps));
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"File not created: {StatusFile}");
|
||||
}
|
||||
|
||||
Waiters.WaitForFileToBeReadable(StatusFile, _defaultTimeout);
|
||||
}
|
||||
|
||||
public string StatusFile { get; private set; }
|
||||
public string StartedFile { get; private set; }
|
||||
public string AppWithDepsFolder { get; private set; }
|
||||
public string DependencyFolder { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class AwaitableProcess : IDisposable
|
||||
{
|
||||
private Process _process;
|
||||
private readonly ProcessSpec _spec;
|
||||
private BufferBlock<string> _source;
|
||||
private ITestOutputHelper _logger;
|
||||
private int _reading;
|
||||
|
||||
public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger)
|
||||
{
|
||||
_spec = spec;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_process != null)
|
||||
{
|
||||
throw new InvalidOperationException("Already started");
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
UseShellExecute = false,
|
||||
FileName = _spec.Executable,
|
||||
WorkingDirectory = _spec.WorkingDirectory,
|
||||
Arguments = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(_spec.Arguments),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
_process = Process.Start(psi);
|
||||
_logger.WriteLine($"{DateTime.Now}: process start: '{psi.FileName} {psi.Arguments}'");
|
||||
StartProcessingOutput(_process.StandardOutput);
|
||||
StartProcessingOutput(_process.StandardError);;
|
||||
}
|
||||
|
||||
public Task<string> GetOutputLineAsync(string message)
|
||||
=> GetOutputLineAsync(m => message == m);
|
||||
|
||||
public async Task<string> GetOutputLineAsync(Predicate<string> predicate)
|
||||
{
|
||||
while (!_source.Completion.IsCompleted)
|
||||
{
|
||||
while (await _source.OutputAvailableAsync())
|
||||
{
|
||||
var next = await _source.ReceiveAsync();
|
||||
_logger.WriteLine($"{DateTime.Now}: recv: '{next}'");
|
||||
if (predicate(next))
|
||||
{
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void StartProcessingOutput(StreamReader streamReader)
|
||||
{
|
||||
_source = _source ?? new BufferBlock<string>();
|
||||
Interlocked.Increment(ref _reading);
|
||||
Task.Run(() =>
|
||||
{
|
||||
string line;
|
||||
while ((line = streamReader.ReadLine()) != null)
|
||||
{
|
||||
_logger.WriteLine($"{DateTime.Now} post: {line}");
|
||||
_source.Post(line);
|
||||
}
|
||||
|
||||
if (Interlocked.Decrement(ref _reading) <= 0)
|
||||
{
|
||||
_source.Complete();
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_process != null && !_process.HasExited)
|
||||
{
|
||||
_process.KillTree();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,227 +2,134 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class GlobbingAppTests
|
||||
public class GlobbingAppTests : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
private static readonly TimeSpan _negativeTestWaitTime = TimeSpan.FromSeconds(10);
|
||||
|
||||
private readonly ITestOutputHelper _logger;
|
||||
|
||||
private GlobbingApp _app;
|
||||
public GlobbingAppTests(ITestOutputHelper logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_app = new GlobbingApp(logger);
|
||||
_app.Prepare();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task ChangeCompiledFile(bool usePollingWatcher)
|
||||
{
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(2, types);
|
||||
|
||||
var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(2, types);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Broken. See https://github.com/aspnet/DotNetTools/issues/212")]
|
||||
public async Task AddCompiledFile()
|
||||
{
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(2, types);
|
||||
|
||||
var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Bar.cs");
|
||||
File.WriteAllText(fileToChange, "public class Bar {}");
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(3, types);
|
||||
}
|
||||
|
||||
// TODO re-enable when MSBuild is updated. See https://github.com/aspnet/DotNetTools/issues/224
|
||||
[Fact(Skip = "Broken. See https://github.com/Microsoft/msbuild/issues/701")]
|
||||
public async Task DeleteCompiledFile()
|
||||
{
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(2, types);
|
||||
|
||||
var fileToChange = Path.Combine(_app.SourceDirectory, "include", "Foo.cs");
|
||||
File.Delete(fileToChange);
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(1, types);
|
||||
}
|
||||
|
||||
// TODO re-enable when MSBuild is updated. See https://github.com/aspnet/DotNetTools/issues/224
|
||||
[Fact(Skip = "Broken. See https://github.com/Microsoft/msbuild/issues/701")]
|
||||
public async Task DeleteSourceFolder()
|
||||
{
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(2, types);
|
||||
|
||||
var folderToDelete = Path.Combine(_app.SourceDirectory, "include");
|
||||
Directory.Delete(folderToDelete, recursive: true);
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
types = await _app.GetCompiledAppDefinedTypes().OrTimeout();
|
||||
Assert.Equal(1, types);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeCompiledFile_PollingWatcher()
|
||||
public async Task RenameCompiledFile()
|
||||
{
|
||||
ChangeCompiledFile(usePollingWatcher: true);
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var oldFile = Path.Combine(_app.SourceDirectory, "include", "Foo.cs");
|
||||
var newFile = Path.Combine(_app.SourceDirectory, "include", "Foo_new.cs");
|
||||
File.Move(oldFile, newFile);
|
||||
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeCompiledFile_DotNetWatcher()
|
||||
public async Task ChangeExcludedFile()
|
||||
{
|
||||
ChangeCompiledFile(usePollingWatcher: false);
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
|
||||
var changedFile = Path.Combine(_app.SourceDirectory, "exclude", "Baz.cs");
|
||||
File.WriteAllText(changedFile, "");
|
||||
|
||||
var restart = _app.HasRestarted();
|
||||
var finished = await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(10)), restart);
|
||||
Assert.NotSame(restart, finished);
|
||||
}
|
||||
|
||||
// Change a file included in compilation
|
||||
private void ChangeCompiledFile(bool usePollingWatcher)
|
||||
public void Dispose()
|
||||
{
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
_app.Dispose();
|
||||
}
|
||||
|
||||
private class GlobbingApp : WatchableApp
|
||||
{
|
||||
public GlobbingApp(ITestOutputHelper logger)
|
||||
: base("GlobbingApp", logger)
|
||||
{
|
||||
scenario.UsePollingWatcher = usePollingWatcher;
|
||||
|
||||
scenario.Start();
|
||||
|
||||
var fileToChange = Path.Combine(scenario.TestAppFolder, "include", "Foo.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add a file to a folder included in compilation
|
||||
[Fact]
|
||||
public void AddCompiledFile()
|
||||
{
|
||||
// Add a file in a folder that's included in compilation
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
public async Task<int> GetCompiledAppDefinedTypes()
|
||||
{
|
||||
scenario.Start();
|
||||
|
||||
var fileToChange = Path.Combine(scenario.TestAppFolder, "include", "Bar.cs");
|
||||
File.WriteAllText(fileToChange, "");
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
var definedTypesMessage = await Process.GetOutputLineAsync(m => m.StartsWith("Defined types = "));
|
||||
return int.Parse(definedTypesMessage.Split('=').Last());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a file included in compilation
|
||||
[Fact]
|
||||
public void DeleteCompiledFile()
|
||||
{
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
scenario.Start();
|
||||
|
||||
var fileToChange = Path.Combine(scenario.TestAppFolder, "include", "Foo.cs");
|
||||
File.Delete(fileToChange);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an entire folder
|
||||
[Fact]
|
||||
public void DeleteSourceFolder()
|
||||
{
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
scenario.Start();
|
||||
|
||||
var folderToDelete = Path.Combine(scenario.TestAppFolder, "include");
|
||||
Directory.Delete(folderToDelete, recursive: true);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
}
|
||||
|
||||
// Rename a file included in compilation
|
||||
[Fact]
|
||||
public void RenameCompiledFile()
|
||||
{
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
using (var wait = new WaitForFileToChange(scenario.StatusFile))
|
||||
{
|
||||
scenario.Start();
|
||||
|
||||
var oldFile = Path.Combine(scenario.TestAppFolder, "include", "Foo.cs");
|
||||
var newFile = Path.Combine(scenario.TestAppFolder, "include", "Foo_new.cs");
|
||||
File.Move(oldFile, newFile);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
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(_logger))
|
||||
{
|
||||
scenario.UsePollingWatcher = usePollingWatcher;
|
||||
|
||||
scenario.Start();
|
||||
|
||||
var ids = File.ReadAllLines(scenario.StatusFile);
|
||||
var procId = int.Parse(ids[0]);
|
||||
|
||||
var changedFile = Path.Combine(scenario.TestAppFolder, "include", "not_compiled.css");
|
||||
File.WriteAllText(changedFile, "");
|
||||
|
||||
Console.WriteLine($"Waiting {_negativeTestWaitTime.TotalSeconds} seconds to see if the app restarts");
|
||||
Waiters.WaitForProcessToStop(
|
||||
procId,
|
||||
_negativeTestWaitTime,
|
||||
expectedToStop: false,
|
||||
errorMessage: "Test app restarted");
|
||||
}
|
||||
}
|
||||
|
||||
// Change a file that's in an excluded folder
|
||||
[Fact]
|
||||
public void ChangeExcludedFile()
|
||||
{
|
||||
using (var scenario = new GlobbingAppScenario(_logger))
|
||||
{
|
||||
scenario.Start();
|
||||
|
||||
var ids = File.ReadAllLines(scenario.StatusFile);
|
||||
var procId = int.Parse(ids[0]);
|
||||
|
||||
var changedFile = Path.Combine(scenario.TestAppFolder, "exclude", "Baz.cs");
|
||||
File.WriteAllText(changedFile, "");
|
||||
|
||||
Console.WriteLine($"Waiting {_negativeTestWaitTime.TotalSeconds} seconds to see if the app restarts");
|
||||
Waiters.WaitForProcessToStop(
|
||||
procId,
|
||||
_negativeTestWaitTime,
|
||||
expectedToStop: false,
|
||||
errorMessage: "Test app restarted");
|
||||
}
|
||||
}
|
||||
|
||||
private class GlobbingAppScenario : DotNetWatchScenario
|
||||
{
|
||||
private const string TestAppName = "GlobbingApp";
|
||||
|
||||
public GlobbingAppScenario(ITestOutputHelper logger)
|
||||
: base(logger)
|
||||
{
|
||||
StatusFile = Path.Combine(Scenario.TempFolder, "status");
|
||||
StartedFile = StatusFile + ".started";
|
||||
|
||||
Scenario.AddTestProjectFolder(TestAppName);
|
||||
Scenario.Restore(TestAppName);
|
||||
|
||||
TestAppFolder = Path.Combine(Scenario.WorkFolder, TestAppName);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Wait for the process to start
|
||||
using (var wait = new WaitForFileToChange(StartedFile))
|
||||
{
|
||||
RunDotNetWatch(new[] { "run", StatusFile }, Path.Combine(Scenario.WorkFolder, TestAppName));
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"File not created: {StartedFile}");
|
||||
}
|
||||
|
||||
Waiters.WaitForFileToBeReadable(StartedFile, _defaultTimeout);
|
||||
}
|
||||
|
||||
public string StatusFile { get; private set; }
|
||||
public string StartedFile { get; private set; }
|
||||
public string TestAppFolder { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,132 +2,63 @@
|
|||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class NoDepsAppTests
|
||||
public class NoDepsAppTests : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
|
||||
private readonly ITestOutputHelper _logger;
|
||||
private readonly WatchableApp _app;
|
||||
|
||||
public NoDepsAppTests(ITestOutputHelper logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_app = new WatchableApp("NoDepsApp", logger);
|
||||
_app.Prepare();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RestartProcessOnFileChange()
|
||||
public async Task RestartProcessOnFileChange()
|
||||
{
|
||||
using (var scenario = new NoDepsAppScenario(_logger))
|
||||
{
|
||||
// Wait for the process to start
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
scenario.RunDotNetWatch(new[] { "run", scenario.StatusFile, "--no-exit" });
|
||||
await _app.StartWatcher(new[] { "--no-exit" }).OrTimeout();
|
||||
var pid = await _app.GetProcessId().OrTimeout();
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"File not created: {scenario.StartedFile}");
|
||||
}
|
||||
// Then wait for it to restart when we change a file
|
||||
var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
// Then wait for it to restart when we change a file
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
var fileToChange = Path.Combine(scenario.TestAppFolder, "Program.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
var pid2 = await _app.GetProcessId().OrTimeout();
|
||||
Assert.NotEqual(pid, pid2);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
|
||||
// Check that the first child process is no longer running
|
||||
Waiters.WaitForFileToBeReadable(scenario.StatusFile, _defaultTimeout);
|
||||
var ids = File.ReadAllLines(scenario.StatusFile);
|
||||
var firstProcessId = int.Parse(ids[0]);
|
||||
Waiters.WaitForProcessToStop(
|
||||
firstProcessId,
|
||||
TimeSpan.FromSeconds(1),
|
||||
expectedToStop: true,
|
||||
errorMessage: $"PID: {firstProcessId} is still alive");
|
||||
}
|
||||
// first app should have shut down
|
||||
Assert.Throws<ArgumentException>(() => Process.GetProcessById(pid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RestartProcessThatTerminatesAfterFileChange()
|
||||
public async Task RestartProcessThatTerminatesAfterFileChange()
|
||||
{
|
||||
using (var scenario = new NoDepsAppScenario(_logger))
|
||||
{
|
||||
// Wait for the process to start
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
scenario.RunDotNetWatch(new[] { "run", scenario.StatusFile });
|
||||
await _app.StartWatcher().OrTimeout();
|
||||
var pid = await _app.GetProcessId().OrTimeout();
|
||||
await _app.HasExited().OrTimeout(); // process should exit after run
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"File not created: {scenario.StartedFile}");
|
||||
}
|
||||
var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
// Then wait for the app to exit
|
||||
Waiters.WaitForFileToBeReadable(scenario.StartedFile, _defaultTimeout);
|
||||
var ids = File.ReadAllLines(scenario.StatusFile);
|
||||
var procId = int.Parse(ids[0]);
|
||||
Waiters.WaitForProcessToStop(
|
||||
procId,
|
||||
_defaultTimeout,
|
||||
expectedToStop: true,
|
||||
errorMessage: "Test app did not exit");
|
||||
|
||||
// Then wait for it to restart when we change a file
|
||||
using (var wait = new WaitForFileToChange(scenario.StartedFile))
|
||||
{
|
||||
// On Unix the file write time is in 1s increments;
|
||||
// if we don't wait, there's a chance that the polling
|
||||
// watcher will not detect the change
|
||||
Thread.Sleep(1000);
|
||||
|
||||
var fileToChange = Path.Combine(scenario.TestAppFolder, "Program.cs");
|
||||
var programCs = File.ReadAllText(fileToChange);
|
||||
File.WriteAllText(fileToChange, programCs);
|
||||
|
||||
wait.Wait(_defaultTimeout,
|
||||
expectedToChange: true,
|
||||
errorMessage: $"Process did not restart because {scenario.StartedFile} was not changed");
|
||||
}
|
||||
}
|
||||
await _app.HasRestarted().OrTimeout();
|
||||
var pid2 = await _app.GetProcessId().OrTimeout();
|
||||
Assert.NotEqual(pid, pid2);
|
||||
await _app.HasExited().OrTimeout(); // process should exit after run
|
||||
}
|
||||
|
||||
private class NoDepsAppScenario : DotNetWatchScenario
|
||||
public void Dispose()
|
||||
{
|
||||
private const string TestAppName = "NoDepsApp";
|
||||
|
||||
public NoDepsAppScenario(ITestOutputHelper logger)
|
||||
: base(logger)
|
||||
{
|
||||
StatusFile = Path.Combine(Scenario.TempFolder, "status");
|
||||
StartedFile = StatusFile + ".started";
|
||||
|
||||
Scenario.AddTestProjectFolder(TestAppName);
|
||||
Scenario.Restore(TestAppName);
|
||||
|
||||
TestAppFolder = Path.Combine(Scenario.WorkFolder, TestAppName);
|
||||
}
|
||||
|
||||
public string StatusFile { get; private set; }
|
||||
public string StartedFile { get; private set; }
|
||||
public string TestAppFolder { get; private set; }
|
||||
|
||||
public void RunDotNetWatch(IEnumerable<string> args)
|
||||
{
|
||||
RunDotNetWatch(args, Path.Combine(Scenario.WorkFolder, TestAppName));
|
||||
}
|
||||
_app.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// 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 Microsoft.Extensions.Internal;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class DotNetWatchScenario : IDisposable
|
||||
{
|
||||
protected ProjectToolScenario Scenario { get; }
|
||||
public DotNetWatchScenario()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DotNetWatchScenario(ITestOutputHelper logger)
|
||||
{
|
||||
Scenario = new ProjectToolScenario(logger);
|
||||
}
|
||||
|
||||
public Process WatcherProcess { get; private set; }
|
||||
|
||||
public bool UsePollingWatcher { get; set; }
|
||||
|
||||
protected void RunDotNetWatch(IEnumerable<string> arguments, string workingFolder)
|
||||
{
|
||||
IDictionary<string, string> envVariables = null;
|
||||
if (UsePollingWatcher)
|
||||
{
|
||||
envVariables = new Dictionary<string, string>()
|
||||
{
|
||||
["DOTNET_USE_POLLING_FILE_WATCHER"] = "true"
|
||||
};
|
||||
}
|
||||
|
||||
WatcherProcess = Scenario.ExecuteDotnetWatch(arguments, workingFolder, envVariables);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (WatcherProcess != null)
|
||||
{
|
||||
if (!WatcherProcess.HasExited)
|
||||
{
|
||||
WatcherProcess.KillTree();
|
||||
}
|
||||
WatcherProcess.Dispose();
|
||||
}
|
||||
Scenario.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
|
|
@ -58,12 +56,21 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
|
||||
public void Restore(string project)
|
||||
{
|
||||
project = Path.Combine(WorkFolder, project);
|
||||
|
||||
_logger?.WriteLine($"Restoring msbuild project in {project}");
|
||||
ExecuteCommand(project, "restore");
|
||||
}
|
||||
|
||||
var restore = Command
|
||||
.Create(new Muxer().MuxerPath, new[] { "restore", "/p:SkipInvalidConfigurations=true" })
|
||||
public void Build(string project)
|
||||
{
|
||||
_logger?.WriteLine($"Building {project}");
|
||||
ExecuteCommand(project, "build");
|
||||
}
|
||||
|
||||
private void ExecuteCommand(string project, params string[] arguments)
|
||||
{
|
||||
project = Path.Combine(WorkFolder, project);
|
||||
var command = Command
|
||||
.Create(new Muxer().MuxerPath, arguments)
|
||||
.WorkingDirectory(project)
|
||||
.CaptureStdErr()
|
||||
.CaptureStdOut()
|
||||
|
|
@ -71,9 +78,9 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
.OnOutputLine(l => _logger?.WriteLine(l))
|
||||
.Execute();
|
||||
|
||||
if (restore.ExitCode != 0)
|
||||
if (command.ExitCode != 0)
|
||||
{
|
||||
throw new Exception($"Exit code {restore.ExitCode}");
|
||||
throw new InvalidOperationException($"Exit code {command.ExitCode}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +95,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
File.Copy(nugetConfigFilePath, tempNugetConfigFile);
|
||||
}
|
||||
|
||||
public Process ExecuteDotnetWatch(IEnumerable<string> arguments, string workDir, IDictionary<string, string> environmentVariables = null)
|
||||
public IEnumerable<string> GetDotnetWatchArguments()
|
||||
{
|
||||
// this launches a new .NET Core process using the runtime of the current test app
|
||||
// and the version of dotnet-watch that this test app is compiled against
|
||||
|
|
@ -104,32 +111,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
|
||||
args.Add(Path.Combine(AppContext.BaseDirectory, "dotnet-watch.dll"));
|
||||
|
||||
var argsStr = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args.Concat(arguments));
|
||||
|
||||
_logger?.WriteLine($"Running dotnet {argsStr} in {workDir}");
|
||||
|
||||
var psi = new ProcessStartInfo(new Muxer().MuxerPath, argsStr)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = workDir
|
||||
};
|
||||
|
||||
if (environmentVariables != null)
|
||||
{
|
||||
foreach (var newEnvVar in environmentVariables)
|
||||
{
|
||||
var varKey = newEnvVar.Key;
|
||||
var varValue = newEnvVar.Value;
|
||||
#if NET451
|
||||
psi.EnvironmentVariables[varKey] = varValue;
|
||||
|
||||
#else
|
||||
psi.Environment[varKey] = varValue;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return Process.Start(psi);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static string FindNugetConfig()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
// 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;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class WatchableApp : IDisposable
|
||||
{
|
||||
private const string StartedMessage = "Started";
|
||||
private const string ExitingMessage = "Exiting";
|
||||
|
||||
protected ProjectToolScenario Scenario { get; }
|
||||
private readonly ITestOutputHelper _logger;
|
||||
protected AwaitableProcess Process { get; set; }
|
||||
private string _appName;
|
||||
private bool _prepared;
|
||||
|
||||
public WatchableApp(string appName, ITestOutputHelper logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_appName = appName;
|
||||
Scenario = new ProjectToolScenario(logger);
|
||||
Scenario.AddTestProjectFolder(appName);
|
||||
SourceDirectory = Path.Combine(Scenario.WorkFolder, appName);
|
||||
}
|
||||
|
||||
public string SourceDirectory { get; }
|
||||
|
||||
public Task HasRestarted()
|
||||
=> Process.GetOutputLineAsync(StartedMessage);
|
||||
|
||||
public Task HasExited()
|
||||
=> Process.GetOutputLineAsync(ExitingMessage);
|
||||
|
||||
public bool UsePollingWatcher { get; set; }
|
||||
|
||||
public Task StartWatcher([CallerMemberName] string name = null)
|
||||
=> StartWatcher(Array.Empty<string>(), name);
|
||||
|
||||
public async Task<int> GetProcessId()
|
||||
{
|
||||
var line = await Process.GetOutputLineAsync(l => l.StartsWith("PID ="));
|
||||
var pid = line.Split('=').Last();
|
||||
return int.Parse(pid);
|
||||
}
|
||||
|
||||
public void Prepare()
|
||||
{
|
||||
Scenario.Restore(_appName);
|
||||
Scenario.Build(_appName);
|
||||
_prepared = true;
|
||||
}
|
||||
|
||||
public async Task StartWatcher(string[] arguments, [CallerMemberName] string name = null)
|
||||
{
|
||||
if (!_prepared)
|
||||
{
|
||||
throw new InvalidOperationException("Call .Prepare() first");
|
||||
}
|
||||
|
||||
var args = Scenario
|
||||
.GetDotnetWatchArguments()
|
||||
.Concat(new[] { "run", "--" })
|
||||
.Concat(arguments);
|
||||
|
||||
var spec = new ProcessSpec
|
||||
{
|
||||
Executable = new Muxer().MuxerPath,
|
||||
Arguments = args,
|
||||
WorkingDirectory = SourceDirectory
|
||||
};
|
||||
|
||||
Process = new AwaitableProcess(spec, _logger);
|
||||
Process.Start();
|
||||
await Process.GetOutputLineAsync(StartedMessage);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Process.Dispose();
|
||||
Scenario.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
public static async Task<T> OrTimeout<T>(this Task<T> task, int timeout = 30, [CallerFilePath] string file = null, [CallerLineNumber] int line = 0)
|
||||
{
|
||||
await OrTimeout((Task)task, timeout, file, line);
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
public static async Task OrTimeout(this Task task, int timeout = 30, [CallerFilePath] string file = null, [CallerLineNumber] int line = 0)
|
||||
{
|
||||
var finished = await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(timeout)));
|
||||
if (!ReferenceEquals(finished, task))
|
||||
{
|
||||
throw new TimeoutException($"Task exceeded max running time of {timeout}s at {file}:{line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<Compile Include="**\*.cs" />
|
||||
<EmbeddedResource Include="**\*.resx" />
|
||||
<ProjectReference Include="..\Dependency\Dependency.csproj" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161029-1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161104-2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NETCore.App" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace ConsoleApplication
|
||||
|
|
@ -14,26 +13,9 @@ namespace ConsoleApplication
|
|||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ConsoleWrite("AppWithDeps started.");
|
||||
|
||||
File.AppendAllLines(args[0], new string[] { $"{processId}" });
|
||||
|
||||
File.WriteAllText(args[0] + ".started", "");
|
||||
Block();
|
||||
}
|
||||
|
||||
private static void ConsoleWrite(string text)
|
||||
{
|
||||
Console.WriteLine($"[{processId}] {text}");
|
||||
}
|
||||
|
||||
private static void Block()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ConsoleWrite("Blocked...");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
Console.WriteLine("Started");
|
||||
Console.WriteLine($"PID = " + Process.GetCurrentProcess().Id);
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<Compile Include="**\*.cs" />
|
||||
<EmbeddedResource Include="**\*.resx" />
|
||||
<PackageReference Include="NETStandard.Library" Version="1.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161029-1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161104-2" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Program.cs;include\*.cs" Exclude="exclude\*" />
|
||||
<EmbeddedResource Include="**\*.resx" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161029-1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161104-2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NETCore.App" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
|
|||
|
|
@ -3,37 +3,20 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace ConsoleApplication
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static readonly int processId = Process.GetCurrentProcess().Id;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ConsoleWrite("GlobbingApp started.");
|
||||
|
||||
File.AppendAllLines(args[0], new string[] { $"{processId}" });
|
||||
|
||||
File.WriteAllText(args[0] + ".started", "");
|
||||
Block();
|
||||
}
|
||||
|
||||
private static void ConsoleWrite(string text)
|
||||
{
|
||||
Console.WriteLine($"[{processId}] {text}");
|
||||
}
|
||||
|
||||
private static void Block()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ConsoleWrite("Blocked...");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
Console.WriteLine("Started");
|
||||
Console.WriteLine("PID = " + Process.GetCurrentProcess().Id);
|
||||
Console.WriteLine("Defined types = " + typeof(Program).GetTypeInfo().Assembly.DefinedTypes.Count());
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="**\*.cs" />
|
||||
<EmbeddedResource Include="**\*.resx" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161029-1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk" Version="1.0.0-alpha-20161104-2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.NETCore.App" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
|
|
|||
|
|
@ -3,41 +3,21 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace ConsoleApplication
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
private static readonly int processId = Process.GetCurrentProcess().Id;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ConsoleWrite("NoDepsApp started.");
|
||||
|
||||
File.AppendAllLines(args[0], new string[] { $"{processId}" });
|
||||
|
||||
File.WriteAllText(args[0] + ".started", "");
|
||||
|
||||
if (args.Length > 1 && args[1] == "--no-exit")
|
||||
Console.WriteLine("Started");
|
||||
Console.WriteLine($"PID = " + Process.GetCurrentProcess().Id);
|
||||
if (args.Length > 0 && args[0] == "--no-exit")
|
||||
{
|
||||
Block();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConsoleWrite(string text)
|
||||
{
|
||||
Console.WriteLine($"[{processId}] {text}");
|
||||
}
|
||||
|
||||
private static void Block()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ConsoleWrite("Blocked...");
|
||||
Thread.Sleep(1000);
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
Console.WriteLine("Exiting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
// 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;
|
||||
using System.Threading;
|
||||
using Microsoft.DotNet.Watcher.Internal;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public class WaitForFileToChange : IDisposable
|
||||
{
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly string _expectedFile;
|
||||
|
||||
private ManualResetEvent _changed = new ManualResetEvent(false);
|
||||
|
||||
public WaitForFileToChange(string file)
|
||||
{
|
||||
_watcher = FileWatcherFactory.CreateWatcher(Path.GetDirectoryName(file), usePollingWatcher: true);
|
||||
_expectedFile = file;
|
||||
|
||||
_watcher.OnFileChange += WatcherEvent;
|
||||
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private void WatcherEvent(object sender, string file)
|
||||
{
|
||||
if (file.Equals(_expectedFile, StringComparison.Ordinal))
|
||||
{
|
||||
Waiters.WaitForFileToBeReadable(_expectedFile, TimeSpan.FromSeconds(10));
|
||||
_changed?.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait(TimeSpan timeout, bool expectedToChange, string errorMessage)
|
||||
{
|
||||
if (_changed != null)
|
||||
{
|
||||
var changed = _changed.WaitOne(timeout);
|
||||
if (changed != expectedToChange)
|
||||
{
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
|
||||
_watcher.OnFileChange -= WatcherEvent;
|
||||
|
||||
_watcher.Dispose();
|
||||
_changed.Dispose();
|
||||
|
||||
_changed = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
||||
{
|
||||
public static class Waiters
|
||||
{
|
||||
public static void WaitForFileToBeReadable(string file, TimeSpan timeout)
|
||||
{
|
||||
var watch = new Stopwatch();
|
||||
|
||||
Exception lastException = null;
|
||||
|
||||
watch.Start();
|
||||
while (watch.Elapsed < timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.ReadAllText(file);
|
||||
watch.Stop();
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
lastException = e;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
watch.Stop();
|
||||
|
||||
if (lastException != null)
|
||||
{
|
||||
Console.WriteLine("Last exception:");
|
||||
Console.WriteLine(lastException);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"{file} is not readable.");
|
||||
}
|
||||
|
||||
public static void WaitForProcessToStop(int processId, TimeSpan timeout, bool expectedToStop, string errorMessage)
|
||||
{
|
||||
Console.WriteLine($"Waiting for process {processId} to stop...");
|
||||
|
||||
Process process = null;
|
||||
|
||||
try
|
||||
{
|
||||
process = Process.GetProcessById(processId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If we expect the process to stop, then it might have stopped already
|
||||
if (!expectedToStop)
|
||||
{
|
||||
Console.WriteLine($"Could not find process {processId}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
var watch = new Stopwatch();
|
||||
watch.Start();
|
||||
while (watch.Elapsed < timeout)
|
||||
{
|
||||
if (process == null || process.HasExited)
|
||||
{
|
||||
Console.WriteLine($"Process {processId} is no longer running");
|
||||
break;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
watch.Stop();
|
||||
|
||||
bool isStopped = process == null || process.HasExited;
|
||||
if (isStopped != expectedToStop)
|
||||
{
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@echo off
|
||||
|
||||
rem For local testing
|
||||
|
||||
dotnet build
|
||||
..\..\.build\dotnet\dotnet.exe exec --depsfile bin\Debug\netcoreapp1.0\Microsoft.DotNet.Watcher.Tools.FunctionalTests.deps.json --runtimeconfig bin\Debug\netcoreapp1.0\Microsoft.DotNet.Watcher.Tools.FunctionalTests.runtimeconfig.json ..\..\.build\dotnet-test-xunit\2.2.0-preview2-build1029\lib\netcoreapp1.0\dotnet-test-xunit.dll bin\Debug\netcoreapp1.0\Microsoft.DotNet.Watcher.Tools.FunctionalTests.dll %*
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
dotnet build
|
||||
|
||||
../../.build/dotnet/dotnet exec \
|
||||
--depsfile bin/Debug/netcoreapp1.0/Microsoft.DotNet.Watcher.Tools.FunctionalTests.deps.json \
|
||||
--runtimeconfig bin/Debug/netcoreapp1.0/Microsoft.DotNet.Watcher.Tools.FunctionalTests.runtimeconfig.json \
|
||||
../../.build/dotnet-test-xunit/2.2.0-preview2-build1029/lib/netcoreapp1.0/dotnet-test-xunit.dll \
|
||||
bin/Debug/netcoreapp1.0/Microsoft.DotNet.Watcher.Tools.FunctionalTests.dll \
|
||||
$@
|
||||
Loading…
Reference in New Issue