diff --git a/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml b/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml new file mode 100644 index 000000000..79083e425 --- /dev/null +++ b/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml @@ -0,0 +1,16 @@ +@page +@model TestRazorApp.MyFeature.Pages.Page1Model +@{ + Layout = null; +} + + + + + + + Page1 + + + + diff --git a/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml.cs b/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml.cs new file mode 100644 index 000000000..583793ac3 --- /dev/null +++ b/TestAssets/TestProjects/TestRazorApp/Areas/MyFeature/Pages/Page1.cshtml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace TestRazorApp.MyFeature.Pages +{ + public class Page1Model : PageModel + { + public void OnGet() + { + + } + } +} \ No newline at end of file diff --git a/TestAssets/TestProjects/TestRazorApp/TestRazorApp.csproj b/TestAssets/TestProjects/TestRazorApp/TestRazorApp.csproj new file mode 100644 index 000000000..59cfc871f --- /dev/null +++ b/TestAssets/TestProjects/TestRazorApp/TestRazorApp.csproj @@ -0,0 +1,11 @@ + + + + + netstandard2.0 + + + + + + diff --git a/src/dotnet/BuildServer/BuildServerProvider.cs b/src/dotnet/BuildServer/BuildServerProvider.cs index e292d854a..bf26fdfad 100644 --- a/src/dotnet/BuildServer/BuildServerProvider.cs +++ b/src/dotnet/BuildServer/BuildServerProvider.cs @@ -12,15 +12,19 @@ namespace Microsoft.DotNet.BuildServer { internal class BuildServerProvider : IBuildServerProvider { + public const string PidFileDirectoryVariableName = "DOTNET_BUILD_PIDFILE_DIRECTORY"; private readonly IFileSystem _fileSystem; private readonly IEnvironmentProvider _environmentProvider; + private readonly IReporter _reporter; public BuildServerProvider( IFileSystem fileSystem = null, - IEnvironmentProvider environmentProvider = null) + IEnvironmentProvider environmentProvider = null, + IReporter reporter = null) { _fileSystem = fileSystem ?? FileSystemWrapper.Default; _environmentProvider = environmentProvider ?? new EnvironmentProvider(); + _reporter = reporter ?? Reporter.Error; } public IEnumerable EnumerateBuildServers(ServerEnumerationFlags flags = ServerEnumerationFlags.All) @@ -59,7 +63,7 @@ namespace Microsoft.DotNet.BuildServer if ((flags & ServerEnumerationFlags.Razor) == ServerEnumerationFlags.Razor && Path.GetFileName(path).StartsWith(RazorPidFile.FilePrefix)) { - var file = RazorPidFile.Read(new FilePath(path), _fileSystem); + var file = ReadRazorPidFile(new FilePath(path)); if (file != null) { yield return new RazorServer(file); @@ -70,7 +74,7 @@ namespace Microsoft.DotNet.BuildServer public DirectoryPath GetPidFileDirectory() { - var directory = _environmentProvider.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY"); + var directory = _environmentProvider.GetEnvironmentVariable(PidFileDirectoryVariableName); if (!string.IsNullOrEmpty(directory)) { return new DirectoryPath(directory); @@ -82,5 +86,22 @@ namespace Microsoft.DotNet.BuildServer "pids", "build")); } + + private RazorPidFile ReadRazorPidFile(FilePath path) + { + try + { + return RazorPidFile.Read(path, _fileSystem); + } + catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) + { + _reporter.WriteLine( + string.Format( + LocalizableStrings.FailedToReadPidFile, + path.Value, + ex.Message).Yellow()); + return null; + } + } } } diff --git a/src/dotnet/BuildServer/LocalizableStrings.resx b/src/dotnet/BuildServer/LocalizableStrings.resx index 8534ae97a..d15e3925a 100644 --- a/src/dotnet/BuildServer/LocalizableStrings.resx +++ b/src/dotnet/BuildServer/LocalizableStrings.resx @@ -129,4 +129,7 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + diff --git a/src/dotnet/BuildServer/RazorPidFile.cs b/src/dotnet/BuildServer/RazorPidFile.cs index dd8806c9e..f2baa7115 100644 --- a/src/dotnet/BuildServer/RazorPidFile.cs +++ b/src/dotnet/BuildServer/RazorPidFile.cs @@ -33,7 +33,13 @@ namespace Microsoft.DotNet.BuildServer { fileSystem = fileSystem ?? FileSystemWrapper.Default; - using (var stream = fileSystem.File.OpenRead(path.Value)) + using (var stream = fileSystem.File.OpenFile( + path.Value, + FileMode.Open, + FileAccess.Read, + FileShare.Write | FileShare.Delete, + 4096, + FileOptions.None)) using (var reader = new StreamReader(stream, Encoding.UTF8)) { if (!int.TryParse(reader.ReadLine(), out var processId)) diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf index 52d049e73..c2d75710c 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.cs.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf index 01befdbdf..773380657 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.de.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf index 6e2b1f9d3..f10fc5c1a 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.es.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf index d3b8260eb..5d8247e06 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.fr.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf index 0ba45554d..964f5b5b1 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.it.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf index b32bc5468..7f8488372 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ja.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf index 2a6174d9d..d4021594e 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ko.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf index 18a9f836b..77fbbf49e 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.pl.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf index eb5bbde9b..6dcaccbd0 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.pt-BR.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf index 3be9872f6..415392021 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.ru.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf index 455687b8b..c04a534c5 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.tr.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf index e4534284b..a19f1958d 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hans.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf index d6a2505b9..4b7407194 100644 --- a/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/BuildServer/xlf/LocalizableStrings.zh-Hant.xlf @@ -22,6 +22,11 @@ The shutdown command failed: {0} + + Failed to read pid file '{0}': {1} + Failed to read pid file '{0}': {1} + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildServerCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildServerCommand.cs new file mode 100644 index 000000000..986587188 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildServerCommand.cs @@ -0,0 +1,20 @@ +// 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 Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public sealed class BuildServerCommand : DotnetCommand + { + public override CommandResult Execute(string args = "") + { + return base.Execute($"build-server {args}"); + } + + public override CommandResult ExecuteWithCapturedOutput(string args = "") + { + return base.ExecuteWithCapturedOutput($"build-server {args}"); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs index 2054d261a..4320d0da6 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs @@ -94,6 +94,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests int bufferSize, FileOptions fileOptions) { + if (fileMode == FileMode.Open && fileAccess == FileAccess.Read) + { + return OpenRead(path); + } + throw new NotImplementedException(); } diff --git a/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs b/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs index 4293e49d5..15f9a94ea 100644 --- a/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs +++ b/test/dotnet.Tests/BuildServerTests/BuildServerProviderTests.cs @@ -9,7 +9,9 @@ using FluentAssertions; using Microsoft.DotNet.BuildServer; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; +using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.Extensions.DependencyModel.Tests; +using Microsoft.Extensions.EnvironmentAbstractions; using Moq; using Xunit; using LocalizableStrings = Microsoft.DotNet.BuildServer.LocalizableStrings; @@ -129,12 +131,59 @@ namespace Microsoft.DotNet.Tests.BuildServerTests razorServer.PidFile.PipeName.Should().Be(PipeName); } + [Theory] + [InlineData(typeof(UnauthorizedAccessException))] + [InlineData(typeof(IOException))] + public void GivenAnExceptionAccessingTheRazorPidFileItPrintsAWarning(Type exceptionType) + { + const int ProcessId = 1234; + const string ErrorMessage = "failed!"; + + string pidDirectory = Path.GetFullPath("var/pids/build"); + string pidFilePath = Path.Combine(pidDirectory, $"{RazorPidFile.FilePrefix}{ProcessId}"); + + var directoryMock = new Mock(); + directoryMock.Setup(d => d.Exists(pidDirectory)).Returns(true); + directoryMock.Setup(d => d.EnumerateFiles(pidDirectory, "*")).Returns(new [] { pidFilePath }); + + var fileMock = new Mock(); + fileMock + .Setup(f => f.OpenFile( + pidFilePath, + FileMode.Open, + FileAccess.Read, + FileShare.Write | FileShare.Delete, + 4096, + FileOptions.None)) + .Throws((Exception)Activator.CreateInstance(exceptionType, new object[] { ErrorMessage } )); + + var fileSystemMock = new Mock(); + fileSystemMock.SetupGet(fs => fs.Directory).Returns(directoryMock.Object); + fileSystemMock.SetupGet(fs => fs.File).Returns(fileMock.Object); + + var reporter = new BufferedReporter(); + + var provider = new BuildServerProvider( + fileSystemMock.Object, + CreateEnvironmentProviderMock(pidDirectory).Object, + reporter); + + var servers = provider.EnumerateBuildServers(ServerEnumerationFlags.Razor).ToArray(); + servers.Should().BeEmpty(); + + reporter.Lines.Should().Equal( + string.Format( + LocalizableStrings.FailedToReadPidFile, + pidFilePath, + ErrorMessage).Yellow()); + } + private Mock CreateEnvironmentProviderMock(string value = null) { var provider = new Mock(MockBehavior.Strict); provider - .Setup(p => p.GetEnvironmentVariable("DOTNET_BUILD_PIDFILE_DIRECTORY")) + .Setup(p => p.GetEnvironmentVariable(BuildServerProvider.PidFileDirectoryVariableName)) .Returns(value); return provider; diff --git a/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs b/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs index 78765b812..a699b203f 100644 --- a/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/BuildServerShutdownCommandTests.cs @@ -10,17 +10,21 @@ 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 + public class BuildServerShutdownCommandTests : TestBase { private readonly BufferedReporter _reporter = new BufferedReporter(); @@ -157,6 +161,43 @@ namespace Microsoft.DotNet.Tests.Commands 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,