diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs index 144f9704066..3bce56886fd 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; namespace Microsoft.VisualStudio.Razor.ProjectSystem; @@ -16,8 +18,17 @@ internal sealed class TestAccessor(WindowsRazorProjectHostBase @this) internal bool GetIntermediateOutputPathFromProjectChange(IImmutableDictionary state, out string? result) { - _this.SkipIntermediateOutputPathExistCheck_TestOnly = true; + _this._skipDirectoryExistCheck_TestOnly = true; return _this.TryGetIntermediateOutputPath(state, out result); } + + internal Task InitializeAsync() + => _this.InitializeAsync(); + + internal Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue update) + => _this.OnProjectChangedAsync(sliceDimensions, update); + + internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath) + => _this.OnProjectRenamingAsync(oldProjectFilePath, newProjectFilePath); } } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs index 701c7666d72..ec880d444a1 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs @@ -38,9 +38,7 @@ internal abstract partial class WindowsRazorProjectHostBase : OnceInitializedOnc internal const string ConfigurationGeneralSchemaName = "ConfigurationGeneral"; - // Internal settable for testing - // 250ms between publishes to prevent bursts of changes yet still be responsive to changes. - internal int EnqueueDelay { get; set; } = 250; + private bool _skipDirectoryExistCheck_TestOnly; protected WindowsRazorProjectHostBase( IUnconfiguredProjectCommonServices commonServices, @@ -61,14 +59,6 @@ protected WindowsRazorProjectHostBase( protected IUnconfiguredProjectCommonServices CommonServices { get; } - internal bool SkipIntermediateOutputPathExistCheck_TestOnly { get; set; } - - // internal for tests. The product will call through the IProjectDynamicLoadComponent interface. - internal Task LoadAsync() - { - return InitializeAsync(); - } - protected sealed override Task InitializeCoreAsync(CancellationToken cancellationToken) { CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenamingAsync; @@ -155,8 +145,7 @@ private void SlicesChanged(IProjectVersionedValue update) + private Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue update) { if (IsDisposing || IsDisposed) { @@ -204,8 +193,7 @@ await ExecuteWithLockAsync( } } - // Internal for tests - internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath) + private Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath) { // When a project gets renamed we expect any rules watched by the derived class to fire. // @@ -320,7 +308,6 @@ protected bool TryGetBeforeIntermediateOutputPath(IImmutableDictionary state, [NotNullWhen(returnValue: true)] out string? path) @@ -357,15 +344,11 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna var basePath = new DirectoryInfo(baseIntermediateOutputPathValue).Parent; var joinedPath = Path.Combine(basePath.FullName, intermediateOutputPathValue); - if (!SkipIntermediateOutputPathExistCheck_TestOnly && !Directory.Exists(joinedPath)) + if (!Path.IsPathRooted(baseIntermediateOutputPathValue)) { - // The directory doesn't exist for the currently executing application. - // This can occur in Razor class library scenarios because: - // 1. Razor class libraries base intermediate path is not absolute. Meaning instead of C:/project/obj it returns /obj. - // 2. Our `new DirectoryInfo(...).Parent` call above is forgiving so if the path passed to it isn't absolute (Razor class library scenario) it utilizes Directory.GetCurrentDirectory where - // in this case would be the C:/Windows/System path - // Because of the above two issues the joinedPath ends up looking like "C:\WINDOWS\system32\obj\Debug\netstandard2.0\" which doesn't actually exist and of course isn't writeable. The end-user effect of this - // quirk means that you don't get any component completions for Razor class libraries because we're unable to capture their project state information. + // For Razor class libraries, the base intermediate path is relative. Meaning instead of C:/project/obj it returns /obj. + // The `new DirectoryInfo(...).Parent` call above is forgiving so if the path passed to it isn't absolute (Razor class library scenario) it utilizes Directory.GetCurrentDirectory, which + // could be the C:/Windows/System path, or the solution path, or anything really. // // To workaround these inconsistencies with Razor class libraries we fall back to the MSBuildProjectDirectory and build what we think is the intermediate output path. joinedPath = ResolveFallbackIntermediateOutputPath(rule, intermediateOutputPathValue); @@ -381,7 +364,7 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna return true; } - private static string? ResolveFallbackIntermediateOutputPath(IProjectRuleSnapshot rule, string intermediateOutputPathValue) + private string? ResolveFallbackIntermediateOutputPath(IProjectRuleSnapshot rule, string intermediateOutputPathValue) { if (!rule.Properties.TryGetValue(MSBuildProjectDirectoryPropertyName, out var projectDirectory)) { @@ -390,7 +373,7 @@ private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSna } var joinedPath = Path.Combine(projectDirectory, intermediateOutputPathValue); - if (!Directory.Exists(joinedPath)) + if (!_skipDirectoryExistCheck_TestOnly && !Directory.Exists(joinedPath)) { return null; } diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs index b253b2d9800..c548daff7a8 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs @@ -627,7 +627,7 @@ public async Task DefaultRazorProjectHost_UIThread_CreateAndDispose_Succeeds() var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); // Act & Assert - await host.LoadAsync(); + await host.GetTestAccessor().InitializeAsync(); Assert.Empty(_projectManager.GetProjects()); await host.DisposeAsync(); @@ -642,7 +642,7 @@ public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succ var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); // Act & Assert - await Task.Run(async () => await host.LoadAsync()); + await Task.Run(async () => await host.GetTestAccessor().InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); await Task.Run(async () => await host.DisposeAsync()); @@ -660,11 +660,13 @@ public async Task DefaultRazorProjectHost_OnProjectChanged_NoRulesDefined() var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); + var testAccessor = host.GetTestAccessor(); + // Act & Assert - await Task.Run(async () => await host.LoadAsync()); + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); Assert.Empty(_projectManager.GetProjects()); } @@ -692,24 +694,25 @@ public async Task OnProjectChanged_ReadsProperties_InitializesProject() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert var project = Assert.Single(_projectManager.GetProjects()); @@ -758,12 +761,41 @@ public void IntermediateOutputPathCalculationHandlesRelativePaths(string baseInt var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); var state = TestProjectRuleSnapshot.CreateProperties( - WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, - new Dictionary() - { - [WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName] = intermediateOutputPath, - [WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName] = baseIntermediateOutputPath, - }); + WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, + new Dictionary() + { + [WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName] = intermediateOutputPath, + [WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName] = baseIntermediateOutputPath, + }); + + var dict = ImmutableDictionary.Empty; + dict = dict.Add(WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, state); + + var result = host.GetTestAccessor().GetIntermediateOutputPathFromProjectChange(dict, + out var combinedIntermediateOutputPath); + + Assert.True(result); + Assert.Equal(expectedCombinedIOP, combinedIntermediateOutputPath); + } + + [UITheory] + // This is what we see for Razor class libraries + [InlineData("obj/", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] + [InlineData("../obj/", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] + [InlineData("../obj", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] + public void IntermediateOutputPathCalculationHandlesRelativePaths_BaseIntermediateOutputPath(string baseIntermediateOutputPath, string msbuildProjectDirectoryPropertyName, string intermediateOutputPath, string expectedCombinedIOP) + { + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); + var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); + + var state = TestProjectRuleSnapshot.CreateProperties( + WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, + new Dictionary() + { + [WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName] = intermediateOutputPath, + [WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName] = baseIntermediateOutputPath, + [WindowsRazorProjectHostBase.MSBuildProjectDirectoryPropertyName] = msbuildProjectDirectoryPropertyName, + }); var dict = ImmutableDictionary.Empty; dict = dict.Add(WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, state); @@ -790,21 +822,23 @@ public async Task OnProjectChanged_NoVersionFound_DoesNotInitializeProject() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert Assert.Empty(_projectManager.GetProjects()); @@ -840,19 +874,20 @@ public async Task OnProjectChanged_UpdateProject_MarksSolutionOpen() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 @@ -861,7 +896,7 @@ await _projectManager.UpdateAsync(updater => updater.SolutionClosed(); }); - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 Assert.False(_projectManager.IsSolutionClosing); @@ -894,23 +929,24 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var project = Assert.Single(_projectManager.GetProjects()); @@ -953,25 +989,25 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() _configurationItems.Item("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, }); _extensionItems.Item("MVC-2.0"); _razorComponentWithTargetPathItems.Item(TestProjectData.AnotherProjectNestedComponentFile3.FilePath, new Dictionary() - { - { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedComponentFile3.TargetPath }, - }); + { + { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedComponentFile3.TargetPath }, + }); _razorGenerateWithTargetPathItems.Item(TestProjectData.AnotherProjectNestedFile3.FilePath, new Dictionary() - { - { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedFile3.TargetPath }, - }); + { + { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedFile3.TargetPath }, + }); changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - _configurationGeneral.ToChange(changes[5].After), + _razorGeneralProperties.ToChange(changes[0].After), + _configurationItems.ToChange(changes[1].After), + _extensionItems.ToChange(changes[2].After), + _razorComponentWithTargetPathItems.ToChange(changes[3].After), + _razorGenerateWithTargetPathItems.ToChange(changes[4].After), + _configurationGeneral.ToChange(changes[5].After), }; - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await host.GetTestAccessor().OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 2 project = Assert.Single(_projectManager.GetProjects()); @@ -1050,23 +1086,24 @@ public async Task OnProjectChanged_VersionRemoved_DeInitializesProject() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -1085,14 +1122,14 @@ public async Task OnProjectChanged_VersionRemoved_DeInitializesProject() changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), + _razorGeneralProperties.ToChange(changes[0].After), + _configurationItems.ToChange(changes[1].After), + _extensionItems.ToChange(changes[2].After), + _razorComponentWithTargetPathItems.ToChange(changes[3].After), + _razorGenerateWithTargetPathItems.ToChange(changes[4].After), }; - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 2 Assert.Empty(_projectManager.GetProjects()); @@ -1125,23 +1162,24 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -1167,14 +1205,14 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), + _razorGeneralProperties.ToChange(changes[0].After), + _configurationItems.ToChange(changes[1].After), + _extensionItems.ToChange(changes[2].After), + _razorComponentWithTargetPathItems.ToChange(changes[3].After), + _razorGenerateWithTargetPathItems.ToChange(changes[4].After), }; - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 3 Assert.Empty(_projectManager.GetProjects()); @@ -1204,24 +1242,25 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var project = Assert.Single(_projectManager.GetProjects()); @@ -1229,25 +1268,25 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() Assert.Same("MVC-2.1", project.Configuration.ConfigurationName); Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(FileKinds.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(FileKinds.Component, document.FileKind); - }); + project.DocumentFilePaths.OrderBy(d => d), + d => + { + var document = project.GetRequiredDocument(d); + Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); + Assert.Equal(FileKinds.Legacy, document.FileKind); + }, + d => + { + var document = project.GetRequiredDocument(d); + Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); + Assert.Equal(FileKinds.Component, document.FileKind); + }); // Act - 2 services.UnconfiguredProject.FullPath = TestProjectData.AnotherProject.FilePath; - await Task.Run(async () => await host.OnProjectRenamingAsync(TestProjectData.SomeProject.FilePath, TestProjectData.AnotherProject.FilePath)); + await Task.Run(async () => await testAccessor.OnProjectRenamingAsync(TestProjectData.SomeProject.FilePath, TestProjectData.AnotherProject.FilePath)); // Assert - 1 project = Assert.Single(_projectManager.GetProjects()); @@ -1255,21 +1294,21 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() Assert.Same("MVC-2.1", project.Configuration.ConfigurationName); Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(FileKinds.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(FileKinds.Component, document.FileKind); - }); + project.DocumentFilePaths.OrderBy(d => d), + d => + { + var document = project.GetRequiredDocument(d); + Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); + Assert.Equal(FileKinds.Legacy, document.FileKind); + }, + d => + { + var document = project.GetRequiredDocument(d); + Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); + Assert.Equal(FileKinds.Component, document.FileKind); + }); await Task.Run(async () => await host.DisposeAsync()); Assert.Empty(_projectManager.GetProjects()); @@ -1302,23 +1341,24 @@ public async Task OnProjectChanged_ChangeIntermediateOutputPath_RemovesAndAddsPr var changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), + _razorGeneralProperties.ToChange(), + _configurationItems.ToChange(), + _extensionItems.ToChange(), + _razorComponentWithTargetPathItems.ToChange(), + _razorGenerateWithTargetPathItems.ToChange(), + _configurationGeneral.ToChange(), }; var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager); - host.SkipIntermediateOutputPathExistCheck_TestOnly = true; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var project = Assert.Single(_projectManager.GetProjects()); @@ -1359,15 +1399,15 @@ public async Task OnProjectChanged_ChangeIntermediateOutputPath_RemovesAndAddsPr changes = new TestProjectChangeDescription[] { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - _configurationGeneral.ToChange(changes[5].After), + _razorGeneralProperties.ToChange(changes[0].After), + _configurationItems.ToChange(changes[1].After), + _extensionItems.ToChange(changes[2].After), + _razorComponentWithTargetPathItems.ToChange(changes[3].After), + _razorGenerateWithTargetPathItems.ToChange(changes[4].After), + _configurationGeneral.ToChange(changes[5].After), }; - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 2 // Changing intermediate output path is effectively removing the old project and adding a new one. diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs index 039b1630f41..a3cba9dedcc 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs @@ -304,7 +304,7 @@ public async Task FallbackRazorProjectHost_UIThread_CreateAndDispose_Succeeds() var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); // Act & Assert - await host.LoadAsync(); + await host.GetTestAccessor().InitializeAsync(); Assert.Empty(_projectManager.GetProjects()); await host.DisposeAsync(); @@ -320,7 +320,7 @@ public async Task FallbackRazorProjectHost_BackgroundThread_CreateAndDispose_Suc var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); // Act & Assert - await Task.Run(async () => await host.LoadAsync()); + await Task.Run(async () => await host.GetTestAccessor().InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); await Task.Run(async () => await host.DisposeAsync()); @@ -342,11 +342,13 @@ public async Task OnProjectChanged_NoRulesDefined() AssemblyVersion = new Version(2, 0), }; + var testAccessor = host.GetTestAccessor(); + // Act & Assert - await Task.Run(async () => await host.LoadAsync()); + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); Assert.Empty(_projectManager.GetProjects()); } @@ -378,11 +380,13 @@ public async Task OnProjectChanged_ReadsProperties_InitializesProject() AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version }; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -410,11 +414,13 @@ public async Task OnProjectChanged_NoAssemblyFound_DoesNotInitializeProject() var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert Assert.Empty(_projectManager.GetProjects()); @@ -438,11 +444,13 @@ public async Task OnProjectChanged_AssemblyFoundButCannotReadVersion_DoesNotInit var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert Assert.Empty(_projectManager.GetProjects()); @@ -480,11 +488,13 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() AssemblyVersion = new Version(2, 0), }; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(initialChanges))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(initialChanges))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -495,7 +505,7 @@ public async Task OnProjectChanged_UpdateProject_Succeeds() // Act - 2 host.AssemblyVersion = new Version(1, 0); - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 2 snapshot = Assert.Single(_projectManager.GetProjects()); @@ -525,11 +535,13 @@ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject() AssemblyVersion = new Version(2, 0), }; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -538,7 +550,7 @@ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject() // Act - 2 host.AssemblyVersion = null; - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 2 Assert.Empty(_projectManager.GetProjects()); @@ -570,11 +582,13 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() AssemblyVersion = new Version(2, 0), }; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -591,7 +605,7 @@ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() // Act - 3 host.AssemblyVersion = new Version(1, 1); - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 3 Assert.Empty(_projectManager.GetProjects()); @@ -615,11 +629,13 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version }; - await Task.Run(async () => await host.LoadAsync()); + var testAccessor = host.GetTestAccessor(); + + await Task.Run(async () => await testAccessor.InitializeAsync()); Assert.Empty(_projectManager.GetProjects()); // Act - 1 - await Task.Run(async () => await host.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); + await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); // Assert - 1 var snapshot = Assert.Single(_projectManager.GetProjects()); @@ -628,7 +644,7 @@ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() // Act - 2 services.UnconfiguredProject.FullPath = "Test2.csproj"; - await Task.Run(async () => await host.OnProjectRenamingAsync("Test.csproj", "Test2.csproj")); + await Task.Run(async () => await testAccessor.OnProjectRenamingAsync("Test.csproj", "Test2.csproj")); // Assert - 1 snapshot = Assert.Single(_projectManager.GetProjects()); @@ -647,7 +663,6 @@ internal TestFallbackRazorProjectHost( ProjectSnapshotManager projectManager) : base(commonServices, serviceProvider, projectManager) { - SkipIntermediateOutputPathExistCheck_TestOnly = true; } public Version AssemblyVersion { get; set; }