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.
This commit is contained in:
Peter Huene 2018-04-15 00:50:32 -07:00
parent 8e01912b36
commit 1ade191cb6
No known key found for this signature in database
GPG key ID: E1D265D820213D6A
49 changed files with 1115 additions and 716 deletions

View file

@ -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)
{
}
}
}

View file

@ -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<IBuildServer> 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"));
}
}
}

View file

@ -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<Result> ShutdownServerAsync();
string Name { get; }
void Shutdown();
}
}

View file

@ -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<IBuildServer> EnumerateBuildServers(ServerEnumerationFlags flags = ServerEnumerationFlags.All);
}
}

View file

@ -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<FilePath> EnumerateRazorToolAssemblies();
}
}

View file

@ -126,7 +126,7 @@
<data name="RazorServer" xml:space="preserve">
<value>Razor build server</value>
</data>
<data name="NoRazorProjectFound" xml:space="preserve">
<value>a Razor project was not found in the current directory.</value>
<data name="ShutdownCommandFailed" xml:space="preserve">
<value>The shutdown command failed: {0}</value>
</data>
</root>

View file

@ -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();
}
}
}

View file

@ -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<Result> ShutdownServerAsync()
{
return Task.Run(() => {
try
{
BuildManager.DefaultBuildManager.ShutdownAllNodes();
return new Result(ResultKind.Success);
}
catch (Exception ex)
{
return new Result(ex);
}
});
}
}
}

View file

@ -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<FilePath> EnumerateRazorToolAssemblies()
{
HashSet<string> seen = new HashSet<string>();
var globalProperties = new Dictionary<string, string>(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);
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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)
{
}
}
}
}

View file

@ -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<Result> 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);
}
});
}
}
}

View file

@ -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; }
}
}

View file

@ -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<Result> 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));
}
}
}
}

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -17,9 +17,9 @@
<target state="new">Razor build server</target>
<note />
</trans-unit>
<trans-unit id="NoRazorProjectFound">
<source>a Razor project was not found in the current directory.</source>
<target state="new">a Razor project was not found in the current directory.</target>
<trans-unit id="ShutdownCommandFailed">
<source>The shutdown command failed: {0}</source>
<target state="new">The shutdown command failed: {0}</target>
<note />
</trans-unit>
</body>

View file

@ -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<IBuildServerManager> managers = null,
IBuildServerProvider serverProvider = null,
bool useOrderedWait = false,
IReporter reporter = null)
: base(result)
{
if (managers == null)
bool msbuild = options.ValueOrDefault<bool>("msbuild");
bool vbcscompiler = options.ValueOrDefault<bool>("vbcscompiler");
bool razor = options.ValueOrDefault<bool>("razor");
bool all = !msbuild && !vbcscompiler && !razor;
_enumerationFlags = ServerEnumerationFlags.None;
if (msbuild || all)
{
bool msbuild = options.ValueOrDefault<bool>("msbuild");
bool vbcscompiler = options.ValueOrDefault<bool>("vbcscompiler");
bool razor = options.ValueOrDefault<bool>("razor");
bool all = !msbuild && !vbcscompiler && !razor;
var enabledManagers = new List<IBuildServerManager>();
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<IBuildServerManager> 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<Result>)> StartShutdown()
private List<(IBuildServer, Task)> StartShutdown()
{
var tasks = new List<(IBuildServerManager, Task<Result>)>();
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());
}
}
}

View file

@ -132,16 +132,22 @@
<data name="ShuttingDownServer" xml:space="preserve">
<value>Shutting down {0}...</value>
</data>
<data name="ShuttingDownServerWithPid" xml:space="preserve">
<value>Shutting down {0} (process {1})...</value>
</data>
<data name="ShutDownSucceeded" xml:space="preserve">
<value>{0} shut down successfully.</value>
</data>
<data name="ShutDownSucceededWithPid" xml:space="preserve">
<value>{0} (process {1}) shut down successfully.</value>
</data>
<data name="ShutDownFailed" xml:space="preserve">
<value>{0} failed to shut down: {1}</value>
</data>
<data name="ShutDownSkipped" xml:space="preserve">
<value>{0} shut down was skipped: {1}</value>
<data name="ShutDownFailedWithPid" xml:space="preserve">
<value>{0} (process {1}) failed to shut down: {2}</value>
</data>
<data name="UnsupportedEnumValue" xml:space="preserve">
<value>The value '{0}' for enum type '{1}' is not supported.</value>
<data name="NoServersToShutdown" xml:space="preserve">
<value>No build servers are running.</value>
</data>
</root>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -37,14 +37,24 @@
<target state="new">{0} failed to shut down: {1}</target>
<note />
</trans-unit>
<trans-unit id="ShutDownSkipped">
<source>{0} shut down was skipped: {1}</source>
<target state="new">{0} shut down was skipped: {1}</target>
<trans-unit id="ShuttingDownServerWithPid">
<source>Shutting down {0} (process {1})...</source>
<target state="new">Shutting down {0} (process {1})...</target>
<note />
</trans-unit>
<trans-unit id="UnsupportedEnumValue">
<source>The value '{0}' for enum type '{1}' is not supported.</source>
<target state="new">The value '{0}' for enum type '{1}' is not supported.</target>
<trans-unit id="ShutDownSucceededWithPid">
<source>{0} (process {1}) shut down successfully.</source>
<target state="new">{0} (process {1}) shut down successfully.</target>
<note />
</trans-unit>
<trans-unit id="ShutDownFailedWithPid">
<source>{0} (process {1}) failed to shut down: {2}</source>
<target state="new">{0} (process {1}) failed to shut down: {2}</target>
<note />
</trans-unit>
<trans-unit id="NoServersToShutdown">
<source>No build servers are running.</source>
<target state="new">No build servers are running.</target>
<note />
</trans-unit>
</body>

