From 39c0f3683e9245e70c0b434cc0b55d44080ddbf1 Mon Sep 17 00:00:00 2001 From: Petr Date: Mon, 30 Jan 2023 22:02:39 +0100 Subject: [PATCH 01/12] Trying things out --- FSharp.Editor.sln | 43 ++- eng/Build.ps1 | 1 + .../AbstractIntegrationTest.cs | 14 + .../CreateProjectTests.cs | 42 ++ .../FSharp.Editor.IntegrationTests.csproj | 32 ++ .../InProcess/ErrorListExtensions.cs | 73 ++++ .../InProcess/ErrorListInProcess.cs | 107 ++++++ .../InProcess/SolutionExplorerInProcess.cs | 359 ++++++++++++++++++ .../xunit.runner.json | 7 + 9 files changed, 661 insertions(+), 17 deletions(-) create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListExtensions.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListInProcess.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/xunit.runner.json diff --git a/FSharp.Editor.sln b/FSharp.Editor.sln index 70ffe6bb0e..e69caa45d4 100644 --- a/FSharp.Editor.sln +++ b/FSharp.Editor.sln @@ -1,71 +1,80 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{321F47BC-8148-4C8D-B340-08B7BF07D31D}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{321F47BC-8148-4C8D-B340-08B7BF07D31D}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{24399E68-9000-4556-BDDD-8D74A9660D28}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{24399E68-9000-4556-BDDD-8D74A9660D28}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.PatternMatcher", "vsintegration\src\FSharp.PatternMatcher\FSharp.PatternMatcher.csproj", "{4FFA5E03-4128-48C9-8FCD-D7C60729ED74}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.UIResources", "vsintegration\src\FSharp.UIResources\FSharp.UIResources.csproj", "{DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Editor.IntegrationTests", "vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj", "{42BE0F2F-BC45-437B-851D-E88A83201339}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU Proto|Any CPU = Proto|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Release|Any CPU.ActiveCfg = Release|Any CPU {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Release|Any CPU.Build.0 = Release|Any CPU - {321F47BC-8148-4C8D-B340-08B7BF07D31D}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Release|Any CPU.Build.0 = Release|Any CPU - {AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.Build.0 = Release|Any CPU {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Proto|Any CPU.ActiveCfg = Proto|Any CPU {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Proto|Any CPU.Build.0 = Proto|Any CPU + {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}.Release|Any CPU.Build.0 = Release|Any CPU {24399E68-9000-4556-BDDD-8D74A9660D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {24399E68-9000-4556-BDDD-8D74A9660D28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24399E68-9000-4556-BDDD-8D74A9660D28}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {24399E68-9000-4556-BDDD-8D74A9660D28}.Release|Any CPU.ActiveCfg = Release|Any CPU {24399E68-9000-4556-BDDD-8D74A9660D28}.Release|Any CPU.Build.0 = Release|Any CPU - {24399E68-9000-4556-BDDD-8D74A9660D28}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {86E148BE-92C8-47CC-A070-11D769C6D898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86E148BE-92C8-47CC-A070-11D769C6D898}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86E148BE-92C8-47CC-A070-11D769C6D898}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {86E148BE-92C8-47CC-A070-11D769C6D898}.Release|Any CPU.ActiveCfg = Release|Any CPU {86E148BE-92C8-47CC-A070-11D769C6D898}.Release|Any CPU.Build.0 = Release|Any CPU - {86E148BE-92C8-47CC-A070-11D769C6D898}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Release|Any CPU.Build.0 = Release|Any CPU - {4FFA5E03-4128-48C9-8FCD-D7C60729ED74}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Release|Any CPU.Build.0 = Release|Any CPU - {DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Proto|Any CPU.ActiveCfg = Debug|Any CPU {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Release|Any CPU.Build.0 = Release|Any CPU - {EAC029EB-4A8F-4966-9B38-60D73D8E20D1}.Proto|Any CPU.ActiveCfg = Debug|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Proto|Any CPU.ActiveCfg = Debug|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Proto|Any CPU.Build.0 = Debug|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42BE0F2F-BC45-437B-851D-E88A83201339}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/eng/Build.ps1 b/eng/Build.ps1 index 1d99378357..9ba1093653 100644 --- a/eng/Build.ps1 +++ b/eng/Build.ps1 @@ -613,6 +613,7 @@ try { if ($testVs -and -not $noVisualStudio) { TestUsingNUnit -testProject "$RepoRoot\vsintegration\tests\UnitTests\VisualFSharp.UnitTests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\VisualFSharp.UnitTests\" TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" + TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" } # verify nupkgs have access to the source code diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs new file mode 100644 index 0000000000..fa371e34cd --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs @@ -0,0 +1,14 @@ +// 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 Microsoft.VisualStudio.Extensibility.Testing; +using Xunit; + +namespace Microsoft.CodeAnalysis.Testing +{ + [IdeSettings(MinVersion = VisualStudioVersion.VS2022, MaxAttempts = 2)] + public abstract class AbstractIntegrationTest : AbstractIdeIntegrationTest + { + } +} diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs new file mode 100644 index 0000000000..c99dcb59c4 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs @@ -0,0 +1,42 @@ +// 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 Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.Shell.Interop; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace FSharp.Editor.IntegrationTests +{ + public class CreateProjectTests : AbstractIntegrationTest + { + [IdeFact] + public async Task BasicFSharpLibraryCompilesAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync( + nameof(CreateProjectTests), + HangMitigatingCancellationToken); + + await TestServices.SolutionExplorer.AddProjectAsync( + "Library", + "Microsoft.FSharp.NETCore.ClassLibrary", + HangMitigatingCancellationToken); + + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync( + true, + HangMitigatingCancellationToken); + Assert.Contains( + "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", + buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(HangMitigatingCancellationToken); + var errorCount = await TestServices.ErrorList.GetErrorCountAsync( + __VSERRORCATEGORY.EC_ERROR, + HangMitigatingCancellationToken); + Assert.Equal(0, errorCount); + } + } +} \ No newline at end of file diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj new file mode 100644 index 0000000000..8bd8fb8fa2 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj @@ -0,0 +1,32 @@ + + + + net472 + preview + enable + xunit + false + false + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListExtensions.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListExtensions.cs new file mode 100644 index 0000000000..6477d9d2ec --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListExtensions.cs @@ -0,0 +1,73 @@ +// 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 Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.TableManager; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + internal static class ErrorListExtensions + { + public static __VSERRORCATEGORY GetCategory(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.ErrorSeverity, (__VSERRORCATEGORY)(-1)); + } + + public static string GetBuildTool(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.BuildTool, ""); + } + + public static string? GetPath(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.Path, null); + } + + public static string? GetDocumentName(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.DocumentName, null); + } + + public static int? GetLine(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrNull(StandardTableKeyNames.Line); + } + + public static int? GetColumn(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrNull(StandardTableKeyNames.Column); + } + + public static string? GetErrorCode(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.ErrorCode, null); + } + + public static string? GetText(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.Text, null); + } + + private static T GetValueOrDefault(this ITableEntry tableEntry, string keyName, T defaultValue) + { + if (!tableEntry.TryGetValue(keyName, out T value)) + { + value = defaultValue; + } + + return value; + } + + private static T? GetValueOrNull(this ITableEntry tableEntry, string keyName) + where T : struct + { + if (!tableEntry.TryGetValue(keyName, out T value)) + { + return null; + } + + return value; + } + } +} diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListInProcess.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListInProcess.cs new file mode 100644 index 0000000000..ca81bfbd8a --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/ErrorListInProcess.cs @@ -0,0 +1,107 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Extensibility.Testing; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Shell.TableManager; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + [TestService] + internal partial class ErrorListInProcess + { + public async Task ShowBuildErrorsAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var errorList = await GetRequiredGlobalServiceAsync(cancellationToken); + errorList.AreBuildErrorSourceEntriesShown = true; + errorList.AreOtherErrorSourceEntriesShown = false; + errorList.AreErrorsShown = true; + errorList.AreMessagesShown = true; + errorList.AreWarningsShown = false; + } + + public Task> GetBuildErrorsAsync(CancellationToken cancellationToken) + { + return GetBuildErrorsAsync(__VSERRORCATEGORY.EC_WARNING, cancellationToken); + } + + public async Task> GetBuildErrorsAsync(__VSERRORCATEGORY minimumSeverity, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var errorItems = await GetErrorItemsAsync(cancellationToken); + var list = new List(); + + foreach (var item in errorItems) + { + if (item.GetCategory() > minimumSeverity) + { + continue; + } + + if (!item.TryGetValue(StandardTableKeyNames.ErrorSource, out var errorSource) + || (ErrorSource)errorSource != ErrorSource.Build) + { + continue; + } + + var source = item.GetBuildTool(); + var document = Path.GetFileName(item.GetPath() ?? item.GetDocumentName()) ?? ""; + var line = item.GetLine() ?? -1; + var column = item.GetColumn() ?? -1; + var errorCode = item.GetErrorCode() ?? ""; + var text = item.GetText() ?? ""; + var severity = item.GetCategory() switch + { + __VSERRORCATEGORY.EC_ERROR => "error", + __VSERRORCATEGORY.EC_WARNING => "warning", + __VSERRORCATEGORY.EC_MESSAGE => "info", + var unknown => unknown.ToString(), + }; + + var message = $"({source}) {document}({line + 1}, {column + 1}): {severity} {errorCode}: {text}"; + list.Add(message); + } + + return list + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ThenBy(x => x, StringComparer.Ordinal) + .ToImmutableArray(); + } + + public Task GetErrorCountAsync(CancellationToken cancellationToken) + { + return GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING, cancellationToken); + } + + public async Task GetErrorCountAsync(__VSERRORCATEGORY minimumSeverity, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var errorItems = await GetErrorItemsAsync(cancellationToken); + return errorItems.Count(e => e.GetCategory() <= minimumSeverity); + } + + private async Task> GetErrorItemsAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var errorList = await GetRequiredGlobalServiceAsync(cancellationToken); + var args = await errorList.TableControl.ForceUpdateAsync(); + return args.AllEntries.ToImmutableArray(); + } + } +} diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs new file mode 100644 index 0000000000..38666b78ee --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -0,0 +1,359 @@ +// 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.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.OperationProgress; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using NuGet.SolutionRestoreManager; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.Extensibility.Testing +{ + internal partial class SolutionExplorerInProcess + { + public async Task CreateSolutionAsync(string solutionName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solutionPath = CreateTemporaryPath(); + await CreateSolutionAsync(solutionPath, solutionName, cancellationToken); + } + + private async Task CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + await CloseSolutionAsync(cancellationToken); + + var solutionFileName = Path.ChangeExtension(solutionName, ".sln"); + Directory.CreateDirectory(solutionPath); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT)); + ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); + } + + private async Task GetDirectoryNameAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _)); + if (string.IsNullOrEmpty(solutionFileFullPath)) + { + throw new InvalidOperationException(); + } + + return Path.GetDirectoryName(solutionFileFullPath); + } + + public async Task AddProjectAsync(string projectName, string projectTemplate, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName); + var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, cancellationToken); + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _)); + } + + private async Task GetProjectTemplatePathAsync(string projectTemplate, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + + return solution.GetProjectTemplate(projectTemplate, "FSharp"); + } + + public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + foreach (var project in solution.Projects.OfType()) + { + await RestoreNuGetPackagesAsync(project.FullName, cancellationToken); + } + } + + public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var operationProgressStatus = await GetRequiredGlobalServiceAsync(cancellationToken); + var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); + await stageStatus.WaitForCompletionAsync(); + + var solutionRestoreService = await GetComponentModelServiceAsync(cancellationToken); + await solutionRestoreService.CurrentRestoreOperation; + + var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName; + var solutionRestoreStatusProvider = await GetComponentModelServiceAsync(cancellationToken); + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } + + var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService; + await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken); + + while (true) + { + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } + + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + } + + public async Task?> BuildSolutionAsync(bool waitForBuildToFinish, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(cancellationToken); + buildOutputWindowPane.Clear(); + + await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.BuildSln, cancellationToken); + if (waitForBuildToFinish) + { + return await WaitForBuildToFinishAsync(buildOutputWindowPane, cancellationToken); + } + + return null; + } + + public async Task GetBuildOutputWindowPaneAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var outputWindow = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(outputWindow.GetPane(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid, out var pane)); + return pane; + } + + private async Task> WaitForBuildToFinishAsync(IVsOutputWindowPane buildOutputWindowPane, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var buildManager = await GetRequiredGlobalServiceAsync(cancellationToken); + using var semaphore = new SemaphoreSlim(1); + using var solutionEvents = new UpdateSolutionEvents(buildManager); + + await semaphore.WaitAsync(); + + void HandleUpdateSolutionDone(bool succeeded, bool modified, bool canceled) => semaphore.Release(); + solutionEvents.OnUpdateSolutionDone += HandleUpdateSolutionDone; + try + { + await semaphore.WaitAsync(); + } + finally + { + solutionEvents.OnUpdateSolutionDone -= HandleUpdateSolutionDone; + } + + // Force the error list to update + ErrorHandler.ThrowOnFailure(buildOutputWindowPane.FlushToTaskList()); + + var textView = (IVsTextView)buildOutputWindowPane; + var wpfTextViewHost = await textView.GetTextViewHostAsync(JoinableTaskFactory, cancellationToken); + var lines = wpfTextViewHost.TextView.TextViewLines; + if (lines.Count < 1) + { + return Enumerable.Empty(); + } + + return lines.Select(line => line.Extent.GetText()); + } + + private string CreateTemporaryPath() + { + return Path.Combine(Path.GetTempPath(), "fsharp-test", Path.GetRandomFileName()); + } + + private async Task GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + return solution.Projects.OfType().First( + project => + { + ThreadHelper.ThrowIfNotOnUIThread(); + return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) + || string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase); + }); + } + + internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IVsUpdateSolutionEvents2, IDisposable + { + private uint _cookie; + private IVsSolutionBuildManager2 _solutionBuildManager; + + internal delegate void UpdateSolutionDoneEvent(bool succeeded, bool modified, bool canceled); + + internal delegate void UpdateSolutionBeginEvent(ref bool cancel); + + internal delegate void UpdateSolutionStartUpdateEvent(ref bool cancel); + + internal delegate void UpdateProjectConfigDoneEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig, int success); + + internal delegate void UpdateProjectConfigBeginEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig); + + public event UpdateSolutionDoneEvent? OnUpdateSolutionDone; + + public event UpdateSolutionBeginEvent? OnUpdateSolutionBegin; + + public event UpdateSolutionStartUpdateEvent? OnUpdateSolutionStartUpdate; + + public event Action? OnActiveProjectConfigurationChange; + + public event Action? OnUpdateSolutionCancel; + + public event UpdateProjectConfigDoneEvent? OnUpdateProjectConfigDone; + + public event UpdateProjectConfigBeginEvent? OnUpdateProjectConfigBegin; + + internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _solutionBuildManager = solutionBuildManager; + ErrorHandler.ThrowOnFailure(solutionBuildManager.AdviseUpdateSolutionEvents(this, out _cookie)); + } + + int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } + + int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } + + int IVsUpdateSolutionEvents.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } + + int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } + + int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) + { + OnUpdateProjectConfigBegin?.Invoke(pHierProj, pCfgProj); + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) + { + OnUpdateProjectConfigDone?.Invoke(pHierProj, pCfgProj, fSuccess); + return 0; + } + + private int UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionStartUpdate?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + private int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + OnActiveProjectConfigurationChange?.Invoke(); + return 0; + } + + void IDisposable.Dispose() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + OnUpdateSolutionDone = null; + OnUpdateSolutionBegin = null; + OnUpdateSolutionStartUpdate = null; + OnActiveProjectConfigurationChange = null; + OnUpdateSolutionCancel = null; + OnUpdateProjectConfigDone = null; + OnUpdateProjectConfigBegin = null; + + if (_cookie != 0) + { + var tempCookie = _cookie; + _cookie = 0; + ErrorHandler.ThrowOnFailure(_solutionBuildManager.UnadviseUpdateSolutionEvents(tempCookie)); + } + } + } + } +} diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/xunit.runner.json b/vsintegration/tests/FSharp.Editor.IntegrationTests/xunit.runner.json new file mode 100644 index 0000000000..2d07715ae5 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/xunit.runner.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "ifAvailable", + "shadowCopy": false, + "parallelizeTestCollections": false, + "maxParallelThreads": 1 +} From 1a1872a26e70d3a6fb744bba62712f93172584b8 Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 3 Feb 2023 15:35:53 +0100 Subject: [PATCH 02/12] up --- .../FSharp.Editor.IntegrationTests/CreateProjectTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs index c99dcb59c4..7b0f72e200 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs @@ -11,6 +11,13 @@ namespace FSharp.Editor.IntegrationTests { public class CreateProjectTests : AbstractIntegrationTest { + // This is just starting up a basic F# lib: + + // namespace Library + // + // module Say = + // let hello name = + // printfn "Hello %s" name [IdeFact] public async Task BasicFSharpLibraryCompilesAsync() { From 0b35954de4dacfc2b508c0b20b3e1d76373363ee Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 3 Feb 2023 17:34:49 +0100 Subject: [PATCH 03/12] Update CreateProjectTests.cs --- .../CreateProjectTests.cs | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs index 7b0f72e200..249385c79d 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.Extensibility.Testing; using Microsoft.VisualStudio.Shell.Interop; using Xunit; using Task = System.Threading.Tasks.Task; @@ -11,8 +12,8 @@ namespace FSharp.Editor.IntegrationTests { public class CreateProjectTests : AbstractIntegrationTest { - // This is just starting up a basic F# lib: - + // This is starting up a basic F# lib: + // // namespace Library // // module Say = @@ -21,28 +22,52 @@ public class CreateProjectTests : AbstractIntegrationTest [IdeFact] public async Task BasicFSharpLibraryCompilesAsync() { - await TestServices.SolutionExplorer.CreateSolutionAsync( - nameof(CreateProjectTests), - HangMitigatingCancellationToken); + var token = HangMitigatingCancellationToken; + var solutionExplorer = TestServices.SolutionExplorer; + var errorList = TestServices.ErrorList; - await TestServices.SolutionExplorer.AddProjectAsync( + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync( "Library", "Microsoft.FSharp.NETCore.ClassLibrary", - HangMitigatingCancellationToken); + token); + + await solutionExplorer.RestoreNuGetPackagesAsync(token); + + var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; + var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); + Assert.Contains(expectedBuildSummary, actualBuildSummary); + + await errorList.ShowBuildErrorsAsync(token); + var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); + Assert.Equal(0, errorCount); + } + + // This is starting up a basic F# console app: + // + // // For more information see https://aka.ms/fsharp-console-apps + // printfn "Hello from F#" + [IdeFact] + public async Task BasicFSharpConsoleAppCompilesAsync() + { + var token = HangMitigatingCancellationToken; + var solutionExplorer = TestServices.SolutionExplorer; + var errorList = TestServices.ErrorList; + + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync( + "ConsoleApp", + "Microsoft.FSharp.NETCore.ConsoleApplication", + token); - await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + await solutionExplorer.RestoreNuGetPackagesAsync(token); - var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync( - true, - HangMitigatingCancellationToken); - Assert.Contains( - "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", - buildSummary); + var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; + var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); + Assert.Contains(expectedBuildSummary, actualBuildSummary); - await TestServices.ErrorList.ShowBuildErrorsAsync(HangMitigatingCancellationToken); - var errorCount = await TestServices.ErrorList.GetErrorCountAsync( - __VSERRORCATEGORY.EC_ERROR, - HangMitigatingCancellationToken); + await errorList.ShowBuildErrorsAsync(token); + var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); Assert.Equal(0, errorCount); } } From bfdd353f3c4a05d5a2781128de73994962722875 Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 7 Feb 2023 17:05:00 +0100 Subject: [PATCH 04/12] Update Build.ps1 --- eng/Build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Build.ps1 b/eng/Build.ps1 index 9ba1093653..bc7740b005 100644 --- a/eng/Build.ps1 +++ b/eng/Build.ps1 @@ -613,7 +613,7 @@ try { if ($testVs -and -not $noVisualStudio) { TestUsingNUnit -testProject "$RepoRoot\vsintegration\tests\UnitTests\VisualFSharp.UnitTests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\VisualFSharp.UnitTests\" TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" - TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" + TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.IntegrationTests\" } # verify nupkgs have access to the source code From 35c9e226dabdea9bb65461742aeb7c3ab5584e6f Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 7 Feb 2023 18:27:23 +0100 Subject: [PATCH 05/12] Up --- VisualFSharp.sln | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/VisualFSharp.sln b/VisualFSharp.sln index 821fb3e510..8c5d917b92 100644 --- a/VisualFSharp.sln +++ b/VisualFSharp.sln @@ -195,6 +195,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fsharp.ProfilingStartpointP EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{CBC96CC7-65AB-46EA-A82E-F6A788DABF80}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Editor.IntegrationTests", "vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj", "{E31F9B59-FCF1-4D04-8762-C7BB60285A7B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1033,6 +1035,18 @@ Global {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|Any CPU.Build.0 = Release|Any CPU {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|x86.ActiveCfg = Release|Any CPU {CBC96CC7-65AB-46EA-A82E-F6A788DABF80}.Release|x86.Build.0 = Release|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Debug|x86.ActiveCfg = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Debug|x86.Build.0 = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Proto|Any CPU.ActiveCfg = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Proto|Any CPU.Build.0 = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Proto|x86.ActiveCfg = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Proto|x86.Build.0 = Debug|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Release|Any CPU.Build.0 = Release|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Release|x86.ActiveCfg = Release|Any CPU + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1114,6 +1128,7 @@ Global {39CDF34B-FB23-49AE-AB27-0975DA379BB5} = {DFB6ADD7-3149-43D9-AFA0-FC4A818B472B} {FE23BB65-276A-4E41-8CC7-F7752241DEBA} = {39CDF34B-FB23-49AE-AB27-0975DA379BB5} {CBC96CC7-65AB-46EA-A82E-F6A788DABF80} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2} + {E31F9B59-FCF1-4D04-8762-C7BB60285A7B} = {F7876C9B-FB6A-4EFB-B058-D6967DB75FB2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {48EDBBBE-C8EE-4E3C-8B19-97184A487B37} From 2c70c2a940c67ecb61d737cb5c2cc23ed0e54ee6 Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 7 Feb 2023 19:00:46 +0100 Subject: [PATCH 06/12] up --- .../CreateProjectTests.cs | 43 ++++++++++++------- .../WellKnownProjectTemplates.cs | 12 ++++++ 2 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/WellKnownProjectTemplates.cs diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs index 249385c79d..abec6204c2 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs @@ -12,15 +12,8 @@ namespace FSharp.Editor.IntegrationTests { public class CreateProjectTests : AbstractIntegrationTest { - // This is starting up a basic F# lib: - // - // namespace Library - // - // module Say = - // let hello name = - // printfn "Hello %s" name [IdeFact] - public async Task BasicFSharpLibraryCompilesAsync() + public async Task ClassLibrary_Async() { var token = HangMitigatingCancellationToken; var solutionExplorer = TestServices.SolutionExplorer; @@ -29,7 +22,7 @@ public async Task BasicFSharpLibraryCompilesAsync() await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); await solutionExplorer.AddProjectAsync( "Library", - "Microsoft.FSharp.NETCore.ClassLibrary", + WellKnownProjectTemplates.FSharpNetCoreClassLibrary, token); await solutionExplorer.RestoreNuGetPackagesAsync(token); @@ -43,12 +36,8 @@ await solutionExplorer.AddProjectAsync( Assert.Equal(0, errorCount); } - // This is starting up a basic F# console app: - // - // // For more information see https://aka.ms/fsharp-console-apps - // printfn "Hello from F#" [IdeFact] - public async Task BasicFSharpConsoleAppCompilesAsync() + public async Task ConsoleApp_Async() { var token = HangMitigatingCancellationToken; var solutionExplorer = TestServices.SolutionExplorer; @@ -57,7 +46,31 @@ public async Task BasicFSharpConsoleAppCompilesAsync() await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); await solutionExplorer.AddProjectAsync( "ConsoleApp", - "Microsoft.FSharp.NETCore.ConsoleApplication", + WellKnownProjectTemplates.FSharpNetCoreConsoleApplication, + token); + + await solutionExplorer.RestoreNuGetPackagesAsync(token); + + var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; + var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); + Assert.Contains(expectedBuildSummary, actualBuildSummary); + + await errorList.ShowBuildErrorsAsync(token); + var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); + Assert.Equal(0, errorCount); + } + + [IdeFact] + public async Task XUnitTestProject_Async() + { + var token = HangMitigatingCancellationToken; + var solutionExplorer = TestServices.SolutionExplorer; + var errorList = TestServices.ErrorList; + + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync( + "ConsoleApp", + WellKnownProjectTemplates.FSharpNetCoreXUnitTest, token); await solutionExplorer.RestoreNuGetPackagesAsync(token); diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/WellKnownProjectTemplates.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/WellKnownProjectTemplates.cs new file mode 100644 index 0000000000..25681a013b --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/WellKnownProjectTemplates.cs @@ -0,0 +1,12 @@ +// 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 FSharp.Editor.IntegrationTests; + +internal static class WellKnownProjectTemplates +{ + public const string FSharpNetCoreClassLibrary = "Microsoft.FSharp.NETCore.ClassLibrary"; + public const string FSharpNetCoreConsoleApplication = "Microsoft.FSharp.NETCore.ConsoleApplication"; + public const string FSharpNetCoreXUnitTest = "Microsoft.FSharp.NETCore.XUnitTest"; +} From d890455403451bce1ba4659769324c601f1006de Mon Sep 17 00:00:00 2001 From: Petr Date: Wed, 8 Feb 2023 00:11:06 +0100 Subject: [PATCH 07/12] up --- .../FSharp.Editor.IntegrationTests.csproj | 1 + .../InProcess/EditorInProcess.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/EditorInProcess.cs diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj index 8bd8fb8fa2..904609b524 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj @@ -7,6 +7,7 @@ xunit false false + true diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/EditorInProcess.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/EditorInProcess.cs new file mode 100644 index 0000000000..004953d579 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/EditorInProcess.cs @@ -0,0 +1,31 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Extensibility.Testing; + +internal partial class EditorInProcess +{ + public async Task GetTextAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var textSnapshot = view.TextSnapshot; + return textSnapshot.GetText(); + } + + public async Task SetTextAsync(string text, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await GetActiveTextViewAsync(cancellationToken); + var textSnapshot = view.TextSnapshot; + var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); + view.TextBuffer.Replace(replacementSpan, text); + } +} From 242d7294fc7a632463b51343dd8dd8ea81c09a46 Mon Sep 17 00:00:00 2001 From: Petr Date: Wed, 8 Feb 2023 13:46:59 +0100 Subject: [PATCH 08/12] up --- .../AbstractIntegrationTest.cs | 2 +- .../BuildProjectTests.cs | 67 +++ .../CreateProjectTests.cs | 151 +++--- .../InProcess/SolutionExplorerInProcess.cs | 502 +++++++++--------- .../InProcess/TelemetryInProcess.cs | 80 +++ .../TelemetryTests.cs | 35 ++ 6 files changed, 507 insertions(+), 330 deletions(-) create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/TelemetryInProcess.cs create mode 100644 vsintegration/tests/FSharp.Editor.IntegrationTests/TelemetryTests.cs diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs index fa371e34cd..29109cbb82 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/AbstractIntegrationTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.Testing { - [IdeSettings(MinVersion = VisualStudioVersion.VS2022, MaxAttempts = 2)] + [IdeSettings(MinVersion = VisualStudioVersion.VS2022)] public abstract class AbstractIntegrationTest : AbstractIdeIntegrationTest { } diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs new file mode 100644 index 0000000000..3a4c050621 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs @@ -0,0 +1,67 @@ +// 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 Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.Extensibility.Testing; +using Microsoft.VisualStudio.Shell.Interop; +using System.Threading.Tasks; +using Xunit; + +namespace FSharp.Editor.IntegrationTests; + +public class BuildProjectTests : AbstractIntegrationTest +{ + [IdeFact] + public async Task SuccessfulBuild_Async() + { + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + var code = """ +module Test + +let answer = 42 +"""; + + var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; + + await solutionExplorer.CreateSolutionAsync(nameof(BuildProjectTests), token); + await solutionExplorer.AddProjectAsync("Library", template, token); + await editor.SetTextAsync(code, token); + + var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(token); + + Assert.Contains(expectedBuildSummary, actualBuildSummary); + } + + [IdeFact] + public async Task FailedBuild_Async() + { + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + var errorList = TestServices.ErrorList; + var code = """ +module Test + +let answer = +"""; + + var expectedBuildSummary = "========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped =========="; + var expectedError = "(Compiler) Library.fs(3, 1): error FS0010: Incomplete structured construct at or before this point in binding"; + + await solutionExplorer.CreateSolutionAsync(nameof(BuildProjectTests), token); + await solutionExplorer.AddProjectAsync("Library", template, token); + await editor.SetTextAsync(code, token); + + var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(token); + Assert.Contains(expectedBuildSummary, actualBuildSummary); + + await errorList.ShowBuildErrorsAsync(token); + var errors = await errorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_ERROR, token); + Assert.Contains(expectedError, errors); + } +} \ No newline at end of file diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs index abec6204c2..d519d1329b 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/CreateProjectTests.cs @@ -4,84 +4,85 @@ using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.Extensibility.Testing; -using Microsoft.VisualStudio.Shell.Interop; +using System.Threading.Tasks; using Xunit; -using Task = System.Threading.Tasks.Task; -namespace FSharp.Editor.IntegrationTests +namespace FSharp.Editor.IntegrationTests; + +public class CreateProjectTests : AbstractIntegrationTest { - public class CreateProjectTests : AbstractIntegrationTest + [IdeFact] + public async Task ClassLibrary_Async() { - [IdeFact] - public async Task ClassLibrary_Async() - { - var token = HangMitigatingCancellationToken; - var solutionExplorer = TestServices.SolutionExplorer; - var errorList = TestServices.ErrorList; - - await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); - await solutionExplorer.AddProjectAsync( - "Library", - WellKnownProjectTemplates.FSharpNetCoreClassLibrary, - token); - - await solutionExplorer.RestoreNuGetPackagesAsync(token); - - var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; - var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); - Assert.Contains(expectedBuildSummary, actualBuildSummary); - - await errorList.ShowBuildErrorsAsync(token); - var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); - Assert.Equal(0, errorCount); - } - - [IdeFact] - public async Task ConsoleApp_Async() - { - var token = HangMitigatingCancellationToken; - var solutionExplorer = TestServices.SolutionExplorer; - var errorList = TestServices.ErrorList; - - await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); - await solutionExplorer.AddProjectAsync( - "ConsoleApp", - WellKnownProjectTemplates.FSharpNetCoreConsoleApplication, - token); - - await solutionExplorer.RestoreNuGetPackagesAsync(token); - - var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; - var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); - Assert.Contains(expectedBuildSummary, actualBuildSummary); - - await errorList.ShowBuildErrorsAsync(token); - var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); - Assert.Equal(0, errorCount); - } - - [IdeFact] - public async Task XUnitTestProject_Async() - { - var token = HangMitigatingCancellationToken; - var solutionExplorer = TestServices.SolutionExplorer; - var errorList = TestServices.ErrorList; - - await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); - await solutionExplorer.AddProjectAsync( - "ConsoleApp", - WellKnownProjectTemplates.FSharpNetCoreXUnitTest, - token); - - await solutionExplorer.RestoreNuGetPackagesAsync(token); - - var expectedBuildSummary = "========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped =========="; - var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(true, token); - Assert.Contains(expectedBuildSummary, actualBuildSummary); - - await errorList.ShowBuildErrorsAsync(token); - var errorCount = await errorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR, token); - Assert.Equal(0, errorCount); - } + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + + var expectedCode = """ +namespace Library + +module Say = + let hello name = + printfn "Hello %s" name + +"""; + + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync("Library", template, token); + + var actualCode = await editor.GetTextAsync(token); + + Assert.Equal(expectedCode, actualCode); + } + + [IdeFact] + public async Task ConsoleApp_Async() + { + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreConsoleApplication; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + + var expectedCode = """ +// For more information see https://aka.ms/fsharp-console-apps +printfn "Hello from F#" + +"""; + + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync("ConsoleApp", template, token); + + var actualCode = await editor.GetTextAsync(token); + + Assert.Equal(expectedCode, actualCode); + } + + [IdeFact] + public async Task XUnitTestProject_Async() + { + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreXUnitTest; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + + var expectedCode = """ +module Tests + +open System +open Xunit + +[] +let ``My test`` () = + Assert.True(true) + +"""; + + await solutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests), token); + await solutionExplorer.AddProjectAsync("ConsoleApp", template, token); + + var actualCode = await editor.GetTextAsync(token); + + Assert.Equal(expectedCode, actualCode); } } \ No newline at end of file diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs index 38666b78ee..40835e0718 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -16,343 +16,337 @@ using NuGet.SolutionRestoreManager; using Task = System.Threading.Tasks.Task; -namespace Microsoft.VisualStudio.Extensibility.Testing +namespace Microsoft.VisualStudio.Extensibility.Testing; + +internal partial class SolutionExplorerInProcess { - internal partial class SolutionExplorerInProcess + public async Task CreateSolutionAsync(string solutionName, CancellationToken cancellationToken) { - public async Task CreateSolutionAsync(string solutionName, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var solutionPath = CreateTemporaryPath(); - await CreateSolutionAsync(solutionPath, solutionName, cancellationToken); - } + var solutionPath = CreateTemporaryPath(); + await CreateSolutionAsync(solutionPath, solutionName, cancellationToken); + } - private async Task CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + private async Task CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - await CloseSolutionAsync(cancellationToken); + await CloseSolutionAsync(cancellationToken); - var solutionFileName = Path.ChangeExtension(solutionName, ".sln"); - Directory.CreateDirectory(solutionPath); + var solutionFileName = Path.ChangeExtension(solutionName, ".sln"); + Directory.CreateDirectory(solutionPath); - var solution = await GetRequiredGlobalServiceAsync(cancellationToken); - ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT)); - ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); - } + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT)); + ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); + } + + private async Task GetDirectoryNameAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - private async Task GetDirectoryNameAsync(CancellationToken cancellationToken) + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _)); + if (string.IsNullOrEmpty(solutionFileFullPath)) { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + throw new InvalidOperationException(); + } - var solution = await GetRequiredGlobalServiceAsync(cancellationToken); - ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _)); - if (string.IsNullOrEmpty(solutionFileFullPath)) - { - throw new InvalidOperationException(); - } + return Path.GetDirectoryName(solutionFileFullPath); + } - return Path.GetDirectoryName(solutionFileFullPath); - } + public async Task AddProjectAsync(string projectName, string projectTemplate, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - public async Task AddProjectAsync(string projectName, string projectTemplate, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName); + var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, cancellationToken); + var solution = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _)); + } - var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName); - var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, cancellationToken); - var solution = await GetRequiredGlobalServiceAsync(cancellationToken); - ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _)); - } + private async Task GetProjectTemplatePathAsync(string projectTemplate, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - private async Task GetProjectTemplatePathAsync(string projectTemplate, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; - var dte = await GetRequiredGlobalServiceAsync(cancellationToken); - var solution = (EnvDTE80.Solution2)dte.Solution; + return solution.GetProjectTemplate(projectTemplate, "FSharp"); + } - return solution.GetProjectTemplate(projectTemplate, "FSharp"); - } + public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken) + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + foreach (var project in solution.Projects.OfType()) { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - var dte = await GetRequiredGlobalServiceAsync(cancellationToken); - var solution = (EnvDTE80.Solution2)dte.Solution; - foreach (var project in solution.Projects.OfType()) - { - await RestoreNuGetPackagesAsync(project.FullName, cancellationToken); - } + await RestoreNuGetPackagesAsync(project.FullName, cancellationToken); } + } - public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var operationProgressStatus = await GetRequiredGlobalServiceAsync(cancellationToken); + var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); + await stageStatus.WaitForCompletionAsync(); - var operationProgressStatus = await GetRequiredGlobalServiceAsync(cancellationToken); - var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); - await stageStatus.WaitForCompletionAsync(); + var solutionRestoreService = await GetComponentModelServiceAsync(cancellationToken); + await solutionRestoreService.CurrentRestoreOperation; - var solutionRestoreService = await GetComponentModelServiceAsync(cancellationToken); - await solutionRestoreService.CurrentRestoreOperation; + var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName; + var solutionRestoreStatusProvider = await GetComponentModelServiceAsync(cancellationToken); + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } - var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName; - var solutionRestoreStatusProvider = await GetComponentModelServiceAsync(cancellationToken); + var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService; + await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken); + + while (true) + { if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) { return; } - var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService; - await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken); + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + } - while (true) - { - if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) - { - return; - } + public async Task?> BuildSolutionAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); - } - } + var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(cancellationToken); + buildOutputWindowPane.Clear(); - public async Task?> BuildSolutionAsync(bool waitForBuildToFinish, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.BuildSln, cancellationToken); + return await WaitForBuildToFinishAsync(buildOutputWindowPane, cancellationToken); + } + + public async Task GetBuildOutputWindowPaneAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(cancellationToken); - buildOutputWindowPane.Clear(); + var outputWindow = await GetRequiredGlobalServiceAsync(cancellationToken); + ErrorHandler.ThrowOnFailure(outputWindow.GetPane(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid, out var pane)); + return pane; + } - await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.BuildSln, cancellationToken); - if (waitForBuildToFinish) - { - return await WaitForBuildToFinishAsync(buildOutputWindowPane, cancellationToken); - } + private async Task> WaitForBuildToFinishAsync(IVsOutputWindowPane buildOutputWindowPane, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return null; - } + var buildManager = await GetRequiredGlobalServiceAsync(cancellationToken); + using var semaphore = new SemaphoreSlim(1); + using var solutionEvents = new UpdateSolutionEvents(buildManager); - public async Task GetBuildOutputWindowPaneAsync(CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await semaphore.WaitAsync(); - var outputWindow = await GetRequiredGlobalServiceAsync(cancellationToken); - ErrorHandler.ThrowOnFailure(outputWindow.GetPane(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid, out var pane)); - return pane; + void HandleUpdateSolutionDone(bool succeeded, bool modified, bool canceled) => semaphore.Release(); + solutionEvents.OnUpdateSolutionDone += HandleUpdateSolutionDone; + try + { + await semaphore.WaitAsync(); } + finally + { + solutionEvents.OnUpdateSolutionDone -= HandleUpdateSolutionDone; + } + + // Force the error list to update + ErrorHandler.ThrowOnFailure(buildOutputWindowPane.FlushToTaskList()); - private async Task> WaitForBuildToFinishAsync(IVsOutputWindowPane buildOutputWindowPane, CancellationToken cancellationToken) + var textView = (IVsTextView)buildOutputWindowPane; + var wpfTextViewHost = await textView.GetTextViewHostAsync(JoinableTaskFactory, cancellationToken); + var lines = wpfTextViewHost.TextView.TextViewLines; + if (lines.Count < 1) { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + return Enumerable.Empty(); + } - var buildManager = await GetRequiredGlobalServiceAsync(cancellationToken); - using var semaphore = new SemaphoreSlim(1); - using var solutionEvents = new UpdateSolutionEvents(buildManager); + return lines.Select(line => line.Extent.GetText()); + } - await semaphore.WaitAsync(); + private string CreateTemporaryPath() + { + return Path.Combine(Path.GetTempPath(), "fsharp-test", Path.GetRandomFileName()); + } - void HandleUpdateSolutionDone(bool succeeded, bool modified, bool canceled) => semaphore.Release(); - solutionEvents.OnUpdateSolutionDone += HandleUpdateSolutionDone; - try - { - await semaphore.WaitAsync(); - } - finally + private async Task GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var dte = await GetRequiredGlobalServiceAsync(cancellationToken); + var solution = (EnvDTE80.Solution2)dte.Solution; + return solution.Projects.OfType().First( + project => { - solutionEvents.OnUpdateSolutionDone -= HandleUpdateSolutionDone; - } + ThreadHelper.ThrowIfNotOnUIThread(); + return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) + || string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase); + }); + } - // Force the error list to update - ErrorHandler.ThrowOnFailure(buildOutputWindowPane.FlushToTaskList()); + internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IVsUpdateSolutionEvents2, IDisposable + { + private uint _cookie; + private IVsSolutionBuildManager2 _solutionBuildManager; - var textView = (IVsTextView)buildOutputWindowPane; - var wpfTextViewHost = await textView.GetTextViewHostAsync(JoinableTaskFactory, cancellationToken); - var lines = wpfTextViewHost.TextView.TextViewLines; - if (lines.Count < 1) - { - return Enumerable.Empty(); - } + internal delegate void UpdateSolutionDoneEvent(bool succeeded, bool modified, bool canceled); - return lines.Select(line => line.Extent.GetText()); - } + internal delegate void UpdateSolutionBeginEvent(ref bool cancel); - private string CreateTemporaryPath() - { - return Path.Combine(Path.GetTempPath(), "fsharp-test", Path.GetRandomFileName()); - } + internal delegate void UpdateSolutionStartUpdateEvent(ref bool cancel); - private async Task GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - var dte = await GetRequiredGlobalServiceAsync(cancellationToken); - var solution = (EnvDTE80.Solution2)dte.Solution; - return solution.Projects.OfType().First( - project => - { - ThreadHelper.ThrowIfNotOnUIThread(); - return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) - || string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase); - }); - } + internal delegate void UpdateProjectConfigDoneEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig, int success); - internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IVsUpdateSolutionEvents2, IDisposable - { - private uint _cookie; - private IVsSolutionBuildManager2 _solutionBuildManager; + internal delegate void UpdateProjectConfigBeginEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig); - internal delegate void UpdateSolutionDoneEvent(bool succeeded, bool modified, bool canceled); + public event UpdateSolutionDoneEvent? OnUpdateSolutionDone; - internal delegate void UpdateSolutionBeginEvent(ref bool cancel); + public event UpdateSolutionBeginEvent? OnUpdateSolutionBegin; - internal delegate void UpdateSolutionStartUpdateEvent(ref bool cancel); + public event UpdateSolutionStartUpdateEvent? OnUpdateSolutionStartUpdate; - internal delegate void UpdateProjectConfigDoneEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig, int success); + public event Action? OnActiveProjectConfigurationChange; - internal delegate void UpdateProjectConfigBeginEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig); + public event Action? OnUpdateSolutionCancel; - public event UpdateSolutionDoneEvent? OnUpdateSolutionDone; + public event UpdateProjectConfigDoneEvent? OnUpdateProjectConfigDone; - public event UpdateSolutionBeginEvent? OnUpdateSolutionBegin; + public event UpdateProjectConfigBeginEvent? OnUpdateProjectConfigBegin; - public event UpdateSolutionStartUpdateEvent? OnUpdateSolutionStartUpdate; + internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) + { + ThreadHelper.ThrowIfNotOnUIThread(); - public event Action? OnActiveProjectConfigurationChange; + _solutionBuildManager = solutionBuildManager; + ErrorHandler.ThrowOnFailure(solutionBuildManager.AdviseUpdateSolutionEvents(this, out _cookie)); + } - public event Action? OnUpdateSolutionCancel; + int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } - public event UpdateProjectConfigDoneEvent? OnUpdateProjectConfigDone; + return 0; + } - public event UpdateProjectConfigBeginEvent? OnUpdateProjectConfigBegin; + int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } - internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) - { - ThreadHelper.ThrowIfNotOnUIThread(); + int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } - _solutionBuildManager = solutionBuildManager; - ErrorHandler.ThrowOnFailure(solutionBuildManager.AdviseUpdateSolutionEvents(this, out _cookie)); - } + int IVsUpdateSolutionEvents.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } - int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) - { - var cancel = false; - OnUpdateSolutionBegin?.Invoke(ref cancel); - if (cancel) - { - pfCancelUpdate = 1; - } - - return 0; - } + int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } - int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) { - OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); - return 0; + pfCancelUpdate = 1; } - int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) - { - return UpdateSolution_StartUpdate(ref pfCancelUpdate); - } + return 0; + } - int IVsUpdateSolutionEvents.UpdateSolution_Cancel() - { - OnUpdateSolutionCancel?.Invoke(); - return 0; - } + int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } - int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) - { - return OnActiveProjectCfgChange(pIVsHierarchy); - } + int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } - int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) - { - var cancel = false; - OnUpdateSolutionBegin?.Invoke(ref cancel); - if (cancel) - { - pfCancelUpdate = 1; - } - - return 0; - } + int IVsUpdateSolutionEvents2.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } - int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) - { - OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); - return 0; - } + int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } - int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate) - { - return UpdateSolution_StartUpdate(ref pfCancelUpdate); - } + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) + { + OnUpdateProjectConfigBegin?.Invoke(pHierProj, pCfgProj); + return 0; + } - int IVsUpdateSolutionEvents2.UpdateSolution_Cancel() - { - OnUpdateSolutionCancel?.Invoke(); - return 0; - } + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) + { + OnUpdateProjectConfigDone?.Invoke(pHierProj, pCfgProj, fSuccess); + return 0; + } - int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + private int UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionStartUpdate?.Invoke(ref cancel); + if (cancel) { - return OnActiveProjectCfgChange(pIVsHierarchy); + pfCancelUpdate = 1; } - int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) - { - OnUpdateProjectConfigBegin?.Invoke(pHierProj, pCfgProj); - return 0; - } + return 0; + } - int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) - { - OnUpdateProjectConfigDone?.Invoke(pHierProj, pCfgProj, fSuccess); - return 0; - } + private int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + OnActiveProjectConfigurationChange?.Invoke(); + return 0; + } - private int UpdateSolution_StartUpdate(ref int pfCancelUpdate) - { - var cancel = false; - OnUpdateSolutionStartUpdate?.Invoke(ref cancel); - if (cancel) - { - pfCancelUpdate = 1; - } - - return 0; - } + void IDisposable.Dispose() + { + ThreadHelper.ThrowIfNotOnUIThread(); - private int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) - { - OnActiveProjectConfigurationChange?.Invoke(); - return 0; - } + OnUpdateSolutionDone = null; + OnUpdateSolutionBegin = null; + OnUpdateSolutionStartUpdate = null; + OnActiveProjectConfigurationChange = null; + OnUpdateSolutionCancel = null; + OnUpdateProjectConfigDone = null; + OnUpdateProjectConfigBegin = null; - void IDisposable.Dispose() + if (_cookie != 0) { - ThreadHelper.ThrowIfNotOnUIThread(); - - OnUpdateSolutionDone = null; - OnUpdateSolutionBegin = null; - OnUpdateSolutionStartUpdate = null; - OnActiveProjectConfigurationChange = null; - OnUpdateSolutionCancel = null; - OnUpdateProjectConfigDone = null; - OnUpdateProjectConfigBegin = null; - - if (_cookie != 0) - { - var tempCookie = _cookie; - _cookie = 0; - ErrorHandler.ThrowOnFailure(_solutionBuildManager.UnadviseUpdateSolutionEvents(tempCookie)); - } + var tempCookie = _cookie; + _cookie = 0; + ErrorHandler.ThrowOnFailure(_solutionBuildManager.UnadviseUpdateSolutionEvents(tempCookie)); } } } diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/TelemetryInProcess.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/TelemetryInProcess.cs new file mode 100644 index 0000000000..a8209cdc51 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/InProcess/TelemetryInProcess.cs @@ -0,0 +1,80 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Telemetry; +using Microsoft.VisualStudio.Threading; +using IAsyncDisposable = System.IAsyncDisposable; + +namespace Microsoft.VisualStudio.Extensibility.Testing; + +[TestService] +internal partial class TelemetryInProcess +{ + internal async Task EnableTestTelemetryChannelAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + TelemetryService.DetachTestChannel(LoggerTestChannel.Instance); + LoggerTestChannel.Instance.Clear(); + TelemetryService.AttachTestChannel(LoggerTestChannel.Instance); + return new TelemetryChannel(TestServices); + } + + internal async Task DisableTestTelemetryChannelAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + TelemetryService.DetachTestChannel(LoggerTestChannel.Instance); + LoggerTestChannel.Instance.Clear(); + } + + public async Task TryWaitForTelemetryEventsAsync(string eventName, CancellationToken cancellationToken) + => await LoggerTestChannel.Instance.TryWaitForEventsAsync(eventName, cancellationToken); + + public class TelemetryChannel : IAsyncDisposable + { + internal TestServices _testServices; + + public TelemetryChannel(TestServices testServices) + { + _testServices = testServices; + } + + public async ValueTask DisposeAsync() + => await _testServices.Telemetry.DisableTestTelemetryChannelAsync(CancellationToken.None); + + public async Task GetEventAsync(string eventName, CancellationToken cancellationToken) + => await _testServices.Telemetry.TryWaitForTelemetryEventsAsync(eventName, cancellationToken); + } + + private sealed class LoggerTestChannel : ITelemetryTestChannel + { + public static readonly LoggerTestChannel Instance = new(); + + private AsyncQueue _eventsQueue = new(); + + public async Task TryWaitForEventsAsync(string eventName, CancellationToken cancellationToken) + { + while (true) + { + var result = await _eventsQueue.DequeueAsync(cancellationToken); + if (result.Name == eventName) + { + return result; + } + } + } + + public void Clear() + { + _eventsQueue.Complete(); + _eventsQueue = new AsyncQueue(); + } + + void ITelemetryTestChannel.OnPostEvent(object sender, TelemetryTestChannelEventArgs e) + { + _eventsQueue.Enqueue(e.Event); + } + } +} diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/TelemetryTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/TelemetryTests.cs new file mode 100644 index 0000000000..ddb11260bf --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/TelemetryTests.cs @@ -0,0 +1,35 @@ +using Microsoft.CodeAnalysis.Testing; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace FSharp.Editor.IntegrationTests; + +public class TelemetryTests : AbstractIntegrationTest +{ + [IdeFact] + public async Task BasicFSharpTelemetry_Async() + { + var token = HangMitigatingCancellationToken; + var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary; + var solutionExplorer = TestServices.SolutionExplorer; + var editor = TestServices.Editor; + var telemetry = TestServices.Telemetry; + + await using var telemetryChannel = await telemetry.EnableTestTelemetryChannelAsync(token); + await solutionExplorer.CreateSolutionAsync(nameof(TelemetryTests), token); + await solutionExplorer.AddProjectAsync("Library", template, token); + await solutionExplorer.BuildSolutionAsync(token); + + var eventName = "vs/projectsystem/cps/loadcpsproject"; + var @event = await telemetryChannel.GetEventAsync(eventName, token); + + var propKey = "VS.ProjectSystem.Cps.Project.Extension"; + Assert.True(@event.Properties.ContainsKey(propKey)); + + var propValue = @event.Properties[propKey].ToString(); + Assert.Equal("fsproj", propValue); + } +} From 2838e821ebd1b9be12d68269b161d3f6eafb132d Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 10 Feb 2023 20:03:05 +0100 Subject: [PATCH 09/12] Up --- eng/Versions.props | 4 ++++ .../FSharp.Editor.IntegrationTests.csproj | 11 +++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 0939c8b567..dfba5d9f24 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -164,6 +164,10 @@ $(VisualStudioEditorPackagesVersion) $(VisualStudioEditorPackagesVersion) $(VisualStudioEditorPackagesVersion) + 5.6.0 + 0.1.162-beta + $(MicrosoftVisualStudioExtensibilityTestingVersion) + $(MicrosoftVisualStudioExtensibilityTestingVersion) $(MicrosoftVisualStudioThreadingPackagesVersion) diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj index 904609b524..f52bfdf9ba 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/FSharp.Editor.IntegrationTests.csproj @@ -21,12 +21,11 @@ - - - - - - + + + + + From 96cebc2b5dd637a3813f4cedea12d5066785a6f0 Mon Sep 17 00:00:00 2001 From: Petr Date: Mon, 13 Feb 2023 17:14:02 +0100 Subject: [PATCH 10/12] trying running separately --- azure-pipelines.yml | 5 ++++- eng/Build.ps1 | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3281f54c37..cfda1cc77d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -332,7 +332,7 @@ stages: demands: ImageOverride -equals $(WindowsMachineQueueName) timeoutInMinutes: 120 strategy: - maxParallel: 4 + maxParallel: 5 matrix: desktop_release: _configuration: Release @@ -346,6 +346,9 @@ stages: vs_release: _configuration: Release _testKind: testVs + inttests_release: + _configuration: Release + _testKind: testIntegration steps: - checkout: self clean: true diff --git a/eng/Build.ps1 b/eng/Build.ps1 index bc7740b005..7eefc49384 100644 --- a/eng/Build.ps1 +++ b/eng/Build.ps1 @@ -55,6 +55,7 @@ param ( [switch]$testCompilerComponentTests, [switch]$testFSharpCore, [switch]$testFSharpQA, + [switch]$testIntegration, [switch]$testScripting, [switch]$testVs, [switch]$testAll, @@ -102,6 +103,7 @@ function Print-Usage() { Write-Host " -testCoreClr Run tests against CoreCLR" Write-Host " -testFSharpCore Run FSharpCore unit tests" Write-Host " -testFSharpQA Run F# Cambridge tests" + Write-Host " -testIntegration Run F# integration tests" Write-Host " -testScripting Run Scripting tests" Write-Host " -testVs Run F# editor unit tests" Write-Host " -testpack Verify built packages" @@ -142,6 +144,7 @@ function Process-Arguments() { $script:testDesktop = $True $script:testCoreClr = $True $script:testFSharpQA = $True + $script:testIntegration = $True $script:testVs = $True } @@ -154,6 +157,7 @@ function Process-Arguments() { $script:testCoreClr = $False $script:testFSharpCore = $False $script:testFSharpQA = $False + $script:testIntegration = $False $script:testVs = $False $script:testpack = $False $script:verifypackageshipstatus = $True @@ -613,6 +617,9 @@ try { if ($testVs -and -not $noVisualStudio) { TestUsingNUnit -testProject "$RepoRoot\vsintegration\tests\UnitTests\VisualFSharp.UnitTests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\VisualFSharp.UnitTests\" TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj" + } + + if ($testIntegration) { TestUsingXUnit -testProject "$RepoRoot\vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj" -targetFramework $desktopTargetFramework -testadapterpath "$ArtifactsDir\bin\FSharp.Editor.IntegrationTests\" } From 31599382f036b06f0a062b9c713224cfb64b390d Mon Sep 17 00:00:00 2001 From: Petr Date: Mon, 13 Feb 2023 18:45:06 +0100 Subject: [PATCH 11/12] Update FSharp.Editor.sln --- FSharp.Editor.sln | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/FSharp.Editor.sln b/FSharp.Editor.sln index e69caa45d4..f96eabefef 100644 --- a/FSharp.Editor.sln +++ b/FSharp.Editor.sln @@ -3,21 +3,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{321F47BC-8148-4C8D-B340-08B7BF07D31D}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor.Tests", "vsintegration\tests\FSharp.Editor.Tests\FSharp.Editor.Tests.fsproj", "{321F47BC-8148-4C8D-B340-08B7BF07D31D}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler.Service", "src\Compiler\FSharp.Compiler.Service.fsproj", "{AD603EF2-FAC6-48D1-AAEB-A6CF898062A9}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Core", "src\FSharp.Core\FSharp.Core.fsproj", "{67DA0BF3-AAD3-47F4-9EC6-AD8EC532B5A9}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{24399E68-9000-4556-BDDD-8D74A9660D28}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.DependencyManager.Nuget", "src\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj", "{24399E68-9000-4556-BDDD-8D74A9660D28}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Editor", "vsintegration\src\FSharp.Editor\FSharp.Editor.fsproj", "{86E148BE-92C8-47CC-A070-11D769C6D898}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.PatternMatcher", "vsintegration\src\FSharp.PatternMatcher\FSharp.PatternMatcher.csproj", "{4FFA5E03-4128-48C9-8FCD-D7C60729ED74}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.UIResources", "vsintegration\src\FSharp.UIResources\FSharp.UIResources.csproj", "{DA9495E6-BEAA-42A4-AD3B-170D2005AF4B}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.VS.FSI", "vsintegration\src\FSharp.VS.FSI\FSharp.VS.FSI.fsproj", "{EAC029EB-4A8F-4966-9B38-60D73D8E20D1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Editor.IntegrationTests", "vsintegration\tests\FSharp.Editor.IntegrationTests\FSharp.Editor.IntegrationTests.csproj", "{42BE0F2F-BC45-437B-851D-E88A83201339}" EndProject From 9cf1826fc6d882c1d14adc3bce111b9f07960e32 Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 14 Feb 2023 13:36:10 +0100 Subject: [PATCH 12/12] Update BuildProjectTests.cs --- .../tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs b/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs index 3a4c050621..f84ee5d5cc 100644 --- a/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs +++ b/vsintegration/tests/FSharp.Editor.IntegrationTests/BuildProjectTests.cs @@ -29,6 +29,7 @@ module Test await solutionExplorer.CreateSolutionAsync(nameof(BuildProjectTests), token); await solutionExplorer.AddProjectAsync("Library", template, token); + await solutionExplorer.RestoreNuGetPackagesAsync(token); await editor.SetTextAsync(code, token); var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(token); @@ -55,6 +56,7 @@ module Test await solutionExplorer.CreateSolutionAsync(nameof(BuildProjectTests), token); await solutionExplorer.AddProjectAsync("Library", template, token); + await solutionExplorer.RestoreNuGetPackagesAsync(token); await editor.SetTextAsync(code, token); var actualBuildSummary = await solutionExplorer.BuildSolutionAsync(token);