// 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(MockBehavior.Strict); 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 GivenMSBuildOptionOnlyItEnumeratesOnlyMSBuildServers() { var provider = new Mock(MockBehavior.Strict); 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 GivenVBCSCompilerOptionOnlyItEnumeratesOnlyVBCSCompilers() { var provider = new Mock(MockBehavior.Strict); 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 GivenRazorOptionOnlyItEnumeratesOnlyRazorServers() { var provider = new Mock(MockBehavior.Strict); 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[] { CreateServerMock("first"), CreateServerMock("second"), CreateServerMock("third") }; 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); _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(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 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 CreateServerMock(string name, int pid = 0, string exceptionMessage = null) { var mock = new Mock(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> 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(); } } }