File: src\VisualStudio\IntegrationTest\Harness\XUnitShared\InProcess\VisualStudio_InProc.cs
Web Access
Project: src\src\VisualStudio\IntegrationTest\Harness\XUnit\Microsoft.VisualStudio.Extensibility.Testing.Xunit.csproj (Microsoft.VisualStudio.Extensibility.Testing.Xunit)
// 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.
 
namespace Xunit.InProcess
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text;
    using Microsoft.VisualStudio.Shell.Interop;
    using Windows.Win32;
    using Windows.Win32.Foundation;
    using Windows.Win32.UI.WindowsAndMessaging;
    using Xunit.Harness;
    using File = System.IO.File;
    using IVsUIShell = Microsoft.VisualStudio.Shell.Interop.IVsUIShell;
    using OLECMDEXECOPT = Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT;
    using Path = System.IO.Path;
    using SVsActivityLog = Microsoft.VisualStudio.Shell.Interop.SVsActivityLog;
    using SVsUIShell = Microsoft.VisualStudio.Shell.Interop.SVsUIShell;
 
    internal partial class VisualStudio_InProc : InProcComponent
    {
        /// <summary>
        /// We need to add a root element so that the Visual Studio activity log file is valid xml and can easily be consumed using Xml.Linq.
        /// </summary>
        private const string VisualStudioActivityLogRoot = "entries";
 
        private VisualStudio_InProc()
        {
        }
 
        public static VisualStudio_InProc Create()
            => new VisualStudio_InProc();
 
        public new void WaitForSystemIdle()
            => InProcComponent.WaitForSystemIdle();
 
        public new bool IsCommandAvailable(string commandName)
            => InProcComponent.IsCommandAvailable(commandName);
 
        public new void ExecuteCommand(string commandName, string args = "")
            => InProcComponent.ExecuteCommand(commandName, args);
 
        public void AddCodeBaseDirectory(string directory)
        {
            AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
            {
                var path = Path.Combine(directory, new AssemblyName(e.Name).Name + ".dll");
                if (File.Exists(path))
                {
                    return Assembly.LoadFrom(path);
                }
 
                return null;
            };
        }
 
        public void ActivateMainWindow()
        {
            InvokeOnUIThread(() =>
            {
                var dte = GetDTE();
 
                var aa1 = dte.ActiveWindow;
                var s = aa1.Caption;
                nint activeWindow = dte.ActiveWindow.HWnd;
                var activeVisualStudioWindow = (HWND)activeWindow;
                Debug.WriteLine($"DTE.ActiveWindow.HWnd = {activeVisualStudioWindow}");
                if (activeVisualStudioWindow != IntPtr.Zero)
                {
                    if (TrySetForegroundWindow(activeVisualStudioWindow))
                    {
                        return;
                    }
                }
 
                nint mainWindow = dte.MainWindow.HWnd;
                activeVisualStudioWindow = (HWND)mainWindow;
                Debug.WriteLine($"DTE.MainWindow.HWnd = {activeVisualStudioWindow}");
                if (!TrySetForegroundWindow(activeVisualStudioWindow))
                {
                    throw new InvalidOperationException("Failed to set the foreground window.");
                }
            });
        }
 
        /// <summary>
        /// Get the Visual Studio in-memory activity log.
        /// This is always turned on and has a rolling buffer of the last 100 entries, and the first 10 entries, which have general configuration information.
        /// </summary>
        /// <returns>null if no data; error string if error; log info if valid data.</returns>
        internal static string? GetInMemoryActivityLog()
        {
            return InvokeOnUIThread(() =>
            {
                // We get an IVsActivityLogDumper interface by getting the SVsActivityLogService and type casting it to IVSActivityLogDumper
                if (GlobalServiceProvider.ServiceProvider.GetService(typeof(SVsActivityLog)) is not IVsActivityLogDumper vsActivityLogDumper)
                {
                    return null;
                }
 
                // And then using it, get a string that contains the VS in-memory activity log.
                // NOTE: this will return empty if the user explicitly enabled logging (in that case there is no in-memory activity log as it's written to a file)
                var vsActivityLogContents = vsActivityLogDumper.GetActivityLogBuffer();
                if (string.IsNullOrWhiteSpace(vsActivityLogContents))
                {
                    return null;
                }
 
                // The API returns the log with some 0x0 characters at the end that make it not a valid xml file
                var lastIndexOfClosingBracket = vsActivityLogContents.LastIndexOf('>');
                var vsActivityLogBuilder = new StringBuilder(vsActivityLogContents.Remove(lastIndexOfClosingBracket + 1, vsActivityLogContents.Length - lastIndexOfClosingBracket - 1));
 
                // We need to add a root element so that the it's valid xml and can easily be consumed using Xml.Linq
                vsActivityLogBuilder.Insert(0, $"<{VisualStudioActivityLogRoot}>{Environment.NewLine}");
                vsActivityLogBuilder.AppendLine($"{Environment.NewLine}</{VisualStudioActivityLogRoot}>");
 
                return vsActivityLogBuilder.ToString();
            });
        }
 
        internal static string GetIdeState(ImmutableList<KeyValuePair<string, Func<string>>> customIdeStateCollectors)
        {
            return InvokeOnUIThread(() =>
            {
                var stateBuilder = new StringBuilder();
 
                /*
                 * Solution
                 */
                if (GlobalServiceProvider.ServiceProvider.GetService(typeof(SVsSolution)) is IVsSolution solution
                    && solution.GetSolutionInfo(out var solutionDirectory, out var solutionFile, out var userOptsFile) == VSConstants.S_OK)
                {
                    stateBuilder.AppendLine("Solution:");
                    stateBuilder.AppendLine($"  Solution Directory: {solutionDirectory}");
                    stateBuilder.AppendLine($"  Solution File:      {solutionFile}");
                    stateBuilder.AppendLine($"  User Opts File:     {userOptsFile}");
                }
 
                /*
                 * Error list
                 */
                if (GlobalServiceProvider.ServiceProvider.GetService(typeof(SVsTaskList)) is IVsTaskList taskList
                    && taskList.EnumTaskItems(out var enumTaskItems) == VSConstants.S_OK)
                {
                    stateBuilder.AppendLine("Error list:");
 
                    var index = 0;
                    var taskItems = new IVsTaskItem[10];
                    var actual = new uint[1];
                    while (enumTaskItems.Next((uint)taskItems.Length, taskItems, actual) == VSConstants.S_OK)
                    {
                        if (actual[0] == 0)
                        {
                            break;
                        }
 
                        for (var i = 0; i < actual[0]; i++)
                        {
                            var item = taskItems[i];
                            var text = item.get_Text(out var name) == VSConstants.S_OK ? name : string.Empty;
                            stateBuilder.AppendLine($"  {++index}: {name}");
                        }
                    }
                }
 
                /*
                 * Custom collectors
                 */
                foreach (var collector in customIdeStateCollectors)
                {
                    stateBuilder.Append(collector.Key).AppendLine(":");
                    var lines = collector.Value().Replace("\r\n", "\n").Split(new[] { '\n' });
                    foreach (var line in lines)
                    {
                        if (line == string.Empty)
                        {
                            stateBuilder.AppendLine();
                        }
                        else
                        {
                            stateBuilder.Append("  ").AppendLine(line);
                        }
                    }
                }
 
                return stateBuilder.ToString();
            });
        }
 
        public void Quit()
        {
            BeginInvokeOnUIThread(() =>
            {
                var shell = GetGlobalService<SVsUIShell, IVsUIShell>();
                var cmdGroup = VSConstants.GUID_VSStandardCommandSet97;
                var cmdId = VSConstants.VSStd97CmdID.Exit;
                var cmdExecOpt = OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
                Marshal.ThrowExceptionForHR(shell.PostExecCommand(cmdGroup, (uint)cmdId, (uint)cmdExecOpt, pvaIn: null));
            });
        }
 
        private static bool TrySetForegroundWindow(HWND window)
        {
            var activeWindow = PInvoke.GetLastActivePopup(window);
            activeWindow = PInvoke.IsWindowVisible(activeWindow) ? activeWindow : window;
            PInvoke.SwitchToThisWindow(activeWindow, true);
 
            if (!PInvoke.SetForegroundWindow(activeWindow))
            {
                if (!PInvoke.AllocConsole())
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }
 
                try
                {
                    var consoleWindow = PInvoke.GetConsoleWindow();
                    if (consoleWindow == IntPtr.Zero)
                    {
                        throw new InvalidOperationException("Failed to obtain the console window.");
                    }
 
                    if (!PInvoke.SetWindowPos(consoleWindow, hWndInsertAfter: HWND.Null, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOZORDER))
                    {
                        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                    }
                }
                finally
                {
                    if (!PInvoke.FreeConsole())
                    {
                        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                    }
                }
 
                if (!PInvoke.SetForegroundWindow(activeWindow))
                {
                    return false;
                }
            }
 
            return true;
        }
    }
}