View file

@ -166,7 +166,15 @@ namespace Microsoft.Extensions.DependencyModel.Tests
public IEnumerable<string> 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<string> EnumerateFileSystemEntries(string path)

View file

@ -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<IEnvironmentProvider> CreateEnvironmentProviderMock(string value = null)
{
var provider = new Mock<IEnvironmentProvider>(MockBehavior.Strict);
provider
.Setup(p => p.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY"))
.Returns(value);
return provider;
}
}
}

View file

@ -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<IRazorAssemblyResolver>(MockBehavior.Strict);
resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] {});
var commandFactoryMock = new Mock<ICommandFactory>(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<IRazorAssemblyResolver>(MockBehavior.Strict);
resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] { new FilePath(FakeRazorAssemblyPath) });
var commandMock = new Mock<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { FakeRazorAssemblyPath, "shutdown" },
It.IsAny<NuGetFramework>(),
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<IRazorAssemblyResolver>(MockBehavior.Strict);
resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Throws(exception);
var commandFactoryMock = new Mock<ICommandFactory>(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<IRazorAssemblyResolver>(MockBehavior.Strict);
resolverMock.Setup(r => r.EnumerateRazorToolAssemblies()).Returns(new FilePath[] { new FilePath(FakeRazorAssemblyPath) });
var commandMock = new Mock<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { FakeRazorAssemblyPath, "shutdown" },
It.IsAny<NuGetFramework>(),
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();
}
}
}

View file

@ -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<BuildServerException>().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<ICommandFactory> CreateCommandFactoryMock(string serverPath, string pipeName, int exitCode = 0, string stdErr = "")
{
var commandMock = new Mock<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { serverPath, "shutdown", "-w", "-p", pipeName },
It.IsAny<NuGetFramework>(),
Constants.DefaultConfiguration))
.Returns(commandMock.Object);
return commandFactoryMock;
}
}
}

View file

@ -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<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { VBCSCompilerServerManager.VBCSCompilerPath, "-shutdown" },
It.IsAny<NuGetFramework>(),
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<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { VBCSCompilerServerManager.VBCSCompilerPath, "-shutdown" },
It.IsAny<NuGetFramework>(),
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();
}
}
}

View file

@ -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<BuildServerException>().WithMessage(
string.Format(
LocalizableStrings.ShutdownCommandFailed,
ErrorMessage));
}
private Mock<ICommandFactory> CreateCommandFactoryMock(int exitCode = 0, string stdErr = "")
{
var commandMock = new Mock<ICommand>(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<ICommandFactory>(MockBehavior.Strict);
commandFactoryMock
.Setup(
f => f.Create(
"exec",
new string[] { VBCSCompilerServer.VBCSCompilerPath, "-shutdown" },
It.IsAny<NuGetFramework>(),
Constants.DefaultConfiguration))
.Returns(commandMock.Object);
return commandFactoryMock;
}
}
}

View file

@ -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<IBuildServerProvider>(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<IBuildServer>());
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<IBuildServerProvider>(MockBehavior.Strict);
command.Managers.Select(m => m.ServerName).Should().Equal(
DotNet.BuildServer.LocalizableStrings.MSBuildServer
);
provider
.Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.MSBuild))
.Returns(Array.Empty<IBuildServer>());
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<IBuildServerProvider>(MockBehavior.Strict);
command.Managers.Select(m => m.ServerName).Should().Equal(
DotNet.BuildServer.LocalizableStrings.VBCSCompilerServer
);
provider
.Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.VBCSCompiler))
.Returns(Array.Empty<IBuildServer>());
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<IBuildServerProvider>(MockBehavior.Strict);
command.Managers.Select(m => m.ServerName).Should().Equal(
DotNet.BuildServer.LocalizableStrings.RazorServer
);
provider
.Setup(p => p.EnumerateBuildServers(ServerEnumerationFlags.Razor))
.Returns(Array.Empty<IBuildServer>());
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<IBuildServerProvider>(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<IBuildServerProvider>(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<IBuildServerManager> managers = null)
private BuildServerShutdownCommand CreateCommand(
string options = "",
IBuildServerProvider serverProvider = null,
IEnumerable<IBuildServer> 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<IBuildServerManager> CreateManagerMock(string serverName, Result result)
private Mock<IBuildServer> CreateServerMock(string name, int pid = 0, string exceptionMessage = null)
{
var mock = new Mock<IBuildServerManager>(MockBehavior.Strict);
var mock = new Mock<IBuildServer>(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<Mock<IBuildServerManager>> mocks)
private void VerifyShutdownCalls(IEnumerable<Mock<IBuildServer>> 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();
}
}
}