Add Mac project build change trigger.

- Added a Mac specific implementation of the project build change trigger. It applies to all types of project builds so we need to do a little extra filtering to ensure that we're not operating on a non-ASP.NET Core project.
- Added tests to validate that the project build event fires correctly.

#1851
This commit is contained in:
N. Taylor Mullen 2017-12-18 15:59:22 -08:00
parent 06c2cf31d4
commit 4b93741610
4 changed files with 219 additions and 1 deletions

View File

@ -81,7 +81,7 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.Editor
}
// VisualStudio for Mac only supports ASP.NET Core Razor.
public override bool IsSupportedProject(object project) => true;
public override bool IsSupportedProject(object project) => project is DotNetProject;
public override string GetProjectName(object project)
{

View File

@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor;
using MonoDevelop.Ide;
using MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
{
[Export(typeof(ProjectSnapshotChangeTrigger))]
internal class ProjectBuildChangeTrigger : ProjectSnapshotChangeTrigger
{
private readonly TextBufferProjectService _projectService;
private readonly ForegroundDispatcher _foregroundDispatcher;
private ProjectSnapshotManagerBase _projectManager;
[ImportingConstructor]
public ProjectBuildChangeTrigger(VisualStudioWorkspaceAccessor workspaceAccessor)
{
if (workspaceAccessor == null)
{
throw new ArgumentNullException(nameof(workspaceAccessor));
}
_foregroundDispatcher = workspaceAccessor.Workspace.Services.GetRequiredService<ForegroundDispatcher>();
var languageServices = workspaceAccessor.Workspace.Services.GetLanguageServices(RazorLanguage.Name);
_projectService = languageServices.GetRequiredService<TextBufferProjectService>();
}
// Internal for testing
internal ProjectBuildChangeTrigger(
ForegroundDispatcher foregroundDispatcher,
TextBufferProjectService projectService,
ProjectSnapshotManagerBase projectManager)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectService == null)
{
throw new ArgumentNullException(nameof(projectService));
}
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
}
_foregroundDispatcher = foregroundDispatcher;
_projectService = projectService;
_projectManager = projectManager;
}
public override void Initialize(ProjectSnapshotManagerBase projectManager)
{
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
}
_projectManager = projectManager;
IdeApp.ProjectOperations.EndBuild += ProjectOperations_EndBuild;
}
// Internal for testing
internal void ProjectOperations_EndBuild(object sender, BuildEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
_foregroundDispatcher.AssertForegroundThread();
if (!args.Success)
{
// Build failed
return;
}
var projectItem = args.SolutionItem;
if (!_projectService.IsSupportedProject(projectItem))
{
// We're hooked into all build events, it's possible to get called with an unsupported project item type.
return;
}
var projectName = _projectService.GetProjectName(projectItem);
var projectPath = _projectService.GetProjectPath(projectItem);
// Get the corresponding roslyn project by matching the project name and the project path.
foreach (var project in _projectManager.Workspace.CurrentSolution.Projects)
{
if (string.Equals(projectName, project.Name, StringComparison.Ordinal) &&
string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
{
_projectManager.ProjectBuildComplete(project);
break;
}
}
}
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor;
using MonoDevelop.Projects;
using Moq;
using Xunit;
using Project = Microsoft.CodeAnalysis.Project;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
{
public class ProjectBuildChangeTriggerTest : ForegroundDispatcherTestBase
{
[ForegroundFact]
public void ProjectOperations_EndBuild_Invokes_ProjectBuildComplete()
{
// Arrange
var args = new BuildEventArgs(monitor: null, success: true);
var expectedProjectName = "Test1";
var expectedProjectPath = "Path/To/Project";
var projectService = CreateProjectService(expectedProjectName, expectedProjectPath);
var workspace = new AdhocWorkspace();
CreateProjectInWorkspace(workspace, expectedProjectName, expectedProjectPath);
CreateProjectInWorkspace(workspace, "Test2", "Path/To/AnotherProject");
var projectManager = new Mock<ProjectSnapshotManagerBase>(MockBehavior.Strict);
projectManager.SetupGet(p => p.Workspace).Returns(workspace);
projectManager
.Setup(p => p.ProjectBuildComplete(It.IsAny<Project>()))
.Callback<Project>(c => Assert.Equal(expectedProjectName, c.Name));
var trigger = new ProjectBuildChangeTrigger(Dispatcher, projectService, projectManager.Object);
// Act
trigger.ProjectOperations_EndBuild(null, args);
// Assert
projectManager.VerifyAll();
}
[ForegroundFact]
public void ProjectOperations_EndBuild_UntrackedProject_Noops()
{
// Arrange
var args = new BuildEventArgs(monitor: null, success: true);
var projectService = CreateProjectService("Test1", "Path/To/Project");
var workspace = new AdhocWorkspace();
CreateProjectInWorkspace(workspace, "Test2", "Path/To/AnotherProject");
var projectManager = new Mock<ProjectSnapshotManagerBase>();
projectManager.SetupGet(p => p.Workspace).Returns(workspace);
projectManager
.Setup(p => p.ProjectBuildComplete(It.IsAny<Project>()))
.Throws<InvalidOperationException>();
var trigger = new ProjectBuildChangeTrigger(Dispatcher, projectService, projectManager.Object);
// Act & Assert
trigger.ProjectOperations_EndBuild(null, args);
}
[ForegroundFact]
public void ProjectOperations_EndBuild_BuildFailed_Noops()
{
// Arrange
var args = new BuildEventArgs(monitor: null, success: false);
var projectService = new Mock<TextBufferProjectService>();
projectService.Setup(p => p.IsSupportedProject(null)).Throws<InvalidOperationException>();
var projectManager = new Mock<ProjectSnapshotManagerBase>();
projectManager.SetupGet(p => p.Workspace).Throws<InvalidOperationException>();
var trigger = new ProjectBuildChangeTrigger(Dispatcher, projectService.Object, projectManager.Object);
// Act & Assert
trigger.ProjectOperations_EndBuild(null, args);
}
[ForegroundFact]
public void ProjectOperations_EndBuild_UnsupportedProject_Noops()
{
// Arrange
var args = new BuildEventArgs(monitor: null, success: true);
var projectService = new Mock<TextBufferProjectService>();
projectService.Setup(p => p.IsSupportedProject(null)).Returns(false);
var projectManager = new Mock<ProjectSnapshotManagerBase>();
projectManager.SetupGet(p => p.Workspace).Throws<InvalidOperationException>();
var trigger = new ProjectBuildChangeTrigger(Dispatcher, projectService.Object, projectManager.Object);
// Act & Assert
trigger.ProjectOperations_EndBuild(null, args);
}
private static TextBufferProjectService CreateProjectService(string projectName, string projectPath)
{
var projectService = new Mock<TextBufferProjectService>();
projectService.Setup(p => p.GetProjectName(null)).Returns(projectName);
projectService.Setup(p => p.GetProjectPath(null)).Returns(projectPath);
projectService.Setup(p => p.IsSupportedProject(null)).Returns(true);
return projectService.Object;
}
private static AdhocWorkspace CreateProjectInWorkspace(AdhocWorkspace workspace, string name, string path)
{
workspace.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), new VersionStamp(), name, "TestAssembly", LanguageNames.CSharp, filePath: path));
return workspace;
}
}
}