diff --git a/.travis.yml b/.travis.yml index 2a46104677..dfa226a245 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ branches: - master - release - dev + - /^rel\/.*/ - /^(.*\/)?ci-.*$/ before_install: - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi diff --git a/appveyor.yml b/appveyor.yml index 1041615c68..778c4c4e0d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,7 @@ branches: only: - master - release + - /^rel\/.*/ - dev - /^(.*\/)?ci-.*$/ build_script: diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs index 3cbef02868..8ec43cf3a5 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AppWithDepsTests.cs @@ -17,7 +17,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public AppWithDepsTests(ITestOutputHelper logger) { _app = new AppWithDeps(logger); - _app.Prepare(); } [Fact] diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AwaitableProcess.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AwaitableProcess.cs index 4834125ebe..1700845d5e 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AwaitableProcess.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/AwaitableProcess.cs @@ -4,9 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Microsoft.Extensions.Internal; @@ -21,12 +18,12 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests private readonly ProcessSpec _spec; private BufferBlock _source; private ITestOutputHelper _logger; - private int _reading; public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) { _spec = spec; _logger = logger; + _source = new BufferBlock(); } public void Start() @@ -36,19 +33,32 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests throw new InvalidOperationException("Already started"); } - var psi = new ProcessStartInfo + _process = new Process { - UseShellExecute = false, - FileName = _spec.Executable, - WorkingDirectory = _spec.WorkingDirectory, - Arguments = ArgumentEscaper.EscapeAndConcatenate(_spec.Arguments), - RedirectStandardOutput = true, - RedirectStandardError = true + EnableRaisingEvents = true, + StartInfo = new ProcessStartInfo + { + UseShellExecute = false, + FileName = _spec.Executable, + WorkingDirectory = _spec.WorkingDirectory, + Arguments = ArgumentEscaper.EscapeAndConcatenate(_spec.Arguments), + RedirectStandardOutput = true, + RedirectStandardError = true, + Environment = + { + ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" + } + } }; - _process = Process.Start(psi); - _logger.WriteLine($"{DateTime.Now}: process start: '{psi.FileName} {psi.Arguments}'"); - StartProcessingOutput(_process.StandardOutput); - StartProcessingOutput(_process.StandardError);; + + _process.OutputDataReceived += OnData; + _process.ErrorDataReceived += OnData; + _process.Exited += OnExit; + + _process.Start(); + _process.BeginErrorReadLine(); + _process.BeginOutputReadLine(); + _logger.WriteLine($"{DateTime.Now}: process start: '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}'"); } public Task GetOutputLineAsync(string message) @@ -87,32 +97,33 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests return lines; } - private void StartProcessingOutput(StreamReader streamReader) + private void OnData(object sender, DataReceivedEventArgs args) { - _source = _source ?? new BufferBlock(); - Interlocked.Increment(ref _reading); - Task.Run(() => - { - string line; - while ((line = streamReader.ReadLine()) != null) - { - _logger.WriteLine($"{DateTime.Now}: post: '{line}'"); - _source.Post(line); - } + var line = args.Data ?? string.Empty; + _logger.WriteLine($"{DateTime.Now}: post: '{line}'"); + _source.Post(line); + } - if (Interlocked.Decrement(ref _reading) <= 0) - { - _source.Complete(); - } - }).ConfigureAwait(false); + private void OnExit(object sender, EventArgs args) + { + _source.Complete(); } public void Dispose() { - if (_process != null && !_process.HasExited) + _source.Complete(); + + if (_process != null) { - _process.KillTree(); + if (!_process.HasExited) + { + _process.KillTree(); + } + + _process.ErrorDataReceived -= OnData; + _process.OutputDataReceived -= OnData; + _process.Exited -= OnExit; } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/DotNetWatcherTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/DotNetWatcherTests.cs index 950f765135..1a6884b815 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/DotNetWatcherTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/DotNetWatcherTests.cs @@ -15,7 +15,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public DotNetWatcherTests(ITestOutputHelper logger) { _app = new KitchenSinkApp(logger); - _app.Prepare(); } [Fact] diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs index 011d64805a..b516d671da 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/GlobbingAppTests.cs @@ -20,7 +20,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public GlobbingAppTests(ITestOutputHelper logger) { _app = new GlobbingApp(logger); - _app.Prepare(); } [Theory] @@ -118,6 +117,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests [Fact] public async Task ListsFiles() { + await _app.PrepareAsync(); _app.Start(new [] { "--list" }); var lines = await _app.Process.GetAllOutputLines(); diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs index 633766d96a..214e7f2984 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/NoDepsAppTests.cs @@ -20,7 +20,6 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public NoDepsAppTests(ITestOutputHelper logger) { _app = new WatchableApp("NoDepsApp", logger); - _app.Prepare(); } [Fact] diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs index 5c96f03bfb..e212b51086 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/ProjectToolScenario.cs @@ -7,7 +7,10 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Tools.Internal; using Xunit.Abstractions; namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests @@ -55,50 +58,84 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests } } - public void Restore(string project) + public Task RestoreAsync(string project) { _logger?.WriteLine($"Restoring msbuild project in {project}"); - ExecuteCommand(project, "restore"); + return ExecuteCommandAsync(project, TimeSpan.FromSeconds(120), "restore"); } - public void Build(string project) + public Task BuildAsync(string project) { _logger?.WriteLine($"Building {project}"); - ExecuteCommand(project, "build"); + return ExecuteCommandAsync(project, TimeSpan.FromSeconds(60), "build"); } - private void ExecuteCommand(string project, params string[] arguments) + private async Task ExecuteCommandAsync(string project, TimeSpan timeout, params string[] arguments) { + var tcs = new TaskCompletionSource(); project = Path.Combine(WorkFolder, project); - var psi = new ProcessStartInfo + _logger?.WriteLine($"Project directory: '{project}'"); + + var process = new Process { - FileName = DotNetMuxer.MuxerPathOrDefault(), - Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments), - WorkingDirectory = project, - RedirectStandardOutput = true, - RedirectStandardError = true + EnableRaisingEvents = true, + StartInfo = new ProcessStartInfo + { + FileName = DotNetMuxer.MuxerPathOrDefault(), + Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments), + WorkingDirectory = project, + RedirectStandardOutput = true, + RedirectStandardError = true, + Environment = + { + ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true" + } + }, }; - var process = new Process() + void OnData(object sender, DataReceivedEventArgs args) + => _logger?.WriteLine(args.Data ?? string.Empty); + + void OnExit(object sender, EventArgs args) { - StartInfo = psi, - EnableRaisingEvents = true - }; + _logger?.WriteLine($"Process exited {process.Id}"); + tcs.TrySetResult(null); + } - void WriteLine(object sender, DataReceivedEventArgs args) - => _logger.WriteLine(args.Data); - - process.ErrorDataReceived += WriteLine; - process.OutputDataReceived += WriteLine; + process.ErrorDataReceived += OnData; + process.OutputDataReceived += OnData; + process.Exited += OnExit; process.Start(); - process.WaitForExit(); - process.ErrorDataReceived -= WriteLine; - process.OutputDataReceived -= WriteLine; + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + _logger?.WriteLine($"Started process {process.Id}: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); + + var done = await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + process.CancelErrorRead(); + process.CancelOutputRead(); + + process.ErrorDataReceived -= OnData; + process.OutputDataReceived -= OnData; + process.Exited -= OnExit; + + if (!ReferenceEquals(done, tcs.Task)) + { + if (!process.HasExited) + { + _logger?.WriteLine($"Killing process {process.Id}"); + process.KillTree(); + } + + throw new TimeoutException($"Process timed out after {timeout.TotalSeconds} seconds"); + } + + _logger?.WriteLine($"Process exited {process.Id} with code {process.ExitCode}"); if (process.ExitCode != 0) { + throw new InvalidOperationException($"Exit code {process.ExitCode}"); } } diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/WatchableApp.cs b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/WatchableApp.cs index 7be9e65c7c..580a7209ce 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/WatchableApp.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/Scenario/WatchableApp.cs @@ -53,10 +53,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests return int.Parse(pid); } - public void Prepare() + public async Task PrepareAsync() { - Scenario.Restore(_appName); - Scenario.Build(_appName); + await Scenario.RestoreAsync(_appName); + await Scenario.BuildAsync(_appName); _prepared = true; } @@ -64,7 +64,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { if (!_prepared) { - throw new InvalidOperationException("Call .Prepare() first"); + throw new InvalidOperationException($"Call {nameof(PrepareAsync)} first"); } var args = Scenario @@ -91,6 +91,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests public async Task StartWatcherAsync(string[] arguments, [CallerMemberName] string name = null) { + if (!_prepared) + { + await PrepareAsync(); + } + var args = new[] { "run", "--" }.Concat(arguments); Start(args, name); @@ -103,7 +108,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { _logger?.WriteLine("Disposing WatchableApp"); Process?.Dispose(); - Scenario.Dispose(); + Scenario?.Dispose(); } } } diff --git a/test/Microsoft.DotNet.Watcher.Tools.Tests/AssertEx.cs b/test/Microsoft.DotNet.Watcher.Tools.Tests/AssertEx.cs index aa4f94b3fa..58e0f06ebd 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.Tests/AssertEx.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.Tests/AssertEx.cs @@ -21,7 +21,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests { Func normalize = p => p.Replace('\\', '/'); var expected = new HashSet(expectedFiles.Select(normalize)); - Assert.True(expected.SetEquals(actualFiles.Select(normalize)), "File sets should be equal"); + Assert.True(expected.SetEquals(actualFiles.Where(p => !string.IsNullOrEmpty(p)).Select(normalize)), "File sets should be equal"); } } -} \ No newline at end of file +}