File: EditorFactoryTests.cs
Web Access
Project: src\src\VisualStudio\Core\Test.Next\Roslyn.VisualStudio.Next.UnitTests.csproj (Roslyn.VisualStudio.Next.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService;
using Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel;
using Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks;
using Microsoft.VisualStudio.Shell.Interop;
using Moq;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Sdk;
 
namespace Microsoft.VisualStudio.LanguageServices.UnitTests;
 
[UseExportProvider]
public sealed class EditorFactoryTests
{
    // Remove VisualStudioWorkspaceTelemetryService from the composition, since it tries to initialize the "telemetry" in the other process right away
    private static readonly TestComposition s_composition =
        CodeModelTestHelpers.Composition
            .AddParts(typeof(ThrowingRemoteHostClientProvider));
 
    [WpfFact]
    public async Task FormatDocumentCreatedFromTemplateAsync_DoesNotRequestRemoteHostClient()
    {
        // This test asserts that we don't have a request for the RemoteHost when trying to format a newly created document.
        // Since that can happen during the creation of a new solution, we can end up in cases where we are blocking the UI thread on the launch
        // of the remote host where we're then blocking on the spinning up of an entire process.
 
        using var tempRoot = new TempRoot();
        var filePath = tempRoot.CreateFile("Test", "cs").Path;
        var contents = """
            class C
            {
                void M()
                {
                }
            }
            """.ReplaceLineEndings();
 
        File.WriteAllText(filePath, contents);
 
        var exportProvider = s_composition.ExportProviderFactory.CreateExportProvider();
        using var workspace = exportProvider.GetExportedValue<MockVisualStudioWorkspace>();
        var remoteHostClientProvider = (ThrowingRemoteHostClientProvider)workspace.Services.GetRequiredService<IRemoteHostClientProvider>();
        var editorFactory = new CSharpEditorFactory(new MockComponentModel(exportProvider));
 
        await editorFactory.FormatDocumentCreatedFromTemplateAsync(Mock.Of<IVsHierarchy>(), filePath, CancellationToken.None);
 
        // Assert there were no requests that might have gotten caught by some other layer of the stack.
        Assert.Empty(remoteHostClientProvider.StackTracesOfAttemptsToGetTheRemoteHost);
    }
 
    [ExportWorkspaceService(typeof(IRemoteHostClientProvider), WorkspaceKind.Host), Shared, PartNotDiscoverable]
    [method: ImportingConstructor]
    [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    private sealed class ThrowingRemoteHostClientProvider() : IRemoteHostClientProvider
    {
        private readonly ConcurrentBag<StackTrace> _stackTracesOfAttemptsToGetTheRemoteHost = new ConcurrentBag<StackTrace>();
 
        public IEnumerable<StackTrace> StackTracesOfAttemptsToGetTheRemoteHost => _stackTracesOfAttemptsToGetTheRemoteHost;
 
        public Task<RemoteHostClient?> TryGetRemoteHostClientAsync(CancellationToken cancellationToken)
        {
            // Keep track of all the places this gets called. This is done just to ensure we don't catch the failure somewhere and try to fall to an "in-process" path and run anwyays.
            _stackTracesOfAttemptsToGetTheRemoteHost.Add(new StackTrace());
            throw new XunitException("Unexpected attempt to request a remote host client while formatting a newly created document.");
        }
 
        public Task WaitForClientCreationAsync(CancellationToken cancellationToken)
            => Task.CompletedTask;
    }
}