diff --git a/src/dotnet/BuildServer/BuildServerException.cs b/src/dotnet/BuildServer/BuildServerException.cs new file mode 100644 index 000000000..1bbdd9fb4 --- /dev/null +++ b/src/dotnet/BuildServer/BuildServerException.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.DotNet.BuildServer +{ + internal class BuildServerException : Exception + { + public BuildServerException() + { + } + + public BuildServerException(string message) : base(message) + { + } + + public BuildServerException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/dotnet/BuildServer/BuildServerProvider.cs b/src/dotnet/BuildServer/BuildServerProvider.cs new file mode 100644 index 000000000..e292d854a --- /dev/null +++ b/src/dotnet/BuildServer/BuildServerProvider.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Configurer; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.BuildServer +{ + internal class BuildServerProvider : IBuildServerProvider + { + private readonly IFileSystem _fileSystem; + private readonly IEnvironmentProvider _environmentProvider; + + public BuildServerProvider( + IFileSystem fileSystem = null, + IEnvironmentProvider environmentProvider = null) + { + _fileSystem = fileSystem ?? FileSystemWrapper.Default; + _environmentProvider = environmentProvider ?? new EnvironmentProvider(); + } + + public IEnumerable EnumerateBuildServers(ServerEnumerationFlags flags = ServerEnumerationFlags.All) + { + if ((flags & ServerEnumerationFlags.MSBuild) == ServerEnumerationFlags.MSBuild) + { + // Yield a single MSBuild server (handles server discovery itself) + // TODO: use pid file enumeration when supported by the server (https://github.com/dotnet/cli/issues/9113) + yield return new MSBuildServer(); + } + + if ((flags & ServerEnumerationFlags.VBCSCompiler) == ServerEnumerationFlags.VBCSCompiler) + { + // Yield a single VB/C# compiler (handles server discovery itself) + // TODO: use pid file enumeration when supported by the server (https://github.com/dotnet/cli/issues/9112) + yield return new VBCSCompilerServer(); + } + + // TODO: remove or amend this check when the following issues are resolved: + // https://github.com/dotnet/cli/issues/9112 + // https://github.com/dotnet/cli/issues/9113 + if ((flags & ServerEnumerationFlags.Razor) != ServerEnumerationFlags.Razor) + { + yield break; + } + + var directory = GetPidFileDirectory(); + + if (!_fileSystem.Directory.Exists(directory.Value)) + { + yield break; + } + + foreach (var path in _fileSystem.Directory.EnumerateFiles(directory.Value, "*")) + { + if ((flags & ServerEnumerationFlags.Razor) == ServerEnumerationFlags.Razor && + Path.GetFileName(path).StartsWith(RazorPidFile.FilePrefix)) + { + var file = RazorPidFile.Read(new FilePath(path), _fileSystem); + if (file != null) + { + yield return new RazorServer(file); + } + } + } + } + + public DirectoryPath GetPidFileDirectory() + { + var directory = _environmentProvider.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY"); + if (!string.IsNullOrEmpty(directory)) + { + return new DirectoryPath(directory); + } + + return new DirectoryPath( + Path.Combine( + CliFolderPathCalculator.DotnetUserProfileFolderPath, + "pids", + "build")); + } + } +} diff --git a/src/dotnet/BuildServer/IBuildServerManager.cs b/src/dotnet/BuildServer/IBuildServer.cs similarity index 69% rename from src/dotnet/BuildServer/IBuildServerManager.cs rename to src/dotnet/BuildServer/IBuildServer.cs index 76d11a4be..4549567a1 100644 --- a/src/dotnet/BuildServer/IBuildServerManager.cs +++ b/src/dotnet/BuildServer/IBuildServer.cs @@ -6,10 +6,12 @@ using System.Threading.Tasks; namespace Microsoft.DotNet.BuildServer { - internal interface IBuildServerManager + internal interface IBuildServer { - string ServerName { get; } + int ProcessId { get; } - Task ShutdownServerAsync(); + string Name { get; } + + void Shutdown(); } } diff --git a/src/dotnet/BuildServer/IBuildServerProvider.cs b/src/dotnet/BuildServer/IBuildServerProvider.cs new file mode 100644 index 000000000..ef4a5b231 --- /dev/null +++ b/src/dotnet/BuildServer/IBuildServerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.DotNet.BuildServer +{ + [Flags] + internal enum ServerEnumerationFlags + { + None = 0, + MSBuild = 1, + VBCSCompiler = 2, + Razor = 4, + All = MSBuild | VBCSCompiler | Razor + } + + internal interface IBuildServerProvider + { + IEnumerable EnumerateBuildServers(ServerEnumerationFlags flags = ServerEnumerationFlags.All); + } +} diff --git a/src/dotnet/BuildServer/IRazorAssemblyResolver.cs b/src/dotnet/BuildServer/IRazorAssemblyResolver.cs deleted file mode 100644 index 31ad1a3e1..000000000 --- a/src/dotnet/BuildServer/IRazorAssemblyResolver.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.EnvironmentAbstractions; - -namespace Microsoft.DotNet.BuildServer -{ - internal interface IRazorAssemblyResolver - { - IEnumerable EnumerateRazorToolAssemblies(); - } -} diff --git a/src/dotnet/BuildServer/LocalizableStrings.resx b/src/dotnet/BuildServer/LocalizableStrings.resx index 198c6ee2e..8534ae97a 100644 --- a/src/dotnet/BuildServer/LocalizableStrings.resx +++ b/src/dotnet/BuildServer/LocalizableStrings.resx @@ -126,7 +126,7 @@ Razor build server - - a Razor project was not found in the current directory. + + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/MSBuildServer.cs b/src/dotnet/BuildServer/MSBuildServer.cs new file mode 100644 index 000000000..6d25da9f8 --- /dev/null +++ b/src/dotnet/BuildServer/MSBuildServer.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Build.Execution; + +namespace Microsoft.DotNet.BuildServer +{ + internal class MSBuildServer : IBuildServer + { + public int ProcessId => 0; // Not yet used + + public string Name => LocalizableStrings.MSBuildServer; + + public void Shutdown() + { + BuildManager.DefaultBuildManager.ShutdownAllNodes(); + } + } +} diff --git a/src/dotnet/BuildServer/MSBuildServerManager.cs b/src/dotnet/BuildServer/MSBuildServerManager.cs deleted file mode 100644 index d0667a600..000000000 --- a/src/dotnet/BuildServer/MSBuildServerManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Threading.Tasks; -using Microsoft.Build.Execution; - -namespace Microsoft.DotNet.BuildServer -{ - internal class MSBuildServerManager : IBuildServerManager - { - public string ServerName => LocalizableStrings.MSBuildServer; - - public Task ShutdownServerAsync() - { - return Task.Run(() => { - try - { - BuildManager.DefaultBuildManager.ShutdownAllNodes(); - return new Result(ResultKind.Success); - } - catch (Exception ex) - { - return new Result(ex); - } - }); - } - } -} diff --git a/src/dotnet/BuildServer/RazorAssemblyResolver.cs b/src/dotnet/BuildServer/RazorAssemblyResolver.cs deleted file mode 100644 index 517fc2830..000000000 --- a/src/dotnet/BuildServer/RazorAssemblyResolver.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.Build.Execution; -using Microsoft.Extensions.EnvironmentAbstractions; - -namespace Microsoft.DotNet.BuildServer -{ - internal class RazorAssemblyResolver : IRazorAssemblyResolver - { - private readonly IDirectory _directory; - - public RazorAssemblyResolver(IDirectory directory = null) - { - _directory = directory ?? FileSystemWrapper.Default.Directory; - } - - public IEnumerable EnumerateRazorToolAssemblies() - { - HashSet seen = new HashSet(); - - var globalProperties = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - // This property disables default item globbing to improve performance - // This should be safe because we are not evaluating items, only properties - { Constants.EnableDefaultItems, "false" } - }; - - foreach (var projectFile in _directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.*proj")) - { - var project = new ProjectInstance(projectFile, globalProperties, null); - var path = project.GetPropertyValue("_RazorToolAssembly"); - if (string.IsNullOrEmpty(path)) - { - continue; - } - - if (!seen.Add(path)) - { - continue; - } - - yield return new FilePath(path); - } - } - } -} diff --git a/src/dotnet/BuildServer/RazorPidFile.cs b/src/dotnet/BuildServer/RazorPidFile.cs new file mode 100644 index 000000000..dd8806c9e --- /dev/null +++ b/src/dotnet/BuildServer/RazorPidFile.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Text; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.BuildServer +{ + internal class RazorPidFile + { + public const string RazorServerType = "rzc"; + public const string FilePrefix = "rzc-"; + + public RazorPidFile(FilePath path, int processId, FilePath serverPath, string pipeName) + { + Path = path; + ProcessId = processId; + ServerPath = serverPath; + PipeName = pipeName ?? throw new ArgumentNullException(pipeName); + } + + public FilePath Path { get; } + + public int ProcessId; + + public FilePath ServerPath { get; } + + public string PipeName { get; } + + public static RazorPidFile Read(FilePath path, IFileSystem fileSystem = null) + { + fileSystem = fileSystem ?? FileSystemWrapper.Default; + + using (var stream = fileSystem.File.OpenRead(path.Value)) + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + if (!int.TryParse(reader.ReadLine(), out var processId)) + { + return null; + } + + if (reader.ReadLine() != RazorServerType) + { + return null; + } + + var serverPath = reader.ReadLine(); + if (string.IsNullOrEmpty(serverPath)) + { + return null; + } + + var pipeName = reader.ReadLine(); + if (string.IsNullOrEmpty(pipeName)) + { + return null; + } + + return new RazorPidFile(path, processId, new FilePath(serverPath), pipeName); + } + } + } +} diff --git a/src/dotnet/BuildServer/RazorServer.cs b/src/dotnet/BuildServer/RazorServer.cs new file mode 100644 index 000000000..95d06c390 --- /dev/null +++ b/src/dotnet/BuildServer/RazorServer.cs @@ -0,0 +1,77 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.BuildServer +{ + internal class RazorServer : IBuildServer + { + private readonly ICommandFactory _commandFactory; + private readonly IFileSystem _fileSystem; + + public RazorServer( + RazorPidFile pidFile, + ICommandFactory commandFactory = null, + IFileSystem fileSystem = null) + { + PidFile = pidFile ?? throw new ArgumentNullException(nameof(pidFile)); + _commandFactory = commandFactory ?? new DotNetCommandFactory(alwaysRunOutOfProc: true); + _fileSystem = fileSystem ?? FileSystemWrapper.Default; + } + + public int ProcessId => PidFile.ProcessId; + + public string Name => LocalizableStrings.RazorServer; + + public RazorPidFile PidFile { get; } + + public void Shutdown() + { + var command = _commandFactory + .Create( + "exec", + new string[] { + PidFile.ServerPath.Value, + "shutdown", + "-w", // Wait for exit + "-p", // Pipe name + PidFile.PipeName + }) + .CaptureStdOut() + .CaptureStdErr(); + + var result = command.Execute(); + if (result.ExitCode != 0) + { + throw new BuildServerException( + string.Format( + LocalizableStrings.ShutdownCommandFailed, + result.StdErr)); + } + + // After a successful shutdown, ensure the pid file is deleted + // If the pid file was left behind due to a rude exit, this ensures we don't try to shut it down again + try + { + if (_fileSystem.File.Exists(PidFile.Path.Value)) + { + _fileSystem.File.Delete(PidFile.Path.Value); + } + } + catch (UnauthorizedAccessException) + { + } + catch (IOException) + { + } + } + } +} diff --git a/src/dotnet/BuildServer/RazorServerManager.cs b/src/dotnet/BuildServer/RazorServerManager.cs deleted file mode 100644 index 526724d6e..000000000 --- a/src/dotnet/BuildServer/RazorServerManager.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Build.Exceptions; -using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Tools; -using Microsoft.Extensions.EnvironmentAbstractions; - -namespace Microsoft.DotNet.BuildServer -{ - internal class RazorServerManager : IBuildServerManager - { - private readonly IRazorAssemblyResolver _resolver; - private readonly ICommandFactory _commandFactory; - - public RazorServerManager(IRazorAssemblyResolver resolver = null, ICommandFactory commandFactory = null) - { - _resolver = resolver ?? new RazorAssemblyResolver(); - _commandFactory = commandFactory ?? new DotNetCommandFactory(alwaysRunOutOfProc: true); - } - - public string ServerName => LocalizableStrings.RazorServer; - - public Task ShutdownServerAsync() - { - return Task.Run(() => { - try - { - bool haveRazorAssembly = false; - - foreach (var toolAssembly in _resolver.EnumerateRazorToolAssemblies()) - { - haveRazorAssembly = true; - - var command = _commandFactory - .Create("exec", new string[] { toolAssembly.Value, "shutdown" }) - .CaptureStdOut() - .CaptureStdErr(); - - var result = command.Execute(); - if (result.ExitCode != 0) - { - return new Result(ResultKind.Failure, result.StdErr); - } - } - - if (!haveRazorAssembly) - { - return new Result(ResultKind.Skipped, LocalizableStrings.NoRazorProjectFound); - } - - return new Result(ResultKind.Success); - } - catch (InvalidProjectFileException ex) - { - return new Result(ex); - } - }); - } - } -} diff --git a/src/dotnet/BuildServer/Result.cs b/src/dotnet/BuildServer/Result.cs deleted file mode 100644 index 5962fa145..000000000 --- a/src/dotnet/BuildServer/Result.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; - -namespace Microsoft.DotNet.BuildServer -{ - internal enum ResultKind - { - Success, - Failure, - Skipped - } - - internal struct Result - { - public Result(ResultKind kind, string message = null) - { - Kind = kind; - Message = message; - Exception = null; - } - - public Result(Exception exception) - { - Kind = ResultKind.Failure; - Message = exception.Message; - Exception = exception; - } - - public ResultKind Kind { get; private set; } - - public string Message { get; private set; } - - public Exception Exception { get; private set; } - } -} diff --git a/src/dotnet/BuildServer/VBCSCompilerServerManager.cs b/src/dotnet/BuildServer/VBCSCompilerServer.cs similarity index 51% rename from src/dotnet/BuildServer/VBCSCompilerServerManager.cs rename to src/dotnet/BuildServer/VBCSCompilerServer.cs index 12b7bc111..45de2d451 100644 --- a/src/dotnet/BuildServer/VBCSCompilerServerManager.cs +++ b/src/dotnet/BuildServer/VBCSCompilerServer.cs @@ -10,7 +10,7 @@ using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.BuildServer { - internal class VBCSCompilerServerManager : IBuildServerManager + internal class VBCSCompilerServer : IBuildServer { internal static readonly string VBCSCompilerPath = Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @@ -20,29 +20,30 @@ namespace Microsoft.DotNet.BuildServer private readonly ICommandFactory _commandFactory; - public VBCSCompilerServerManager(ICommandFactory commandFactory = null) + public VBCSCompilerServer(ICommandFactory commandFactory = null) { _commandFactory = commandFactory ?? new DotNetCommandFactory(alwaysRunOutOfProc: true); } - public string ServerName => LocalizableStrings.VBCSCompilerServer; + public int ProcessId => 0; // Not yet used - public Task ShutdownServerAsync() + public string Name => LocalizableStrings.VBCSCompilerServer; + + public void Shutdown() { - return Task.Run(() => { - var command = _commandFactory - .Create("exec", new[] { VBCSCompilerPath, "-shutdown" }) - .CaptureStdOut() - .CaptureStdErr(); + var command = _commandFactory + .Create("exec", new[] { VBCSCompilerPath, "-shutdown" }) + .CaptureStdOut() + .CaptureStdErr(); - var result = command.Execute(); - if (result.ExitCode != 0) - { - return new Result(ResultKind.Failure, result.StdErr); - } - - return new Result(ResultKind.Success); - }); + var result = command.Execute(); + if (result.ExitCode != 0) + { + throw new BuildServerException( + string.Format( + LocalizableStrings.ShutdownCommandFailed, + result.StdErr)); + } } } } diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf index 3064a54dc..52d049e73 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf index 162b689a5..01befdbdf 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf index 28ee2c3ce..6e2b1f9d3 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf index 64b184f72..d3b8260eb 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf index 816bdc16b..0ba45554d 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf index 1f143937f..b32bc5468 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf index 61d5383c1..2a6174d9d 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf index 93089c95e..18a9f836b 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf index 6aeb12866..eb5bbde9b 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf index a16008670..3be9872f6 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf index addbfafa2..455687b8b 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf index 7f3a039bc..e4534284b 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf index 0f9fdc61b..d6a2505b9 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf @@ -17,9 +17,9 @@ Razor build server - - a Razor project was not found in the current directory. - a Razor project was not found in the current directory. + + The shutdown command failed: {0} + The shutdown command failed: {0} diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/BuildServerShutdownCommand.cs b/src/dotnet/commands/dotnet-buildserver/shutdown/BuildServerShutdownCommand.cs index 300223e90..a6659bb49 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/BuildServerShutdownCommand.cs +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/BuildServerShutdownCommand.cs @@ -14,6 +14,8 @@ namespace Microsoft.DotNet.Tools.BuildServer.Shutdown { internal class BuildServerShutdownCommand : CommandBase { + private readonly ServerEnumerationFlags _enumerationFlags; + private readonly IBuildServerProvider _serverProvider; private readonly bool _useOrderedWait; private readonly IReporter _reporter; private readonly IReporter _errorReporter; @@ -21,57 +23,63 @@ namespace Microsoft.DotNet.Tools.BuildServer.Shutdown public BuildServerShutdownCommand( AppliedOption options, ParseResult result, - IEnumerable managers = null, + IBuildServerProvider serverProvider = null, bool useOrderedWait = false, IReporter reporter = null) : base(result) { - if (managers == null) + bool msbuild = options.ValueOrDefault("msbuild"); + bool vbcscompiler = options.ValueOrDefault("vbcscompiler"); + bool razor = options.ValueOrDefault("razor"); + bool all = !msbuild && !vbcscompiler && !razor; + + _enumerationFlags = ServerEnumerationFlags.None; + if (msbuild || all) { - bool msbuild = options.ValueOrDefault("msbuild"); - bool vbcscompiler = options.ValueOrDefault("vbcscompiler"); - bool razor = options.ValueOrDefault("razor"); - bool all = !msbuild && !vbcscompiler && !razor; - - var enabledManagers = new List(); - if (msbuild || all) - { - enabledManagers.Add(new MSBuildServerManager()); - } - - if (vbcscompiler || all) - { - enabledManagers.Add(new VBCSCompilerServerManager()); - } - - if (razor || all) - { - enabledManagers.Add(new RazorServerManager()); - } - - managers = enabledManagers; + _enumerationFlags |= ServerEnumerationFlags.MSBuild; } - Managers = managers; + if (vbcscompiler || all) + { + _enumerationFlags |= ServerEnumerationFlags.VBCSCompiler; + } + + if (razor || all) + { + _enumerationFlags |= ServerEnumerationFlags.Razor; + } + + _serverProvider = serverProvider ?? new BuildServerProvider(); _useOrderedWait = useOrderedWait; _reporter = reporter ?? Reporter.Output; _errorReporter = reporter ?? Reporter.Error; } - public IEnumerable Managers { get; } - public override int Execute() { - bool success = true; - var tasks = StartShutdown(); + if (tasks.Count == 0) + { + _reporter.WriteLine(LocalizableStrings.NoServersToShutdown.Green()); + return 0; + } + + bool success = true; while (tasks.Count > 0) { var index = WaitForResult(tasks.Select(t => t.Item2).ToArray()); - var (manager, task) = tasks[index]; + var (server, task) = tasks[index]; - success &= HandleResult(manager, task.Result); + if (task.IsFaulted) + { + success = false; + WriteFailureMessage(server, task.Exception); + } + else + { + WriteSuccessMessage(server); + } tasks.RemoveAt(index); } @@ -79,14 +87,15 @@ namespace Microsoft.DotNet.Tools.BuildServer.Shutdown return success ? 0 : 1; } - private List<(IBuildServerManager, Task)> StartShutdown() + private List<(IBuildServer, Task)> StartShutdown() { - var tasks = new List<(IBuildServerManager, Task)>(); - foreach (var manager in Managers) + var tasks = new List<(IBuildServer, Task)>(); + foreach (var server in _serverProvider.EnumerateBuildServers(_enumerationFlags)) { - _reporter.WriteLine(string.Format(LocalizableStrings.ShuttingDownServer, manager.ServerName)); - tasks.Add((manager, manager.ShutdownServerAsync())); + WriteShutdownMessage(server); + tasks.Add((server, Task.Run(() => server.Shutdown()))); } + return tasks; } @@ -94,50 +103,72 @@ namespace Microsoft.DotNet.Tools.BuildServer.Shutdown { if (_useOrderedWait) { - tasks[0].Wait(); - return 0; + return Task.WaitAny(tasks.First()); } return Task.WaitAny(tasks); } - private bool HandleResult(IBuildServerManager manager, Result result) + private void WriteShutdownMessage(IBuildServer server) { - switch (result.Kind) + if (server.ProcessId != 0) { - case ResultKind.Success: - _reporter.WriteLine( - string.Format( - LocalizableStrings.ShutDownSucceeded, - manager.ServerName).Green()); - return true; + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShuttingDownServerWithPid, + server.Name, + server.ProcessId)); + } + else + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShuttingDownServer, + server.Name)); + } + } - case ResultKind.Skipped: - _reporter.WriteLine( - string.Format( - LocalizableStrings.ShutDownSkipped, - manager.ServerName, - result.Message).Cyan()); - return true; + private void WriteFailureMessage(IBuildServer server, AggregateException exception) + { + if (server.ProcessId != 0) + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShutDownFailedWithPid, + server.Name, + server.ProcessId, + exception.InnerException.Message).Red()); + } + else + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShutDownFailed, + server.Name, + exception.InnerException.Message).Red()); + } - case ResultKind.Failure: - _errorReporter.WriteLine( - string.Format( - LocalizableStrings.ShutDownFailed, - manager.ServerName, - result.Message).Red()); + if (Reporter.IsVerbose) + { + Reporter.Verbose.WriteLine(exception.ToString().Red()); + } + } - if (Reporter.IsVerbose && result.Exception != null) - { - Reporter.Verbose.WriteLine(result.Exception.ToString().Red()); - } - return false; - - default: - throw new NotSupportedException( - string.Format( - LocalizableStrings.UnsupportedEnumValue, - result.Kind.ToString(), - nameof(ResultKind))); + private void WriteSuccessMessage(IBuildServer server) + { + if (server.ProcessId != 0) + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShutDownSucceededWithPid, + server.Name, + server.ProcessId).Green()); + } + else + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.ShutDownSucceeded, + server.Name).Green()); } } } diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/LocalizableStrings.resx b/src/dotnet/commands/dotnet-buildserver/shutdown/LocalizableStrings.resx index edac1e52c..2d94d92a3 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/LocalizableStrings.resx +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/LocalizableStrings.resx @@ -132,16 +132,22 @@ Shutting down {0}... + + Shutting down {0} (process {1})... + {0} shut down successfully. + + {0} (process {1}) shut down successfully. + {0} failed to shut down: {1} - - {0} shut down was skipped: {1} + + {0} (process {1}) failed to shut down: {2} - - The value '{0}' for enum type '{1}' is not supported. + + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.cs.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.cs.xlf index bb9a12c8c..d1c241f1f 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.cs.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.de.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.de.xlf index 4f07f6028..7884b757b 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.de.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.es.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.es.xlf index 47a9ad7c2..25561c2e0 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.es.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.fr.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.fr.xlf index e5a20083c..b8d2e3d6c 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.fr.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.it.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.it.xlf index f48e4c3e0..c54c06b59 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.it.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ja.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ja.xlf index 2b49a95ea..83dc7fa57 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ja.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ko.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ko.xlf index 40ca6507c..b67fc7055 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ko.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pl.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pl.xlf index 625d93493..0df6609cf 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pl.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pt-BR.xlf index 35cf7d075..af68eba6e 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.pt-BR.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ru.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ru.xlf index b6fdacbd6..03c7ce71c 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.ru.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.tr.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.tr.xlf index acde159fc..2804d42c6 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.tr.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hans.xlf index b9e0a6c1c..fd643550e 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hans.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hant.xlf index a5a9c0cfc..5811f0c6e 100644 --- a/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/commands/dotnet-buildserver/shutdown/xlf/LocalizableStrings.zh-Hant.xlf @@ -37,14 +37,24 @@ {0} failed to shut down: {1} - - {0} shut down was skipped: {1} - {0} shut down was skipped: {1} + + Shutting down {0} (process {1})... + Shutting down {0} (process {1})... - - The value '{0}' for enum type '{1}' is not supported. - The value '{0}' for enum type '{1}' is not supported. + + {0} (process {1}) shut down successfully. + {0} (process {1}) shut down successfully. + + + + {0} (process {1}) failed to shut down: {2} + {0} (process {1}) failed to shut down: {2} + + + + No build servers are running. + No build servers are running. diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs index f75d3471b..2054d261a 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs @@ -166,7 +166,15 @@ namespace Microsoft.Extensions.DependencyModel.Tests public IEnumerable EnumerateFiles(string path, string searchPattern) { - throw new NotImplementedException(); + if (searchPattern != "*") + { + throw new NotImplementedException(); + } + + foreach (var kvp in _files.Where(kvp => kvp.Key != kvp.Value && Path.GetDirectoryName(kvp.Key) == path)) + { + yield return kvp.Key; + } } public IEnumerable EnumerateFileSystemEntries(string path) diff --git a/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs b/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs new file mode 100644 index 000000000..4293e49d5 --- /dev/null +++ b/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.BuildServer; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Configurer; +using Microsoft.Extensions.DependencyModel.Tests; +using Moq; +using Xunit; +using LocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings; + +namespace Microsoft.DotNet.Tests.BuildServerTests +{ + public class BuildServerProviderTests + { + [Fact] + public void GivenMSBuildFlagItYieldsMSBuild() + { + var provider = new BuildServerProvider( + new FileSystemMockBuilder().Build(), + CreateEnvironmentProviderMock().Object); + + provider + .EnumerateBuildServers(ServerEnumerationFlags.MSBuild) + .Select(s => s.Name) + .Should() + .Equal(LocalizableStrings.MSBuildServer); + } + + [Fact] + public void GivenVBCSCompilerFlagItYieldsVBCSCompiler() + { + var provider = new BuildServerProvider( + new FileSystemMockBuilder().Build(), + CreateEnvironmentProviderMock().Object); + + provider + .EnumerateBuildServers(ServerEnumerationFlags.VBCSCompiler) + .Select(s => s.Name) + .Should() + .Equal(LocalizableStrings.VBCSCompilerServer); + } + + [Fact] + public void GivenRazorFlagAndNoPidDirectoryTheEnumerationIsEmpty() + { + var provider = new BuildServerProvider( + new FileSystemMockBuilder().Build(), + CreateEnvironmentProviderMock().Object); + + provider + .EnumerateBuildServers(ServerEnumerationFlags.Razor) + .Should() + .BeEmpty(); + } + + [Fact] + public void GivenNoEnvironmentVariableItUsesTheDefaultPidDirectory() + { + var provider = new BuildServerProvider( + new FileSystemMockBuilder().Build(), + CreateEnvironmentProviderMock().Object); + + provider + .GetPidFileDirectory() + .Value + .Should() + .Be(Path.Combine( + CliFolderPathCalculator.DotnetUserProfileFolderPath, + "pids", + "build")); + } + + [Fact] + public void GivenEnvironmentVariableItUsesItForThePidDirectory() + { + const string PidDirectory = "path/to/some/directory"; + + var provider = new BuildServerProvider( + new FileSystemMockBuilder().Build(), + CreateEnvironmentProviderMock(PidDirectory).Object); + + provider + .GetPidFileDirectory() + .Value + .Should() + .Be(PidDirectory); + } + + [Fact] + public void GivenARazorPidFileItReturnsARazorBuildServer() + { + const int ProcessId = 1234; + const string ServerPath = "/path/to/rzc.dll"; + const string PipeName = "some-pipe-name"; + + string pidDirectory = Path.GetFullPath("var/pids/build"); + string pidFilePath = Path.Combine(pidDirectory, $"{RazorPidFile.FilePrefix}{ProcessId}"); + + var fileSystemMock = new FileSystemMockBuilder() + .AddFile( + pidFilePath, + $"{ProcessId}{Environment.NewLine}{RazorPidFile.RazorServerType}{Environment.NewLine}{ServerPath}{Environment.NewLine}{PipeName}") + .AddFile( + Path.Combine(pidDirectory, $"{RazorPidFile.FilePrefix}not-a-pid-file"), + "not-a-pid-file") + .Build(); + + var provider = new BuildServerProvider( + fileSystemMock, + CreateEnvironmentProviderMock(pidDirectory).Object); + + var servers = provider.EnumerateBuildServers(ServerEnumerationFlags.Razor).ToArray(); + servers.Length.Should().Be(1); + + var razorServer = servers.First() as RazorServer; + razorServer.Should().NotBeNull(); + razorServer.ProcessId.Should().Be(ProcessId); + razorServer.Name.Should().Be(LocalizableStrings.RazorServer); + razorServer.PidFile.Should().NotBeNull(); + razorServer.PidFile.Path.Value.Should().Be(pidFilePath); + razorServer.PidFile.ProcessId.Should().Be(ProcessId); + razorServer.PidFile.ServerPath.Value.Should().Be(ServerPath); + razorServer.PidFile.PipeName.Should().Be(PipeName); + } + + private Mock CreateEnvironmentProviderMock(string value = null) + { + var provider = new Mock(MockBehavior.Strict); + + provider + .Setup(p => p.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY")) + .Returns(value); + + return provider; + } + } +} diff --git a/test/dotnet.Tests/BuildServerTests/RazorServerManagerTests.cs b/test/dotnet.Tests/BuildServerTests/RazorServerManagerTests.cs deleted file mode 100644 index 925f21179..000000000 --- a/test/dotnet.Tests/BuildServerTests/RazorServerManagerTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.Build.Exceptions; -using Microsoft.DotNet.BuildServer; -using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Tools; -using Microsoft.DotNet.Tools.Test.Utilities; -using Microsoft.Extensions.EnvironmentAbstractions; -using Moq; -using NuGet.Frameworks; -using Xunit; -using LocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings; - -namespace Microsoft.DotNet.Tests.BuildServerTests -{ - public class RazorServerManagerTests - { - [Fact] - public async Task GivenNoRazorAssemblyShutdownIsSkipped() - { - var resolverMock = new Mock(MockBehavior.Strict); - resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] {}); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - - var manager = new RazorServerManager(resolverMock.Object, commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Skipped); - result.Message.Should().Be(LocalizableStrings.NoRazorProjectFound); - result.Exception.Should().BeNull(); - } - - [Fact] - public async Task GivenARazorAssemblyShutdownSucceeds() - { - const string FakeRazorAssemblyPath = "/path/to/razor.dll"; - - var resolverMock = new Mock(MockBehavior.Strict); - resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] { new FilePath(FakeRazorAssemblyPath) }); - - var commandMock = new Mock(MockBehavior.Strict); - commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); - commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); - commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, 0, "", "")); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - commandFactoryMock - .Setup( - f => f.Create( - "exec", - new string[] { FakeRazorAssemblyPath, "shutdown" }, - It.IsAny(), - Constants.DefaultConfiguration)) - .Returns(commandMock.Object); - - var manager = new RazorServerManager(resolverMock.Object, commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Success); - result.Message.Should().BeNull(); - result.Exception.Should().BeNull(); - } - - [Fact] - public async Task GivenAnInvalidProjectFileShutdownFails() - { - var exception = new InvalidProjectFileException("invalid project!"); - - var resolverMock = new Mock(MockBehavior.Strict); - resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Throws(exception); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - - var manager = new RazorServerManager(resolverMock.Object, commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Failure); - result.Message.Should().Be(exception.Message); - result.Exception.Should().Be(exception); - } - - [Fact] - public async Task GivenANonZeroExitCodeShutdownFails() - { - const string FakeRazorAssemblyPath = "/path/to/razor.dll"; - const string ErrorMessage = "failed!"; - - var resolverMock = new Mock(MockBehavior.Strict); - resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] { new FilePath(FakeRazorAssemblyPath) }); - - var commandMock = new Mock(MockBehavior.Strict); - commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); - commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); - commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, 1, "", ErrorMessage)); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - commandFactoryMock - .Setup( - f => f.Create( - "exec", - new string[] { FakeRazorAssemblyPath, "shutdown" }, - It.IsAny(), - Constants.DefaultConfiguration)) - .Returns(commandMock.Object); - - var manager = new RazorServerManager(resolverMock.Object, commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Failure); - result.Message.Should().Be(ErrorMessage); - result.Exception.Should().BeNull(); - } - } -} diff --git a/test/dotnet.Tests/BuildServerTests/RazorServerTests.cs b/test/dotnet.Tests/BuildServerTests/RazorServerTests.cs new file mode 100644 index 000000000..f9b1ea52f --- /dev/null +++ b/test/dotnet.Tests/BuildServerTests/RazorServerTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.BuildServer; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.Extensions.DependencyModel.Tests; +using Microsoft.Extensions.EnvironmentAbstractions; +using Moq; +using NuGet.Frameworks; +using Xunit; +using LocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings; + +namespace Microsoft.DotNet.Tests.BuildServerTests +{ + public class RazorServerTests + { + [Fact] + public void GivenAFailedShutdownCommandItThrows() + { + const int ProcessId = 1234; + const string ServerPath = "path/to/rzc.dll"; + const string PipeName = "some-pipe-name"; + const string ErrorMessage = "error!"; + + string pidDirectory = Path.GetFullPath("var/pids/build"); + string pidFilePath = Path.Combine(pidDirectory, $"{RazorPidFile.FilePrefix}{ProcessId}"); + + var fileSystemMock = new FileSystemMockBuilder() + .AddFile(pidFilePath, "") + .Build(); + + fileSystemMock.File.Exists(pidFilePath).Should().BeTrue(); + + var server = new RazorServer( + pidFile: new RazorPidFile( + path: new FilePath(pidFilePath), + processId: ProcessId, + serverPath: new FilePath(ServerPath), + pipeName: PipeName), + commandFactory: CreateCommandFactoryMock(ServerPath, PipeName, exitCode: 1, stdErr: ErrorMessage).Object, + fileSystem: fileSystemMock); + + Action a = () => server.Shutdown(); + + a.ShouldThrow().WithMessage( + string.Format( + LocalizableStrings.ShutdownCommandFailed, + ErrorMessage)); + + fileSystemMock.File.Exists(pidFilePath).Should().BeTrue(); + } + + [Fact] + public void GivenASuccessfulShutdownItDoesNotThrow() + { + const int ProcessId = 1234; + const string ServerPath = "path/to/rzc.dll"; + const string PipeName = "some-pipe-name"; + + string pidDirectory = Path.GetFullPath("var/pids/build"); + string pidFilePath = Path.Combine(pidDirectory, $"{RazorPidFile.FilePrefix}{ProcessId}"); + + var fileSystemMock = new FileSystemMockBuilder() + .AddFile(pidFilePath, "") + .Build(); + + fileSystemMock.File.Exists(pidFilePath).Should().BeTrue(); + + var server = new RazorServer( + pidFile: new RazorPidFile( + path: new FilePath(pidFilePath), + processId: ProcessId, + serverPath: new FilePath(ServerPath), + pipeName: PipeName), + commandFactory: CreateCommandFactoryMock(ServerPath, PipeName).Object, + fileSystem: fileSystemMock); + + server.Shutdown(); + + fileSystemMock.File.Exists(pidFilePath).Should().BeFalse(); + } + + private Mock CreateCommandFactoryMock(string serverPath, string pipeName, int exitCode = 0, string stdErr = "") + { + var commandMock = new Mock(MockBehavior.Strict); + commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); + commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); + commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, exitCode, "", stdErr)); + + var commandFactoryMock = new Mock(MockBehavior.Strict); + commandFactoryMock + .Setup( + f => f.Create( + "exec", + new string[] { serverPath, "shutdown", "-w", "-p", pipeName }, + It.IsAny(), + Constants.DefaultConfiguration)) + .Returns(commandMock.Object); + + return commandFactoryMock; + } + } +} diff --git a/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerManagerTests.cs b/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerManagerTests.cs deleted file mode 100644 index 01e156483..000000000 --- a/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerManagerTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.DotNet.BuildServer; -using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Tools; -using Microsoft.Extensions.EnvironmentAbstractions; -using Moq; -using NuGet.Frameworks; -using Xunit; - -namespace Microsoft.DotNet.Tests.BuildServerTests -{ - public class VBCSCompilerServerManagerTests - { - [Fact] - public async Task GivenAZeroExit() - { - var commandMock = new Mock(MockBehavior.Strict); - commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); - commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); - commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, 0, "", "")); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - commandFactoryMock - .Setup( - f => f.Create( - "exec", - new string[] { VBCSCompilerServerManager.VBCSCompilerPath, "-shutdown" }, - It.IsAny(), - Constants.DefaultConfiguration)) - .Returns(commandMock.Object); - - var manager = new VBCSCompilerServerManager(commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Success); - result.Message.Should().BeNull(); - result.Exception.Should().BeNull(); - } - - [Fact] - public async Task GivenANonZeroExitCodeShutdownFails() - { - const string ErrorMessage = "failed!"; - - var commandMock = new Mock(MockBehavior.Strict); - commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); - commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); - commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, 1, "", ErrorMessage)); - - var commandFactoryMock = new Mock(MockBehavior.Strict); - commandFactoryMock - .Setup( - f => f.Create( - "exec", - new string[] { VBCSCompilerServerManager.VBCSCompilerPath, "-shutdown" }, - It.IsAny(), - Constants.DefaultConfiguration)) - .Returns(commandMock.Object); - - var manager = new VBCSCompilerServerManager(commandFactoryMock.Object); - - var result = await manager.ShutdownServerAsync(); - result.Kind.Should().Be(ResultKind.Failure); - result.Message.Should().Be(ErrorMessage); - result.Exception.Should().BeNull(); - } - } -} diff --git a/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerTests.cs b/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerTests.cs new file mode 100644 index 000000000..49dd43391 --- /dev/null +++ b/test/dotnet.Tests/BuildServerTests/VBCSCompilerServerTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using FluentAssertions; +using Microsoft.DotNet.BuildServer; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.Extensions.EnvironmentAbstractions; +using Moq; +using NuGet.Frameworks; +using Xunit; +using LocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings; + +namespace Microsoft.DotNet.Tests.BuildServerTests +{ + public class VBCSCompilerServerTests + { + [Fact] + public void GivenAZeroExitShutdownDoesNotThrow() + { + var server = new VBCSCompilerServer(CreateCommandFactoryMock().Object); + server.Shutdown(); + } + + [Fact] + public void GivenANonZeroExitCodeShutdownThrows() + { + const string ErrorMessage = "failed!"; + + var server = new VBCSCompilerServer(CreateCommandFactoryMock(exitCode: 1, stdErr: ErrorMessage).Object); + + Action a = () => server.Shutdown(); + + a.ShouldThrow().WithMessage( + string.Format( + LocalizableStrings.ShutdownCommandFailed, + ErrorMessage)); + } + + private Mock CreateCommandFactoryMock(int exitCode = 0, string stdErr = "") + { + var commandMock = new Mock(MockBehavior.Strict); + commandMock.Setup(c => c.CaptureStdOut()).Returns(commandMock.Object); + commandMock.Setup(c => c.CaptureStdErr()).Returns(commandMock.Object); + commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, exitCode, "", stdErr)); + + var commandFactoryMock = new Mock(MockBehavior.Strict); + commandFactoryMock + .Setup( + f => f.Create( + "exec", + new string[] { VBCSCompilerServer.VBCSCompilerPath, "-shutdown" }, + It.IsAny(), + Constants.DefaultConfiguration)) + .Returns(commandMock.Object); + + return commandFactoryMock; + } + } +} diff --git a/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs b/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs index f41bc9400..78765b812 100644 --- a/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using FluentAssertions; using Microsoft.DotNet.BuildServer; using Microsoft.DotNet.Cli; @@ -26,57 +25,92 @@ namespace Microsoft.DotNet.Tests.Commands private readonly BufferedReporter _reporter = new BufferedReporter(); [Fact] - public void GivenNoOptionsAllManagersArePresent() + public void GivenNoOptionsItEnumeratesAllServers() { - var command = CreateCommand(); + var provider = new Mock(MockBehavior.Strict); - command.Managers.Select(m => m.ServerName).Should().Equal( - DotNet.BuildServer.LocalizableStrings.MSBuildServer, - DotNet.BuildServer.LocalizableStrings.VBCSCompilerServer, - DotNet.BuildServer.LocalizableStrings.RazorServer - ); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.All)) + .Returns(Array.Empty()); + + var command = CreateCommand(serverProvider: provider.Object); + + command.Execute().Should().Be(0); + + _reporter.Lines.Should().Equal(LocalizableStrings.NoServersToShutdown.Green()); + + provider.Verify(p => p.EnumerateBuildServers(ServerEnumerationFlags.All), Times.Once); } [Fact] - public void GivenMSBuildOptionOnlyItIsTheOnlyManager() + public void GivenMSBuildOptionOnlyItEnumeratesOnlyMSBuildServers() { - var command = CreateCommand("--msbuild"); + var provider = new Mock(MockBehavior.Strict); - command.Managers.Select(m => m.ServerName).Should().Equal( - DotNet.BuildServer.LocalizableStrings.MSBuildServer - ); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.MSBuild)) + .Returns(Array.Empty()); + + var command = CreateCommand(options: "--msbuild", serverProvider: provider.Object); + + command.Execute().Should().Be(0); + + _reporter.Lines.Should().Equal(LocalizableStrings.NoServersToShutdown.Green()); + + provider.Verify(p => p.EnumerateBuildServers(ServerEnumerationFlags.MSBuild), Times.Once); } [Fact] - public void GivenVBCSCompilerOptionOnlyItIsTheOnlyManager() + public void GivenVBCSCompilerOptionOnlyItEnumeratesOnlyVBCSCompilers() { - var command = CreateCommand("--vbcscompiler"); + var provider = new Mock(MockBehavior.Strict); - command.Managers.Select(m => m.ServerName).Should().Equal( - DotNet.BuildServer.LocalizableStrings.VBCSCompilerServer - ); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.VBCSCompiler)) + .Returns(Array.Empty()); + + var command = CreateCommand(options: "--vbcscompiler", serverProvider: provider.Object); + + command.Execute().Should().Be(0); + + _reporter.Lines.Should().Equal(LocalizableStrings.NoServersToShutdown.Green()); + + provider.Verify(p => p.EnumerateBuildServers(ServerEnumerationFlags.VBCSCompiler), Times.Once); } [Fact] - public void GivenRazorOptionOnlyItIsTheOnlyManager() + public void GivenRazorOptionOnlyItEnumeratesOnlyRazorServers() { - var command = CreateCommand("--razor"); + var provider = new Mock(MockBehavior.Strict); - command.Managers.Select(m => m.ServerName).Should().Equal( - DotNet.BuildServer.LocalizableStrings.RazorServer - ); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.Razor)) + .Returns(Array.Empty()); + + var command = CreateCommand(options: "--razor", serverProvider: provider.Object); + + command.Execute().Should().Be(0); + + _reporter.Lines.Should().Equal(LocalizableStrings.NoServersToShutdown.Green()); + + provider.Verify(p => p.EnumerateBuildServers(ServerEnumerationFlags.Razor), Times.Once); } [Fact] public void GivenSuccessfulShutdownsItPrintsSuccess() { var mocks = new[] { - CreateManagerMock("first", new Result(ResultKind.Success)), - CreateManagerMock("second", new Result(ResultKind.Success)), - CreateManagerMock("third", new Result(ResultKind.Success)) + CreateServerMock("first"), + CreateServerMock("second"), + CreateServerMock("third") }; - var command = CreateCommand(managers: mocks.Select(m => m.Object)); + var provider = new Mock(MockBehavior.Strict); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.All)) + .Returns(mocks.Select(m => m.Object)); + + var command = CreateCommand(serverProvider: provider.Object); command.Execute().Should().Be(0); @@ -94,15 +128,21 @@ namespace Microsoft.DotNet.Tests.Commands [Fact] public void GivenAFailingShutdownItPrintsFailureMessage() { - const string FailureMessage = "failed!"; + const string FirstFailureMessage = "first failed!"; + const string ThirdFailureMessage = "third failed!"; var mocks = new[] { - CreateManagerMock("first", new Result(ResultKind.Success)), - CreateManagerMock("second", new Result(ResultKind.Failure, FailureMessage)), - CreateManagerMock("third", new Result(ResultKind.Success)) + CreateServerMock("first", exceptionMessage: FirstFailureMessage), + CreateServerMock("second"), + CreateServerMock("third", exceptionMessage: ThirdFailureMessage) }; - var command = CreateCommand(managers: mocks.Select(m => m.Object)); + var provider = new Mock(MockBehavior.Strict); + provider + .Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.All)) + .Returns(mocks.Select(m => m.Object)); + + var command = CreateCommand(serverProvider: provider.Object); command.Execute().Should().Be(1); @@ -110,113 +150,80 @@ namespace Microsoft.DotNet.Tests.Commands FormatShuttingDownMessage(mocks[0].Object), FormatShuttingDownMessage(mocks[1].Object), FormatShuttingDownMessage(mocks[2].Object), - FormatSuccessMessage(mocks[0].Object), - FormatFailureMessage(mocks[1].Object, FailureMessage), - FormatSuccessMessage(mocks[2].Object)); - - VerifyShutdownCalls(mocks); - } - - [Fact] - public void GivenASkippedShutdownItPrintsSkipMessage() - { - const string SkipMessage = "skipped!"; - - var mocks = new[] { - CreateManagerMock("first", new Result(ResultKind.Success)), - CreateManagerMock("second", new Result(ResultKind.Success)), - CreateManagerMock("third", new Result(ResultKind.Skipped, SkipMessage)) - }; - - var command = CreateCommand(managers: mocks.Select(m => m.Object)); - - command.Execute().Should().Be(0); - - _reporter.Lines.Should().Equal( - FormatShuttingDownMessage(mocks[0].Object), - FormatShuttingDownMessage(mocks[1].Object), - FormatShuttingDownMessage(mocks[2].Object), - FormatSuccessMessage(mocks[0].Object), + FormatFailureMessage(mocks[0].Object, FirstFailureMessage), FormatSuccessMessage(mocks[1].Object), - FormatSkippedMessage(mocks[2].Object, SkipMessage)); + FormatFailureMessage(mocks[2].Object, ThirdFailureMessage)); VerifyShutdownCalls(mocks); } - [Fact] - public void GivenSuccessFailureAndSkippedItPrintsAllThree() - { - const string FailureMessage = "failed!"; - const string SkipMessage = "skipped!"; - - var mocks = new[] { - CreateManagerMock("first", new Result(ResultKind.Success)), - CreateManagerMock("second", new Result(ResultKind.Failure, FailureMessage)), - CreateManagerMock("third", new Result(ResultKind.Skipped, SkipMessage)) - }; - - var command = CreateCommand(managers: mocks.Select(m => m.Object)); - - command.Execute().Should().Be(1); - - _reporter.Lines.Should().Equal( - FormatShuttingDownMessage(mocks[0].Object), - FormatShuttingDownMessage(mocks[1].Object), - FormatShuttingDownMessage(mocks[2].Object), - FormatSuccessMessage(mocks[0].Object), - FormatFailureMessage(mocks[1].Object, FailureMessage), - FormatSkippedMessage(mocks[2].Object, SkipMessage)); - - VerifyShutdownCalls(mocks); - } - - private BuildServerShutdownCommand CreateCommand(string options = "", IEnumerable managers = null) + private BuildServerShutdownCommand CreateCommand( + string options = "", + IBuildServerProvider serverProvider = null, + IEnumerable buildServers = null, + ServerEnumerationFlags expectedFlags = ServerEnumerationFlags.None) { ParseResult result = Parser.Instance.Parse("dotnet build-server shutdown " + options); return new BuildServerShutdownCommand( options: result["dotnet"]["build-server"]["shutdown"], result: result, - managers: managers, + serverProvider: serverProvider, useOrderedWait: true, reporter: _reporter); } - private Mock CreateManagerMock(string serverName, Result result) + private Mock CreateServerMock(string name, int pid = 0, string exceptionMessage = null) { - var mock = new Mock(MockBehavior.Strict); + var mock = new Mock(MockBehavior.Strict); - mock.SetupGet(m => m.ServerName).Returns(serverName); - mock.Setup(m => m.ShutdownServerAsync()).Returns(Task.FromResult(result)); + mock.SetupGet(s => s.ProcessId).Returns(pid); + mock.SetupGet(s => s.Name).Returns(name); + + if (exceptionMessage == null) + { + mock.Setup(s => s.Shutdown()); + } + else + { + mock.Setup(s => s.Shutdown()).Throws(new Exception(exceptionMessage)); + } return mock; } - private void VerifyShutdownCalls(IEnumerable> mocks) + private void VerifyShutdownCalls(IEnumerable> mocks) { foreach (var mock in mocks) { - mock.Verify(m => m.ShutdownServerAsync(), Times.Once()); + mock.Verify(s => s.Shutdown(), Times.Once); } } - private static string FormatShuttingDownMessage(IBuildServerManager manager) + private static string FormatShuttingDownMessage(IBuildServer server) { - return string.Format(LocalizableStrings.ShuttingDownServer, manager.ServerName); + if (server.ProcessId != 0) + { + return string.Format(LocalizableStrings.ShuttingDownServerWithPid, server.Name, server.ProcessId); + } + return string.Format(LocalizableStrings.ShuttingDownServer, server.Name); } - private static string FormatSuccessMessage(IBuildServerManager manager) + private static string FormatSuccessMessage(IBuildServer server) { - return string.Format(LocalizableStrings.ShutDownSucceeded, manager.ServerName).Green(); + if (server.ProcessId != 0) + { + return string.Format(LocalizableStrings.ShutDownSucceededWithPid, server.Name, server.ProcessId).Green(); + } + return string.Format(LocalizableStrings.ShutDownSucceeded, server.Name).Green(); } - private static string FormatFailureMessage(IBuildServerManager manager, string message) + private static string FormatFailureMessage(IBuildServer server, string message) { - return string.Format(LocalizableStrings.ShutDownFailed, manager.ServerName, message).Red(); - } - - private static string FormatSkippedMessage(IBuildServerManager manager, string message) - { - return string.Format(LocalizableStrings.ShutDownSkipped, manager.ServerName, message).Cyan(); + if (server.ProcessId != 0) + { + return string.Format(LocalizableStrings.ShutDownFailedWithPid, server.Name, server.ProcessId, message).Red(); + } + return string.Format(LocalizableStrings.ShutDownFailed, server.Name, message).Red(); } } }