dotnet-installer/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs
Peter Huene b2b3947c68
Fix Razor server shutdown on Windows.
On Windows, the Razor server correctly creates the pid file with
`FileAccess.Write` and `FileOptions.DeleteOnClose`.  This requires a share mode
of `FileShare.Write | FileShare.Delete` to open.  However, the
`dotnet build-server shutdown` command was opening the file with
`FileShare.Read`.  As a result, an `IOException` was being thrown and was not
handled.

This change first opens the file with the appropriate share access and also
properly handles a failure to access or read the contents of the pid file.

Additionally, an integration test was added to test that Razor server shutdown
works as expected.

Fixes #9158.
2018-04-27 13:52:17 -07:00

270 lines
10 KiB
C#

// 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;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.TestFramework;
using Microsoft.DotNet.Tools.BuildServer;
using Microsoft.DotNet.Tools.BuildServer.Shutdown;
using Microsoft.DotNet.Tools.Test.Utilities;
using Microsoft.Extensions.EnvironmentAbstractions;
using Moq;
using Xunit;
using Parser = Microsoft.DotNet.Cli.Parser;
using CommandLocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings;
using LocalizableStrings = Microsoft.DotNet.Tools.BuildServer.Shutdown.LocalizableStrings;
using TestBuildServerCommand = Microsoft.DotNet.Tools.Test.Utilities.BuildServerCommand;
namespace Microsoft.DotNet.Tests.Commands
{
public class BuildServerShutdownCommandTests : TestBase
{
private readonly BufferedReporter _reporter = new BufferedReporter();
[Fact]
public void GivenNoOptionsItEnumeratesAllServers()
{
var provider = new Mock<IBuildServerProvider>(MockBehavior.Strict);
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 GivenMSBuildOptionOnlyItEnumeratesOnlyMSBuildServers()
{
var provider = new Mock<IBuildServerProvider>(MockBehavior.Strict);
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 GivenVBCSCompilerOptionOnlyItEnumeratesOnlyVBCSCompilers()
{
var provider = new Mock<IBuildServerProvider>(MockBehavior.Strict);
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 GivenRazorOptionOnlyItEnumeratesOnlyRazorServers()
{
var provider = new Mock<IBuildServerProvider>(MockBehavior.Strict);
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[] {
CreateServerMock("first"),
CreateServerMock("second"),
CreateServerMock("third")
};
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);
_reporter.Lines.Should().Equal(
FormatShuttingDownMessage(mocks[0].Object),
FormatShuttingDownMessage(mocks[1].Object),
FormatShuttingDownMessage(mocks[2].Object),
FormatSuccessMessage(mocks[0].Object),
FormatSuccessMessage(mocks[1].Object),
FormatSuccessMessage(mocks[2].Object));
VerifyShutdownCalls(mocks);
}
[Fact]
public void GivenAFailingShutdownItPrintsFailureMessage()
{
const string FirstFailureMessage = "first failed!";
const string ThirdFailureMessage = "third failed!";
var mocks = new[] {
CreateServerMock("first", exceptionMessage: FirstFailureMessage),
CreateServerMock("second"),
CreateServerMock("third", exceptionMessage: ThirdFailureMessage)
};
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);
_reporter.Lines.Should().Equal(
FormatShuttingDownMessage(mocks[0].Object),
FormatShuttingDownMessage(mocks[1].Object),
FormatShuttingDownMessage(mocks[2].Object),
FormatFailureMessage(mocks[0].Object, FirstFailureMessage),
FormatSuccessMessage(mocks[1].Object),
FormatFailureMessage(mocks[2].Object, ThirdFailureMessage));
VerifyShutdownCalls(mocks);
}
[Fact]
public void GivenARunningRazorServerItShutsDownSuccessfully()
{
var pipeName = Path.GetRandomFileName();
var pidDirectory = Path.GetFullPath(Path.Combine(TempRoot.Root, Path.GetRandomFileName()));
var testInstance = TestAssets.Get("TestRazorApp")
.CreateInstance()
.WithSourceFiles();
new BuildCommand()
.WithWorkingDirectory(testInstance.Root)
.WithEnvironmentVariable(BuildServerProvider.PidFileDirectoryVariableName, pidDirectory)
.Execute($"/p:_RazorBuildServerPipeName={pipeName}")
.Should()
.Pass();
var files = Directory.GetFiles(pidDirectory, RazorPidFile.FilePrefix + "*");
files.Length.Should().Be(1);
var pidFile = RazorPidFile.Read(new FilePath(files.First()));
pidFile.PipeName.Should().Be(pipeName);
new TestBuildServerCommand()
.WithWorkingDirectory(testInstance.Root)
.WithEnvironmentVariable(BuildServerProvider.PidFileDirectoryVariableName, pidDirectory)
.ExecuteWithCapturedOutput("shutdown --razor")
.Should()
.Pass()
.And
.HaveStdOutContaining(
string.Format(
LocalizableStrings.ShutDownSucceededWithPid,
CommandLocalizableStrings.RazorServer,
pidFile.ProcessId));
}
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,
serverProvider: serverProvider,
useOrderedWait: true,
reporter: _reporter);
}
private Mock<IBuildServer> CreateServerMock(string name, int pid = 0, string exceptionMessage = null)
{
var mock = new Mock<IBuildServer>(MockBehavior.Strict);
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<IBuildServer>> mocks)
{
foreach (var mock in mocks)
{
mock.Verify(s => s.Shutdown(), Times.Once);
}
}
private static string FormatShuttingDownMessage(IBuildServer server)
{
if (server.ProcessId != 0)
{
return string.Format(LocalizableStrings.ShuttingDownServerWithPid, server.Name, server.ProcessId);
}
return string.Format(LocalizableStrings.ShuttingDownServer, server.Name);
}
private static string FormatSuccessMessage(IBuildServer server)
{
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(IBuildServer server, string message)
{
if (server.ProcessId != 0)
{
return string.Format(LocalizableStrings.ShutDownFailedWithPid, server.Name, server.ProcessId, message).Red();
}
return string.Format(LocalizableStrings.ShutDownFailed, server.Name, message).Red();
}
}
}