Merge aspnet/DotNetTools release/2.2

This commit is contained in:
Nate McMaster 2018-11-14 14:12:52 -08:00
commit 0782a9dfa9
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
28 changed files with 247 additions and 178 deletions

View File

@ -12,73 +12,12 @@
"dotnet-sql-cache": { "dotnet-sql-cache": {
"packageTypes": [ "packageTypes": [
"DotnetTool" "DotnetTool"
], ]
"Exclusions": {
"NEUTRAL_RESOURCES_LANGUAGE": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
},
"WRONG_PUBLICKEYTOKEN": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_FILE_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll": "Assembly is built by another project but bundled in our nupkg."
}
}
}, },
"dotnet-user-secrets": { "dotnet-user-secrets": {
"packageTypes": [ "packageTypes": [
"DotnetTool" "DotnetTool"
], ]
"Exclusions": {
"NEUTRAL_RESOURCES_LANGUAGE": {
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
},
"SERVICING_ATTRIBUTE": {
"tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg."
},
"WRONG_PUBLICKEYTOKEN": {
"tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_FILE_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
},
"ASSEMBLY_VERSION_MISMATCH": {
"tools/netcoreapp2.1/any/Newtonsoft.Json.dll": "Assembly is built by another project but bundled in our nupkg.",
"tools/netcoreapp2.1/any/System.Runtime.CompilerServices.Unsafe.dll": "Assembly is built by another project but bundled in our nupkg."
}
}
}, },
"dotnet-dev-certs": { "dotnet-dev-certs": {
"packageTypes": [ "packageTypes": [
@ -88,7 +27,7 @@
"Microsoft.AspNetCore.DeveloperCertificates.XPlat": { "Microsoft.AspNetCore.DeveloperCertificates.XPlat": {
"Exclusions": { "Exclusions": {
"DOC_MISSING": { "DOC_MISSING": {
"lib/netcoreapp2.1/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package" "lib/netcoreapp2.2/Microsoft.AspNetCore.DeveloperCertificates.XPlat.dll": "Docs not required to shipoob package"
} }
} }
} }

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<Description>Package for the CLI first run experience.</Description> <Description>Package for the CLI first run experience.</Description>
<DefineConstants>$(DefineConstants);XPLAT</DefineConstants> <DefineConstants>$(DefineConstants);XPLAT</DefineConstants>
<PackageTags>aspnet;cli</PackageTags> <PackageTags>aspnet;cli</PackageTags>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<Description>Command line tool to generate certificates used in ASP.NET Core during development.</Description> <Description>Command line tool to generate certificates used in ASP.NET Core during development.</Description>
<RootNamespace>Microsoft.AspNetCore.DeveloperCertificates.Tools</RootNamespace> <RootNamespace>Microsoft.AspNetCore.DeveloperCertificates.Tools</RootNamespace>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<Description>Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching.</Description> <Description>Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching.</Description>
<PackageTags>cache;distributedcache;sqlserver</PackageTags> <PackageTags>cache;distributedcache;sqlserver</PackageTags>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<Description>Command line tool to manage user secrets for Microsoft.Extensions.Configuration.</Description> <Description>Command line tool to manage user secrets for Microsoft.Extensions.Configuration.</Description>
<GenerateUserSecretsAttribute>false</GenerateUserSecretsAttribute> <GenerateUserSecretsAttribute>false</GenerateUserSecretsAttribute>

View File

@ -35,7 +35,7 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests
private const string ProjectTemplate = @"<Project ToolsVersion=""15.0"" Sdk=""Microsoft.NET.Sdk""> private const string ProjectTemplate = @"<Project ToolsVersion=""15.0"" Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1</TargetFrameworks> <TargetFrameworks>netcoreapp2.2</TargetFrameworks>
{0} {0}
<EnableDefaultCompileItems>false</EnableDefaultCompileItems> <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup> </PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>Microsoft.Extensions.SecretManager.Tools.Tests</AssemblyName> <AssemblyName>Microsoft.Extensions.SecretManager.Tools.Tests</AssemblyName>
</PropertyGroup> </PropertyGroup>

View File

@ -53,6 +53,10 @@ Environment variables:
DOTNET_WATCH DOTNET_WATCH
dotnet-watch sets this variable to '1' on all child processes launched. dotnet-watch sets this variable to '1' on all child processes launched.
DOTNET_WATCH_ITERATION
dotnet-watch sets this variable to '1' and increments by one each time
a file is changed and the command is restarted.
Remarks: Remarks:
The special option '--' is used to delimit the end of the options and The special option '--' is used to delimit the end of the options and
the beginning of arguments that will be passed to the child dotnet process. the beginning of arguments that will be passed to the child dotnet process.

View File

@ -1,7 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.DotNet.Watcher.Internal; using Microsoft.DotNet.Watcher.Internal;
@ -32,8 +33,13 @@ namespace Microsoft.DotNet.Watcher
cancellationToken.Register(state => ((TaskCompletionSource<object>) state).TrySetResult(null), cancellationToken.Register(state => ((TaskCompletionSource<object>) state).TrySetResult(null),
cancelledTaskSource); cancelledTaskSource);
var iteration = 1;
while (true) while (true)
{ {
processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = iteration.ToString(CultureInfo.InvariantCulture);
iteration++;
var fileSet = await fileSetFactory.CreateAsync(cancellationToken); var fileSet = await fileSetFactory.CreateAsync(cancellationToken);
if (fileSet == null) if (fileSet == null)
@ -69,13 +75,15 @@ namespace Microsoft.DotNet.Watcher
await Task.WhenAll(processTask, fileSetTask); await Task.WhenAll(processTask, fileSetTask);
if (processTask.Result == 0) if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
{ {
_reporter.Output("Exited"); // Only show this error message if the process exited non-zero due to a normal process exit.
// Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
_reporter.Error($"Exited with error code {processTask.Result}");
} }
else else
{ {
_reporter.Error($"Exited with error code {processTask.Result}"); _reporter.Output("Exited");
} }
if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested) if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)

View File

@ -10,6 +10,8 @@ namespace Microsoft.DotNet.Watcher.Internal
{ {
internal class DotnetFileWatcher : IFileSystemWatcher internal class DotnetFileWatcher : IFileSystemWatcher
{ {
private volatile bool _disposed;
private readonly Func<string, FileSystemWatcher> _watcherFactory; private readonly Func<string, FileSystemWatcher> _watcherFactory;
private FileSystemWatcher _fileSystemWatcher; private FileSystemWatcher _fileSystemWatcher;
@ -46,6 +48,11 @@ namespace Microsoft.DotNet.Watcher.Internal
private void WatcherErrorHandler(object sender, ErrorEventArgs e) private void WatcherErrorHandler(object sender, ErrorEventArgs e)
{ {
if (_disposed)
{
return;
}
var exception = e.GetException(); var exception = e.GetException();
// Win32Exception may be triggered when setting EnableRaisingEvents on a file system type // Win32Exception may be triggered when setting EnableRaisingEvents on a file system type
@ -62,6 +69,11 @@ namespace Microsoft.DotNet.Watcher.Internal
private void WatcherRenameHandler(object sender, RenamedEventArgs e) private void WatcherRenameHandler(object sender, RenamedEventArgs e)
{ {
if (_disposed)
{
return;
}
NotifyChange(e.OldFullPath); NotifyChange(e.OldFullPath);
NotifyChange(e.FullPath); NotifyChange(e.FullPath);
@ -79,6 +91,11 @@ namespace Microsoft.DotNet.Watcher.Internal
private void WatcherChangeHandler(object sender, FileSystemEventArgs e) private void WatcherChangeHandler(object sender, FileSystemEventArgs e)
{ {
if (_disposed)
{
return;
}
NotifyChange(e.FullPath); NotifyChange(e.FullPath);
} }
@ -98,15 +115,7 @@ namespace Microsoft.DotNet.Watcher.Internal
{ {
enableEvents = _fileSystemWatcher.EnableRaisingEvents; enableEvents = _fileSystemWatcher.EnableRaisingEvents;
_fileSystemWatcher.EnableRaisingEvents = false; DisposeInnerWatcher();
_fileSystemWatcher.Created -= WatcherChangeHandler;
_fileSystemWatcher.Deleted -= WatcherChangeHandler;
_fileSystemWatcher.Changed -= WatcherChangeHandler;
_fileSystemWatcher.Renamed -= WatcherRenameHandler;
_fileSystemWatcher.Error -= WatcherErrorHandler;
_fileSystemWatcher.Dispose();
} }
_fileSystemWatcher = _watcherFactory(BasePath); _fileSystemWatcher = _watcherFactory(BasePath);
@ -122,6 +131,19 @@ namespace Microsoft.DotNet.Watcher.Internal
} }
} }
private void DisposeInnerWatcher()
{
_fileSystemWatcher.EnableRaisingEvents = false;
_fileSystemWatcher.Created -= WatcherChangeHandler;
_fileSystemWatcher.Deleted -= WatcherChangeHandler;
_fileSystemWatcher.Changed -= WatcherChangeHandler;
_fileSystemWatcher.Renamed -= WatcherRenameHandler;
_fileSystemWatcher.Error -= WatcherErrorHandler;
_fileSystemWatcher.Dispose();
}
public bool EnableRaisingEvents public bool EnableRaisingEvents
{ {
get => _fileSystemWatcher.EnableRaisingEvents; get => _fileSystemWatcher.EnableRaisingEvents;
@ -130,7 +152,8 @@ namespace Microsoft.DotNet.Watcher.Internal
public void Dispose() public void Dispose()
{ {
_fileSystemWatcher.Dispose(); _disposed = true;
DisposeInnerWatcher();
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -33,7 +33,7 @@ namespace Microsoft.DotNet.Watcher.Internal
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
using (var process = CreateProcess(processSpec)) using (var process = CreateProcess(processSpec))
using (var processState = new ProcessState(process)) using (var processState = new ProcessState(process, _reporter))
{ {
cancellationToken.Register(() => processState.TryKill()); cancellationToken.Register(() => processState.TryKill());
@ -97,27 +97,36 @@ namespace Microsoft.DotNet.Watcher.Internal
private class ProcessState : IDisposable private class ProcessState : IDisposable
{ {
private readonly IReporter _reporter;
private readonly Process _process; private readonly Process _process;
private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>(); private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
private volatile bool _disposed; private volatile bool _disposed;
public ProcessState(Process process) public ProcessState(Process process, IReporter reporter)
{ {
_reporter = reporter;
_process = process; _process = process;
_process.Exited += OnExited; _process.Exited += OnExited;
Task = _tcs.Task.ContinueWith(_ => Task = _tcs.Task.ContinueWith(_ =>
{ {
// We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously try
// this code used Process.Exited, which could result in us missing some output due to the ordering of
// events.
//
// See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_
if (!process.WaitForExit(Int32.MaxValue))
{ {
throw new TimeoutException(); // We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously
} // this code used Process.Exited, which could result in us missing some output due to the ordering of
// events.
//
// See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_
if (!_process.WaitForExit(Int32.MaxValue))
{
throw new TimeoutException();
}
process.WaitForExit(); _process.WaitForExit();
}
catch (InvalidOperationException)
{
// suppress if this throws if no process is associated with this object anymore.
}
}); });
} }
@ -125,15 +134,26 @@ namespace Microsoft.DotNet.Watcher.Internal
public void TryKill() public void TryKill()
{ {
if (_disposed)
{
return;
}
try try
{ {
if (!_process.HasExited) if (!_process.HasExited)
{ {
_reporter.Verbose($"Killing process {_process.Id}");
_process.KillTree(); _process.KillTree();
} }
} }
catch catch (Exception ex)
{ } {
_reporter.Verbose($"Error while killing process '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}': {ex.Message}");
#if DEBUG
_reporter.Verbose(ex.ToString());
#endif
}
} }
private void OnExited(object sender, EventArgs args) private void OnExited(object sender, EventArgs args)
@ -143,8 +163,8 @@ namespace Microsoft.DotNet.Watcher.Internal
{ {
if (!_disposed) if (!_disposed)
{ {
_disposed = true;
TryKill(); TryKill();
_disposed = true;
_process.Exited -= OnExited; _process.Exited -= OnExited;
_process.Dispose(); _process.Dispose();
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<Description>Command line tool to watch for source file changes during development and restart the dotnet command.</Description> <Description>Command line tool to watch for source file changes during development and restart the dotnet command.</Description>
<RootNamespace>Microsoft.DotNet.Watcher.Tools</RootNamespace> <RootNamespace>Microsoft.DotNet.Watcher.Tools</RootNamespace>

View File

@ -1,12 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -17,16 +17,26 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
{ {
private Process _process; private Process _process;
private readonly ProcessSpec _spec; private readonly ProcessSpec _spec;
private readonly List<string> _lines;
private BufferBlock<string> _source; private BufferBlock<string> _source;
private ITestOutputHelper _logger; private ITestOutputHelper _logger;
private TaskCompletionSource<int> _exited;
public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger) public AwaitableProcess(ProcessSpec spec, ITestOutputHelper logger)
{ {
_spec = spec; _spec = spec;
_logger = logger; _logger = logger;
_source = new BufferBlock<string>(); _source = new BufferBlock<string>();
_lines = new List<string>();
_exited = new TaskCompletionSource<int>();
} }
public IEnumerable<string> Output => _lines;
public Task Exited => _exited.Task;
public int Id => _process.Id;
public void Start() public void Start()
{ {
if (_process != null) if (_process != null)
@ -52,6 +62,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
} }
}; };
foreach (var env in _spec.EnvironmentVariables)
{
_process.StartInfo.EnvironmentVariables[env.Key] = env.Value;
}
_process.OutputDataReceived += OnData; _process.OutputDataReceived += OnData;
_process.ErrorDataReceived += OnData; _process.ErrorDataReceived += OnData;
_process.Exited += OnExit; _process.Exited += OnExit;
@ -65,24 +80,30 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
public async Task<string> GetOutputLineAsync(string message, TimeSpan timeout) public async Task<string> GetOutputLineAsync(string message, TimeSpan timeout)
{ {
_logger.WriteLine($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec."); _logger.WriteLine($"Waiting for output line [msg == '{message}']. Will wait for {timeout.TotalSeconds} sec.");
return await GetOutputLineAsync(m => message == m).TimeoutAfter(timeout); var cts = new CancellationTokenSource();
cts.CancelAfter(timeout);
return await GetOutputLineAsync($"[msg == '{message}']", m => string.Equals(m, message, StringComparison.Ordinal), cts.Token);
} }
public async Task<string> GetOutputLineStartsWithAsync(string message, TimeSpan timeout) public async Task<string> GetOutputLineStartsWithAsync(string message, TimeSpan timeout)
{ {
_logger.WriteLine($"Waiting for output line [msg.StartsWith('{message}')]. Will wait for {timeout.TotalSeconds} sec."); _logger.WriteLine($"Waiting for output line [msg.StartsWith('{message}')]. Will wait for {timeout.TotalSeconds} sec.");
return await GetOutputLineAsync(m => m.StartsWith(message)).TimeoutAfter(timeout); var cts = new CancellationTokenSource();
cts.CancelAfter(timeout);
return await GetOutputLineAsync($"[msg.StartsWith('{message}')]", m => m != null && m.StartsWith(message, StringComparison.Ordinal), cts.Token);
} }
private async Task<string> GetOutputLineAsync(Predicate<string> predicate) private async Task<string> GetOutputLineAsync(string predicateName, Predicate<string> predicate, CancellationToken cancellationToken)
{ {
while (!_source.Completion.IsCompleted) while (!_source.Completion.IsCompleted)
{ {
while (await _source.OutputAvailableAsync()) while (await _source.OutputAvailableAsync(cancellationToken))
{ {
var next = await _source.ReceiveAsync(); var next = await _source.ReceiveAsync(cancellationToken);
_logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); _lines.Add(next);
if (predicate(next)) var match = predicate(next);
_logger.WriteLine($"{DateTime.Now}: recv: '{next}'. {(match ? "Matches" : "Does not match")} condition '{predicateName}'.");
if (match)
{ {
return next; return next;
} }
@ -92,14 +113,14 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
return null; return null;
} }
public async Task<IList<string>> GetAllOutputLines() public async Task<IList<string>> GetAllOutputLinesAsync(CancellationToken cancellationToken)
{ {
var lines = new List<string>(); var lines = new List<string>();
while (!_source.Completion.IsCompleted) while (!_source.Completion.IsCompleted)
{ {
while (await _source.OutputAvailableAsync()) while (await _source.OutputAvailableAsync(cancellationToken))
{ {
var next = await _source.ReceiveAsync(); var next = await _source.ReceiveAsync(cancellationToken);
_logger.WriteLine($"{DateTime.Now}: recv: '{next}'"); _logger.WriteLine($"{DateTime.Now}: recv: '{next}'");
lines.Add(next); lines.Add(next);
} }
@ -119,6 +140,8 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
// Wait to ensure the process has exited and all output consumed // Wait to ensure the process has exited and all output consumed
_process.WaitForExit(); _process.WaitForExit();
_source.Complete(); _source.Complete();
_exited.TrySetResult(_process.ExitCode);
_logger.WriteLine($"Process {_process.Id} has exited");
} }
public void Dispose() public void Dispose()
@ -135,6 +158,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
_process.ErrorDataReceived -= OnData; _process.ErrorDataReceived -= OnData;
_process.OutputDataReceived -= OnData; _process.OutputDataReceived -= OnData;
_process.Exited -= OnExit; _process.Exited -= OnExit;
_process.Dispose();
} }
} }
} }

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -11,10 +13,12 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
{ {
public class DotNetWatcherTests : IDisposable public class DotNetWatcherTests : IDisposable
{ {
private readonly ITestOutputHelper _logger;
private readonly KitchenSinkApp _app; private readonly KitchenSinkApp _app;
public DotNetWatcherTests(ITestOutputHelper logger) public DotNetWatcherTests(ITestOutputHelper logger)
{ {
_logger = logger;
_app = new KitchenSinkApp(logger); _app = new KitchenSinkApp(logger);
} }
@ -30,6 +34,37 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
Assert.Equal("1", envValue); Assert.Equal("1", envValue);
} }
[Fact]
public async Task RunsWithIterationEnvVariable()
{
await _app.StartWatcherAsync();
var source = Path.Combine(_app.SourceDirectory, "Program.cs");
var contents = File.ReadAllText(source);
const string messagePrefix = "DOTNET_WATCH_ITERATION = ";
for (var i = 1; i <= 3; i++)
{
var message = await _app.Process.GetOutputLineStartsWithAsync(messagePrefix, TimeSpan.FromMinutes(2));
var count = int.Parse(message.Substring(messagePrefix.Length), CultureInfo.InvariantCulture);
Assert.Equal(i, count);
await _app.IsWaitingForFileChange();
try
{
File.SetLastWriteTime(source, DateTime.Now);
await _app.HasRestarted();
}
catch (Exception ex)
{
_logger.WriteLine("Retrying. First attempt to restart app failed: " + ex.Message);
// retry
File.SetLastWriteTime(source, DateTime.Now);
await _app.HasRestarted();
}
}
}
public void Dispose() public void Dispose()
{ {
_app.Dispose(); _app.Dispose();

View File

@ -304,42 +304,44 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
private void AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher) private void AssertFileChangeRaisesEvent(string directory, IFileSystemWatcher watcher)
{ {
var semaphoreSlim = new SemaphoreSlim(0); using (var semaphoreSlim = new SemaphoreSlim(0))
var expectedPath = Path.Combine(directory, Path.GetRandomFileName());
EventHandler<string> handler = (object _, string f) =>
{ {
_output.WriteLine("File changed: " + f); var expectedPath = Path.Combine(directory, Path.GetRandomFileName());
EventHandler<string> handler = (object _, string f) =>
{
_output.WriteLine("File changed: " + f);
try
{
if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase))
{
semaphoreSlim.Release();
}
}
catch (ObjectDisposedException)
{
// There's a known race condition here:
// even though we tell the watcher to stop raising events and we unsubscribe the handler
// there might be in-flight events that will still process. Since we dispose the reset
// event, this code will fail if the handler executes after Dispose happens.
}
};
File.AppendAllText(expectedPath, " ");
watcher.OnFileChange += handler;
try try
{ {
if (string.Equals(f, expectedPath, StringComparison.OrdinalIgnoreCase)) // On Unix the file write time is in 1s increments;
{ // if we don't wait, there's a chance that the polling
semaphoreSlim.Release(); // watcher will not detect the change
} Thread.Sleep(1000);
File.AppendAllText(expectedPath, " ");
Assert.True(semaphoreSlim.Wait(DefaultTimeout), "Expected a file change event for " + expectedPath);
} }
catch (ObjectDisposedException) finally
{ {
// There's a known race condition here: watcher.OnFileChange -= handler;
// even though we tell the watcher to stop raising events and we unsubscribe the handler
// there might be in-flight events that will still process. Since we dispose the reset
// event, this code will fail if the handler executes after Dispose happens.
} }
};
File.AppendAllText(expectedPath, " ");
watcher.OnFileChange += handler;
try
{
// 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);
File.AppendAllText(expectedPath, " ");
Assert.True(semaphoreSlim.Wait(DefaultTimeout), "Expected a file change event for " + expectedPath);
}
finally
{
watcher.OnFileChange -= handler;
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.DotNet.Watcher.Tools.Tests; using Microsoft.DotNet.Watcher.Tools.Tests;
using Xunit; using Xunit;
@ -101,7 +102,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
{ {
await _app.PrepareAsync(); await _app.PrepareAsync();
_app.Start(new [] { "--list" }); _app.Start(new [] { "--list" });
var lines = await _app.Process.GetAllOutputLines(); var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
var lines = await _app.Process.GetAllOutputLinesAsync(cts.Token);
var files = lines.Where(l => !l.StartsWith("watch :"));
AssertEx.EqualFileList( AssertEx.EqualFileList(
_app.Scenario.WorkFolder, _app.Scenario.WorkFolder,
@ -111,7 +115,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
"GlobbingApp/include/Foo.cs", "GlobbingApp/include/Foo.cs",
"GlobbingApp/GlobbingApp.csproj", "GlobbingApp/GlobbingApp.csproj",
}, },
lines); files);
} }
public void Dispose() public void Dispose()

View File

@ -15,10 +15,12 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
private readonly WatchableApp _app; private readonly WatchableApp _app;
private readonly ITestOutputHelper _output;
public NoDepsAppTests(ITestOutputHelper logger) public NoDepsAppTests(ITestOutputHelper logger)
{ {
_app = new WatchableApp("NoDepsApp", logger); _app = new WatchableApp("NoDepsApp", logger);
_output = logger;
} }
[Fact] [Fact]
@ -33,11 +35,10 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
File.WriteAllText(fileToChange, programCs); File.WriteAllText(fileToChange, programCs);
await _app.HasRestarted(); await _app.HasRestarted();
Assert.DoesNotContain(_app.Process.Output, l => l.StartsWith("Exited with error code"));
var pid2 = await _app.GetProcessId(); var pid2 = await _app.GetProcessId();
Assert.NotEqual(pid, pid2); Assert.NotEqual(pid, pid2);
// first app should have shut down
Assert.Throws<ArgumentException>(() => Process.GetProcessById(pid));
} }
[Fact] [Fact]
@ -49,10 +50,19 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
await _app.IsWaitingForFileChange(); await _app.IsWaitingForFileChange();
var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs"); var fileToChange = Path.Combine(_app.SourceDirectory, "Program.cs");
var programCs = File.ReadAllText(fileToChange);
File.WriteAllText(fileToChange, programCs);
await _app.HasRestarted(); try
{
File.SetLastWriteTime(fileToChange, DateTime.Now);
await _app.HasRestarted();
}
catch
{
// retry
File.SetLastWriteTime(fileToChange, DateTime.Now);
await _app.HasRestarted();
}
var pid2 = await _app.GetProcessId(); var pid2 = await _app.GetProcessId();
Assert.NotEqual(pid, pid2); Assert.NotEqual(pid, pid2);
await _app.HasExited(); // process should exit after run await _app.HasExited(); // process should exit after run

View File

@ -28,23 +28,25 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
{ {
_tempDir _tempDir
.WithCSharpProject("testproj") .WithCSharpProject("testproj")
.WithTargetFrameworks("netcoreapp1.0") .WithTargetFrameworks("netcoreapp2.2")
.Dir() .Dir()
.WithFile("Program.cs") .WithFile("Program.cs")
.Create(); .Create();
var stdout = new StringBuilder(); var output = new StringBuilder();
_console.Out = new StringWriter(stdout); _console.Error = _console.Out = new StringWriter(output);
var program = new Program(_console, _tempDir.Root) using (var app = new Program(_console, _tempDir.Root))
.RunAsync(new[] { "run" }); {
var run = app.RunAsync(new[] { "run" });
await _console.CancelKeyPressSubscribed.TimeoutAfter(TimeSpan.FromSeconds(30)); await _console.CancelKeyPressSubscribed.TimeoutAfter(TimeSpan.FromSeconds(30));
_console.ConsoleCancelKey(); _console.ConsoleCancelKey();
var exitCode = await program.TimeoutAfter(TimeSpan.FromSeconds(30)); var exitCode = await run.TimeoutAfter(TimeSpan.FromSeconds(30));
Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", stdout.ToString()); Assert.Contains("Shutdown requested. Press Ctrl+C again to force exit.", output.ToString());
Assert.Equal(0, exitCode); Assert.Equal(0, exitCode);
}
} }
public void Dispose() public void Dispose()

View File

@ -1,8 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -12,7 +11,6 @@ using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Tools.Internal;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests

View File

@ -47,7 +47,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
public async Task HasExited() public async Task HasExited()
{ {
await Process.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut); await Process.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut);
await Process.GetOutputLineAsync(WatchExitedMessage, DefaultMessageTimeOut); await Process.GetOutputLineStartsWithAsync(WatchExitedMessage, DefaultMessageTimeOut);
} }
public async Task IsWaitingForFileChange() public async Task IsWaitingForFileChange()

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems> <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>

View File

@ -13,6 +13,7 @@ namespace KitchenSink
Console.WriteLine("Started"); Console.WriteLine("Started");
Console.WriteLine("PID = " + Process.GetCurrentProcess().Id); Console.WriteLine("PID = " + Process.GetCurrentProcess().Id);
Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH")); Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH"));
Console.WriteLine("DOTNET_WATCH_ITERATION = " + Environment.GetEnvironmentVariable("DOTNET_WATCH_ITERATION"));
} }
} }
} }

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>Microsoft.DotNet.Watcher.Tools.Tests</AssemblyName> <AssemblyName>Microsoft.DotNet.Watcher.Tools.Tests</AssemblyName>
<DefaultItemExcludes>$(DefaultItemExcludes);TestProjects\**\*</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);TestProjects\**\*</DefaultItemExcludes>
</PropertyGroup> </PropertyGroup>

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -8,14 +8,13 @@ namespace Microsoft.Extensions.Tools.Internal
public static class CliContext public static class CliContext
{ {
/// <summary> /// <summary>
/// dotnet --verbose subcommand /// dotnet -d|--diagnostics subcommand
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static bool IsGlobalVerbose() public static bool IsGlobalVerbose()
{ {
bool globalVerbose; bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out bool globalVerbose);
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out globalVerbose);
return globalVerbose; return globalVerbose;
} }
} }
} }