From 1ade191cb62a05f1fb42b58645beda5b70806ba5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 15 Apr 2018 00:50:32 -0700 Subject: [PATCH] Implement Razor server discovery by pid file. Previously, Razor server discovery for the `build-server shutdown` command was implemented by invoking MSBuild on a project file in the current directory to evaluate the path to the Razor server dll. This was problematic since it would only discover a single running Razor server instance and required that the user run the `build-server shutdown` command from a specific location. Razor's server now writes a "pid file" to a well-known location (`~/.dotnet/pids/build`) which the command can now enumerate to discover, and shutdown, the running Razor servers. This commit changes the Razor server discovery to use the pid files and removes the requirement that users need to run the command in specific directories to work. Fixes #9084. --- .../BuildServer/BuildServerException.cs | 22 ++ src/dotnet/BuildServer/BuildServerProvider.cs | 86 +++++++ ...IBuildServerManager.cs => IBuildServer.cs} | 8 +- .../BuildServer/IBuildServerProvider.cs | 23 ++ .../BuildServer/IRazorAssemblyResolver.cs | 14 -- .../BuildServer/LocalizableStrings.resx | 4 +- src/dotnet/BuildServer/MSBuildServer.cs | 21 ++ .../BuildServer/MSBuildServerManager.cs | 29 --- .../BuildServer/RazorAssemblyResolver.cs | 52 ----- src/dotnet/BuildServer/RazorPidFile.cs | 65 ++++++ src/dotnet/BuildServer/RazorServer.cs | 77 ++++++ src/dotnet/BuildServer/RazorServerManager.cs | 65 ------ src/dotnet/BuildServer/Result.cs | 37 --- ...ServerManager.cs => VBCSCompilerServer.cs} | 35 +-- .../BuildServer/xlf/LocalizableStrings.cs.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.de.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.es.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.fr.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.it.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.ja.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.ko.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.pl.xlf | 6 +- .../xlf/LocalizableStrings.pt-BR.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.ru.xlf | 6 +- .../BuildServer/xlf/LocalizableStrings.tr.xlf | 6 +- .../xlf/LocalizableStrings.zh-Hans.xlf | 6 +- .../xlf/LocalizableStrings.zh-Hant.xlf | 6 +- .../shutdown/BuildServerShutdownCommand.cs | 173 ++++++++------ .../shutdown/LocalizableStrings.resx | 14 +- .../shutdown/xlf/LocalizableStrings.cs.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.de.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.es.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.fr.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.it.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.ja.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.ko.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.pl.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.pt-BR.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.ru.xlf | 22 +- .../shutdown/xlf/LocalizableStrings.tr.xlf | 22 +- .../xlf/LocalizableStrings.zh-Hans.xlf | 22 +- .../xlf/LocalizableStrings.zh-Hant.xlf | 22 +- .../Mock/FileSystemMockBuilder.cs | 10 +- .../BuildServerProviderTests.cs | 143 ++++++++++++ .../RazorServerManagerTests.cs | 123 ---------- .../BuildServerTests/RazorServerTests.cs | 109 +++++++++ .../VBCSCompilerServerManagerTests.cs | 75 ------ .../VBCSCompilerServerTests.cs | 63 +++++ .../BuildServerShutdownCommandTests.cs | 219 +++++++++--------- 49 files changed, 1115 insertions(+), 716 deletions(-) create mode 100644 src/dotnet/BuildServer/BuildServerException.cs create mode 100644 src/dotnet/BuildServer/BuildServerProvider.cs rename src/dotnet/BuildServer/{IBuildServerManager.cs => IBuildServer.cs} (69%) create mode 100644 src/dotnet/BuildServer/IBuildServerProvider.cs delete mode 100644 src/dotnet/BuildServer/IRazorAssemblyResolver.cs create mode 100644 src/dotnet/BuildServer/MSBuildServer.cs delete mode 100644 src/dotnet/BuildServer/MSBuildServerManager.cs delete mode 100644 src/dotnet/BuildServer/RazorAssemblyResolver.cs create mode 100644 src/dotnet/BuildServer/RazorPidFile.cs create mode 100644 src/dotnet/BuildServer/RazorServer.cs delete mode 100644 src/dotnet/BuildServer/RazorServerManager.cs delete mode 100644 src/dotnet/BuildServer/Result.cs rename src/dotnet/BuildServer/{VBCSCompilerServerManager.cs => VBCSCompilerServer.cs} (51%) create mode 100644 test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs delete mode 100644 test/dotnet.Tests/BuildServerTests/RazorServerManagerTests.cs create mode 100644 test/dotnet.Tests/BuildServerTests/RazorServerTests.cs delete mode 100644 test/dotnet.Tests/BuildServerTests/VBCSCompilerServerManagerTests.cs create mode 100644 test/dotnet.Tests/BuildServerTests/VBCSCompilerServerTests.cs 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(); } } }