* Create VS Razor Extension Integration tests

Co-authored-by: Sam Harwell <Sam.Harwell@microsoft.com>
This commit is contained in:
Ryan Brandenburg 2022-01-10 13:45:02 -08:00 коммит произвёл GitHub
Родитель 7a7f566aa0
Коммит 4dba549b69
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
40 изменённых файлов: 1942 добавлений и 21 удалений

Просмотреть файл

@ -66,7 +66,7 @@ stages:
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open
${{ if ne(variables['System.TeamProject'], 'public') }}:
name: NetCore1ESPool-Internal
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Main
steps:
- task: NodeTool@0
displayName: Install Node 10.x
@ -108,10 +108,10 @@ stages:
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
name: NetCore1ESPool-Public
demands: ImageOverride -equals build.windows.10.amd64.vs2019.pre.open
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open
${{ if ne(variables['System.TeamProject'], 'public') }}:
name: NetCore1ESPool-Internal
demands: ImageOverride -equals build.windows.10.amd64.vs2019.pre
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre
strategy:
matrix:
${{ if eq(variables['System.TeamProject'], 'public') }}:
@ -141,6 +141,7 @@ stages:
- _DotNetPublishToBlobFeed : false
- _PublishBlobFeedUrl: https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json
- _BuildArgs: ''
- XUNIT_LOGS: '$(Build.SourcesDirectory)\artifacts\log\Debug'
# Variables for internal Official builds
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:

Просмотреть файл

@ -90,7 +90,7 @@
<!-- Several packages share the MS.CA.Testing version -->
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.21103.2</Tooling_MicrosoftCodeAnalysisTestingVersion>
<MicrosoftVisualStudioShellPackagesVersion>17.0.31723.112</MicrosoftVisualStudioShellPackagesVersion>
<MicrosoftVisualStudioPackagesVersion>17.0.448</MicrosoftVisualStudioPackagesVersion>
<MicrosoftVisualStudioPackagesVersion>17.0.487</MicrosoftVisualStudioPackagesVersion>
<RoslynPackageVersion>4.1.0-1.21471.13</RoslynPackageVersion>
<VisualStudioLanguageServerProtocolVersion>17.1.2</VisualStudioLanguageServerProtocolVersion>
</PropertyGroup>
@ -105,6 +105,8 @@
<MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>
<OmniSharpMicrosoftExtensionsLoggingPackageVersion>2.0.0</OmniSharpMicrosoftExtensionsLoggingPackageVersion>
<MicrosoftVisualStudioEditorPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioEditorPackageVersion>
<MicrosoftVisualStudioExtensibilityTestingXunitVersion>0.1.122-beta</MicrosoftVisualStudioExtensibilityTestingXunitVersion>
<MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>$(MicrosoftVisualStudioExtensibilityTestingXunitVersion)</MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>
<MicrosoftVisualStudioLanguagePackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioLanguagePackageVersion>
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
<MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>17.1.11</MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>
@ -130,6 +132,7 @@
<MonoDevelopSdkPackageVersion>1.0.15</MonoDevelopSdkPackageVersion>
<MoqPackageVersion>4.16.0</MoqPackageVersion>
<NerdbankStreamsPackageVersion>2.7.74</NerdbankStreamsPackageVersion>
<NuGetSolutionRestoreManagerInteropVersion>4.8.0</NuGetSolutionRestoreManagerInteropVersion>
<OmniSharpExtensionsLanguageServerPackageVersion>0.19.5</OmniSharpExtensionsLanguageServerPackageVersion>
<OmniSharpExtensionsLanguageProtocolPackageVersion>$(OmniSharpExtensionsLanguageServerPackageVersion)</OmniSharpExtensionsLanguageProtocolPackageVersion>
<OmniSharpMSBuildPackageVersion>1.37.13</OmniSharpMSBuildPackageVersion>

Просмотреть файл

@ -93,6 +93,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\..\.editorconfig = ..\..\.editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Razor.Integration.Test", "test\Microsoft.VisualStudio.Razor.Integration.Test\Microsoft.VisualStudio.Razor.Integration.Test.csproj", "{8CEC0991-259F-4313-B3EF-E398F2B40E61}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -405,6 +407,14 @@ Global
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.Release|Any CPU.Build.0 = Release|Any CPU
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Release|Any CPU.Build.0 = Release|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -448,6 +458,7 @@ Global
{1C7BBF16-3507-4A23-91B4-9EEA03409C72} = {92463391-81BE-462B-AC3C-78C6C760741F}
{39233703-B752-43AC-AD86-E9D3E61B4AD9} = {92463391-81BE-462B-AC3C-78C6C760741F}
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{8CEC0991-259F-4313-B3EF-E398F2B40E61} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}

Просмотреть файл

@ -52,13 +52,13 @@ export class RazorLogger implements vscode.Disposable {
this.logAlways(warningPrefixedMessage);
}
public logError(message: string, error: Error) {
// Always log errors
const errorPrefixedMessage = `(Error) ${message}
${error.message}
Stack Trace:
${error.stack}`;
this.logAlways(errorPrefixedMessage);
public logError(message: string, error: unknown) {
if (error instanceof Error) {
this.logErrorInternal(message, error);
} else {
const errorMsg = String(error);
this.logErrorInternal(message, Error(errorMsg));
}
}
public logMessage(message: string) {
@ -77,6 +77,15 @@ ${error.stack}`;
this.outputChannel.dispose();
}
private logErrorInternal(message: string, error: Error) {
// Always log errors
const errorPrefixedMessage = `(Error) ${message}
${error.message}
Stack Trace:
${error.stack}`;
this.logAlways(errorPrefixedMessage);
}
private logWithMarker(message: string) {
const timeString = new Date().toLocaleTimeString();
const markedMessage = `[Client - ${timeString}] ${message}`;

Просмотреть файл

@ -27,12 +27,26 @@ export class TelemetryReporter {
this.eventStream.post(traceLevelEvent);
}
public reportErrorOnServerStart(error: Error) {
this.reportError('VSCode.Razor.ErrorOnServerStart', error);
public reportErrorOnServerStart(error: unknown) {
let realError;
if (error instanceof Error) {
realError = error;
} else {
realError = Error(String(error));
}
this.reportError('VSCode.Razor.ErrorOnServerStart', realError);
}
public reportErrorOnActivation(error: Error) {
this.reportError('VSCode.Razor.ErrorOnActivation', error);
public reportErrorOnActivation(error: unknown) {
let realError;
if (error instanceof Error) {
realError = error;
} else {
realError = Error(String(error));
}
this.reportError('VSCode.Razor.ErrorOnActivation', realError);
}
public reportDebugLanguageServer() {

Просмотреть файл

@ -7,6 +7,8 @@ using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
@ -59,16 +61,33 @@ namespace Microsoft.AspNetCore.Razor.Language
return false;
}
public async Task<string> ReadAllTextAsync(CancellationToken cancellationToken)
{
using (var reader = new StreamReader(OpenRead()))
{
var contents = await reader.ReadToEndAsync();
return NormalizeContents(contents);
}
}
public string ReadAllText()
{
using (var reader = new StreamReader(OpenRead()))
{
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
// since all the unit tests rely on the assumption that the files will have \r\n endings.
return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
var contents = reader.ReadToEnd();
return NormalizeContents(contents);
}
}
private static string NormalizeContents(string contents)
{
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
// since all the unit tests rely on the assumption that the files will have \r\n endings.
return contents.Replace("\r", "").Replace("\n", "\r\n");
}
/// <summary>
/// Saves the file to the specified path.
/// </summary>

Просмотреть файл

@ -10,9 +10,10 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public static class TestProject
{
public static string GetProjectDirectory(Type type)
public static string GetProjectDirectory(Type type, bool useCurrentDirectory = false)
{
var repoRoot = SearchUp(AppContext.BaseDirectory, "global.json");
var baseDir = useCurrentDirectory ? Directory.GetCurrentDirectory() : AppContext.BaseDirectory;
var repoRoot = SearchUp(baseDir, "global.json");
var assemblyName = type.Assembly.GetName().Name;
var projectDirectory = Path.Combine(repoRoot, "src", "Razor", "test", assemblyName);
if (!Directory.Exists(projectDirectory) &&

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public abstract class AbstractEditorTest : AbstractIntegrationTest
{
private readonly string? _solutionName;
private readonly string? _projectName;
private readonly string? _projectTemplate;
protected AbstractEditorTest()
{
}
protected AbstractEditorTest(string solutionName)
: this(solutionName, WellKnownProjectTemplates.BlazorProject, "BlazorProject")
{
}
protected AbstractEditorTest(string solutionName, string projectTemplate, string projectName)
{
_solutionName = solutionName;
_projectTemplate = projectTemplate;
_projectName = projectName;
}
protected abstract string LanguageName { get; }
public override async Task InitializeAsync()
{
await base.InitializeAsync();
if (_solutionName is not null)
{
RazorDebug.AssertNotNull(_projectTemplate);
RazorDebug.AssertNotNull(_projectName);
await TestServices.SolutionExplorer.CreateSolutionAsync(_solutionName, HangMitigatingCancellationToken);
await TestServices.SolutionExplorer.AddProjectAsync(_projectName, _projectTemplate, LanguageName, HangMitigatingCancellationToken);
await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(ProjectName, HangMitigatingCancellationToken);
}
}
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using Microsoft.VisualStudio.Extensibility.Testing;
using Xunit;
using Xunit.Sdk;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
// TODO: Start collecting LogFiles on failure
/// <remarks>
/// The following is the xunit execution order:
///
/// <list type="number">
/// <item><description>Instance constructor</description></item>
/// <item><description><see cref="IAsyncLifetime.InitializeAsync"/></description></item>
/// <item><description><see cref="BeforeAfterTestAttribute.Before"/></description></item>
/// <item><description>Test method</description></item>
/// <item><description><see cref="BeforeAfterTestAttribute.After"/></description></item>
/// <item><description><see cref="IAsyncLifetime.DisposeAsync"/></description></item>
/// <item><description><see cref="IDisposable.Dispose"/></description></item>
/// </list>
/// </remarks>
[IdeSettings(MinVersion = VisualStudioVersion.VS2022, RootSuffix = "RoslynDev")]
public abstract class AbstractIntegrationTest : AbstractIdeIntegrationTest
{
protected const string ProjectName = "TestProj";
protected const string SolutionName = "TestSolution";
public override async Task InitializeAsync()
{
await base.InitializeAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.IO;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public abstract class AbstractRazorEditorTest : AbstractEditorTest
{
internal const string BlazorProjectName = "BlazorProject";
private static readonly string s_pagesDir = Path.Combine("Pages");
private static readonly string s_sharedDir = Path.Combine("Shared");
internal static readonly string CounterRazorFile = Path.Combine(s_pagesDir, "Counter.razor");
internal static readonly string SemanticTokensFile = Path.Combine(s_pagesDir, "SemanticTokens.razor");
internal static readonly string MainLayoutFile = Path.Combine(s_sharedDir, "MainLayout.razor");
internal static readonly string ImportsRazorFile = "_Imports.razor";
protected override string LanguageName => LanguageNames.Razor;
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await TestServices.SolutionExplorer.CreateSolutionAsync("BlazorSolution", HangMitigatingCancellationToken);
await TestServices.SolutionExplorer.AddProjectAsync("BlazorProject", WellKnownProjectTemplates.BlazorProject, groupId: WellKnownProjectTemplates.GroupIdentifiers.Server, templateId: null, LanguageName, HangMitigatingCancellationToken);
await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken);
await TestServices.Workspace.WaitForProjectSystemAsync(HangMitigatingCancellationToken);
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LanguageServer, HangMitigatingCancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Projection;
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
{
internal static class IBufferGraphExtensions
{
public static SnapshotSpan? MapUpOrDownToFirstMatch(this IBufferGraph bufferGraph, SnapshotSpan span, Predicate<ITextSnapshot> match)
{
var spans = bufferGraph.MapDownToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
if (!spans.Any())
{
spans = bufferGraph.MapUpToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
}
return spans.Select(s => (SnapshotSpan?)s).FirstOrDefault();
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
{
internal static class ITextViewExtensions
{
public static SnapshotPoint? GetCaretPoint(this ITextView textView, Predicate<ITextSnapshot> match)
{
var caret = textView.Caret.Position;
var span = textView.BufferGraph.MapUpOrDownToFirstMatch(new SnapshotSpan(caret.BufferPosition, 0), match);
if (span.HasValue)
{
return span.Value.Start;
}
else
{
return null;
}
}
public static ITextBuffer? GetBufferContainingCaret(this ITextView textView, string contentType = StandardContentTypeNames.Text)
{
var point = GetCaretPoint(textView, s => s.ContentType.IsOfType(contentType));
return point.HasValue ? point.Value.Snapshot.TextBuffer : null;
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
{
internal static class IVsTextManagerExtensions
{
public static Task<IVsTextView> GetActiveViewAsync(this IVsTextManager textManager, JoinableTaskFactory joinableTaskFactory, CancellationToken cancellationToken)
=> textManager.GetActiveViewAsync(joinableTaskFactory, mustHaveFocus: true, buffer: null, cancellationToken);
public static async Task<IVsTextView> GetActiveViewAsync(this IVsTextManager textManager, JoinableTaskFactory joinableTaskFactory, bool mustHaveFocus, IVsTextBuffer? buffer, CancellationToken cancellationToken)
{
await joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
ErrorHandler.ThrowOnFailure(textManager.GetActiveView(fMustHaveFocus: mustHaveFocus ? 1 : 0, pBuffer: buffer, ppView: out var vsTextView));
return vsTextView;
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
{
internal static class IVsTextViewExtensions
{
public static async Task<IWpfTextViewHost> GetTextViewHostAsync(this IVsTextView textView, JoinableTaskFactory joinableTaskFactory, CancellationToken cancellationToken)
{
await joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
ErrorHandler.ThrowOnFailure(((IVsUserData)textView).GetData(DefGuidList.guidIWpfTextViewHost, out var wpfTextViewHost));
return (IWpfTextViewHost)wpfTextViewHost;
}
}
}

Просмотреть файл

@ -0,0 +1,93 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Razor.Integration.Test.Extensions;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
public async Task<ITextSnapshot> GetActiveSnapshotAsync(CancellationToken cancellationToken)
=> (await GetActiveTextViewAsync(cancellationToken)).TextSnapshot;
public async Task ActivateAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
dte.ActiveDocument.Activate();
}
public async Task<bool> IsUseSuggestionModeOnAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var textView = await GetActiveTextViewAsync(cancellationToken);
var subjectBuffer = textView.GetBufferContainingCaret();
Assumes.Present(subjectBuffer);
var options = textView.Options.GlobalOptions;
EditorOptionKey<bool> optionKey;
bool defaultOption;
if (IsDebuggerTextView(textView))
{
optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName);
defaultOption = true;
}
else
{
optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInCompletionOptionName);
defaultOption = false;
}
if (!options.IsOptionDefined(optionKey, localScopeOnly: false))
{
return defaultOption;
}
return options.GetOptionValue(optionKey);
static bool IsDebuggerTextView(IWpfTextView textView)
{
return textView.Roles.Contains("DEBUGVIEW");
}
}
public async Task SetUseSuggestionModeAsync(bool value, CancellationToken cancellationToken)
{
if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
{
var dispatcher = await GetRequiredGlobalServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>(cancellationToken);
ErrorHandler.ThrowOnFailure(dispatcher.Exec(typeof(VSConstants.VSStd2KCmdID).GUID, (uint)VSConstants.VSStd2KCmdID.ToggleConsumeFirstCompletionMode, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero));
if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
{
throw new InvalidOperationException($"Edit.ToggleCompletionMode did not leave the editor in the expected state.");
}
}
if (!value)
{
// For blocking completion mode, make sure we don't have responsive completion interfering when
// integration tests run slowly.
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var options = view.Options.GlobalOptions;
options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, false);
var latencyGuardOptionKey = new EditorOptionKey<bool>("EnableTypingLatencyGuard");
options.SetOptionValue(latencyGuardOptionKey, false);
}
}
}
}

Просмотреть файл

@ -0,0 +1,121 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Razor.Integration.Test.Extensions;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
public async Task MoveCaretAsync(int position, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var subjectBuffer = view.GetBufferContainingCaret();
Assumes.Present(subjectBuffer);
var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position);
view.Caret.MoveTo(point);
}
public Task PlaceCaretAsync(string marker, int charsOffset, CancellationToken cancellationToken)
=> PlaceCaretAsync(marker, charsOffset, occurrence: 0, extendSelection: false, selectBlock: false, cancellationToken);
public async Task PlaceCaretAsync(
string marker,
int charsOffset,
int occurrence,
bool extendSelection,
bool selectBlock,
CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
dte.Find.FindWhat = marker;
dte.Find.MatchCase = true;
dte.Find.MatchInHiddenText = true;
dte.Find.Target = EnvDTE.vsFindTarget.vsFindTargetCurrentDocument;
dte.Find.Action = EnvDTE.vsFindAction.vsFindActionFind;
var originalPosition = await GetCaretPositionAsync(cancellationToken);
view.Caret.MoveTo(new SnapshotPoint(view.GetBufferContainingCaret()!.CurrentSnapshot, 0));
if (occurrence > 0)
{
var result = EnvDTE.vsFindResult.vsFindResultNotFound;
for (var i = 0; i < occurrence; i++)
{
result = dte.Find.Execute();
}
if (result != EnvDTE.vsFindResult.vsFindResultFound)
{
throw new Exception("Occurrence " + occurrence + " of marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText());
}
}
else
{
var result = dte.Find.Execute();
if (result != EnvDTE.vsFindResult.vsFindResultFound)
{
throw new Exception("Marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText());
}
}
if (charsOffset > 0)
{
for (var i = 0; i < charsOffset - 1; i++)
{
view.Caret.MoveToNextCaretPosition();
}
view.Selection.Clear();
}
if (charsOffset < 0)
{
// On the first negative charsOffset, move to anchor-point position, as if the user hit the LEFT key
view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, view.Selection.AnchorPoint.Position.Position));
for (var i = 0; i < -charsOffset - 1; i++)
{
view.Caret.MoveToPreviousCaretPosition();
}
view.Selection.Clear();
}
if (extendSelection)
{
var newPosition = view.Selection.ActivePoint.Position.Position;
view.Selection.Select(new VirtualSnapshotPoint(view.TextSnapshot, originalPosition), new VirtualSnapshotPoint(view.TextSnapshot, newPosition));
view.Selection.Mode = selectBlock ? TextSelectionMode.Box : TextSelectionMode.Stream;
}
}
public async Task<int> GetCaretPositionAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var subjectBuffer = view.GetBufferContainingCaret();
Assumes.Present(subjectBuffer);
var bufferPosition = view.Caret.Position.BufferPosition;
return bufferPosition.Position;
}
}
}

Просмотреть файл

@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Xunit;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
/// <summary>
/// Waits for any semantic classifications to be available on the active TextView, and for at least one of the
/// <paramref name="expectedClassification"/> if provided.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
/// <param name="expectedClassification">The classification to wait for, if any.</param>
/// <param name="count">The number of the given classification to expect.</param>
/// <returns>A <see cref="Task"/> which completes when classification is "ready".</returns>
public async Task WaitForClassificationAsync(CancellationToken cancellationToken, string expectedClassification = "RazorComponentElement", int count = 1)
{
var textView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);
var classifier = await GetClassifierAsync(textView, cancellationToken);
using var semaphore = new SemaphoreSlim(1);
await semaphore.WaitAsync(cancellationToken);
classifier.ClassificationChanged += Classifier_ClassificationChanged;
// Check that we're not ALREADY changed
if (HasClassification(classifier, textView, expectedClassification, count))
{
semaphore.Release();
classifier.ClassificationChanged -= Classifier_ClassificationChanged;
return;
}
try
{
await semaphore.WaitAsync(cancellationToken);
}
finally
{
classifier.ClassificationChanged -= Classifier_ClassificationChanged;
}
void Classifier_ClassificationChanged(object sender, ClassificationChangedEventArgs e)
{
var classifications = GetClassifications(classifier, textView);
if (HasClassification(classifier, textView, expectedClassification, count))
{
semaphore.Release();
}
}
static bool HasClassification(IClassifier classifier, ITextView textView, string expectedClassification, int count)
{
var classifications = GetClassifications(classifier, textView);
return classifications.Where(
c => c.ClassificationType.BaseTypes.Any(bT => bT is ILayeredClassificationType layered &&
layered.Layer == ClassificationLayer.Semantic &&
layered.Classification == expectedClassification)).Count() >= count;
}
}
public async Task VerifyGetClassificationsAsync(IEnumerable<ClassificationSpan> expectedClassifications, CancellationToken cancellationToken)
{
var actualClassifications = await GetClassificationsAsync(cancellationToken);
var actualArray = actualClassifications.ToArray();
var expectedArray = expectedClassifications.ToArray();
for (var i = 0; i < actualArray.Length; i++)
{
var actualClassification = actualArray[i];
var expectedClassification = expectedArray[i];
if (actualClassification.ClassificationType.BaseTypes.Count() > 1)
{
Assert.Equal(expectedClassification.Span, actualClassification.Span);
var semanticBaseTypes = actualClassification.ClassificationType.BaseTypes.Where(t => t is ILayeredClassificationType layered && layered.Layer == ClassificationLayer.Semantic);
if (semanticBaseTypes.Count() == 1)
{
Assert.Equal(expectedClassification.ClassificationType.Classification, semanticBaseTypes.First().Classification);
}
else if (semanticBaseTypes.Count() > 1)
{
Assert.Contains(expectedClassification.ClassificationType.Classification, semanticBaseTypes.Select(s => s.Classification));
}
else
{
Assert.True(false, "Did not have semantic basetype");
}
}
else if (!expectedClassification.Span.Span.Equals(actualClassification.Span.Span)
|| !string.Equals(expectedClassification.ClassificationType.Classification, actualClassification.ClassificationType.Classification))
{
Assert.Equal(expectedClassification.Span, actualClassification.Span);
Assert.Equal(expectedClassification.ClassificationType.Classification, actualClassification.ClassificationType.Classification);
Assert.True(false,
$"i: {i}" +
$"expected: {expectedClassification.Span} {expectedClassification.ClassificationType.Classification} " +
$"actual: {actualClassification.Span} {actualClassification.ClassificationType.Classification}");
}
}
Assert.Equal(expectedArray.Length, actualArray.Length);
}
public async Task<IEnumerable<ClassificationSpan>> GetClassificationsAsync(CancellationToken cancellationToken)
{
var textView = await GetActiveTextViewAsync(cancellationToken);
var classifier = await GetClassifierAsync(textView, cancellationToken);
return GetClassifications(classifier, textView);
}
private static IEnumerable<ClassificationSpan> GetClassifications(IClassifier classifier, ITextView textView)
{
var selectionSpan = new SnapshotSpan(textView.TextSnapshot, new Span(0, textView.TextSnapshot.Length));
var classifiedSpans = classifier.GetClassificationSpans(selectionSpan);
return classifiedSpans;
}
private async Task<IClassifier> GetClassifierAsync(IWpfTextView textView, CancellationToken cancellationToken)
{
var classifierService = await GetComponentModelServiceAsync<IViewClassifierAggregatorService>(cancellationToken);
return classifierService.GetClassifier(textView);
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
public async Task DismissCompletionSessionsAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var asyncBroker = await GetComponentModelServiceAsync<IAsyncCompletionBroker>(cancellationToken);
var session = asyncBroker.GetSession(view);
if (session is not null && !session.IsDismissed)
{
session.Dismiss();
}
}
}
}

Просмотреть файл

@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
public async Task DismissLightBulbSessionAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
broker.DismissSession(view);
}
public async Task<IEnumerable<SuggestedActionSet>> InvokeCodeActionListAsync(CancellationToken cancellationToken)
{
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.SolutionCrawler, cancellationToken);
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.DiagnosticService, cancellationToken);
var lightbulbs = await ShowLightBulbAsync(cancellationToken);
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LightBulb, cancellationToken);
return lightbulbs;
}
public async Task InvokeCodeActionAsync(ISuggestedAction codeAction, CancellationToken cancellationToken)
{
var view = await GetActiveTextViewAsync(cancellationToken);
codeAction.Invoke(cancellationToken);
// ISuggestedAction.Invoke does not dismiss the session, so we must do it manually
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
broker.DismissSession(view);
}
public async Task<bool> IsLightBulbSessionExpandedAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var view = await GetActiveTextViewAsync(cancellationToken);
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
if (!broker.IsLightBulbSessionActive(view))
{
return false;
}
var session = broker.GetSession(view);
if (session is null || !session.IsExpanded)
{
return false;
}
return true;
}
private async Task<IEnumerable<SuggestedActionSet>> ShowLightBulbAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var shell = await GetRequiredGlobalServiceAsync<SVsUIShell, IVsUIShell>(cancellationToken);
var cmdGroup = typeof(VSConstants.VSStd14CmdID).GUID;
var cmdExecOpt = OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
var cmdID = VSConstants.VSStd14CmdID.ShowQuickFixes;
object? obj = null;
shell.PostExecCommand(cmdGroup, (uint)cmdID, (uint)cmdExecOpt, ref obj);
var view = await GetActiveTextViewAsync(cancellationToken);
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
await LightBulbHelper.WaitForLightBulbSessionAsync(broker, view, cancellationToken);
return await LightBulbHelper.WaitForItemsAsync(broker, view, cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
using Microsoft.VisualStudio.Text;
using Xunit;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class EditorInProcess
{
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);
}
public async Task VerifyTextContainsAsync(string text, CancellationToken cancellationToken)
{
var view = await GetActiveTextViewAsync(cancellationToken);
var content = view.TextBuffer.CurrentSnapshot.GetText();
Assert.Contains(text, content);
}
private async Task WaitForProjectReadyAsync(CancellationToken cancellationToken)
{
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LanguageServer, cancellationToken);
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.Workspace, cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
{
internal static class FeatureAttribute
{
public const string AutomaticEndConstructCorrection = nameof(AutomaticEndConstructCorrection);
public const string AutomaticPairCompletion = nameof(AutomaticPairCompletion);
public const string BraceHighlighting = nameof(BraceHighlighting);
public const string CallHierarchy = nameof(CallHierarchy);
public const string Classification = nameof(Classification);
public const string CodeModel = nameof(CodeModel);
public const string CodeDefinitionWindow = nameof(CodeDefinitionWindow);
public const string CompletionSet = nameof(CompletionSet);
public const string DesignerAttributes = nameof(DesignerAttributes);
public const string DiagnosticService = nameof(DiagnosticService);
public const string EncapsulateField = nameof(EncapsulateField);
public const string ErrorList = nameof(ErrorList);
public const string ErrorSquiggles = nameof(ErrorSquiggles);
public const string EventHookup = nameof(EventHookup);
public const string FindReferences = nameof(FindReferences);
public const string GlobalOperation = nameof(GlobalOperation);
public const string GoToImplementation = nameof(GoToImplementation);
public const string GraphProvider = nameof(GraphProvider);
public const string InfoBar = nameof(InfoBar);
public const string InlineDiagnostics = nameof(InlineDiagnostics);
public const string InheritanceMargin = nameof(InheritanceMargin);
public const string InlineHints = nameof(InlineHints);
public const string InteractiveEvaluator = nameof(InteractiveEvaluator);
public const string KeywordHighlighting = nameof(KeywordHighlighting);
public const string LibraryManager = nameof(LibraryManager);
public const string LightBulb = nameof(LightBulb);
public const string LineSeparators = nameof(LineSeparators);
public const string NavigableSymbols = nameof(NavigableSymbols);
public const string NavigateTo = nameof(NavigateTo);
public const string NavigationBar = nameof(NavigationBar);
public const string Outlining = nameof(Outlining);
public const string PackageInstaller = nameof(PackageInstaller);
public const string PersistentStorage = nameof(PersistentStorage);
public const string QuickInfo = nameof(QuickInfo);
public const string ReferenceHighlighting = nameof(ReferenceHighlighting);
public const string Rename = nameof(Rename);
public const string RenameTracking = nameof(RenameTracking);
public const string SolutionChecksumUpdater = nameof(SolutionChecksumUpdater);
public const string SourceGenerators = nameof(SourceGenerators);
public const string RuleSetEditor = nameof(RuleSetEditor);
public const string SignatureHelp = nameof(SignatureHelp);
public const string Snippets = nameof(Snippets);
public const string SolutionCrawler = nameof(SolutionCrawler);
public const string Telemetry = nameof(Telemetry);
public const string TodoCommentList = nameof(TodoCommentList);
public const string LanguageServer = nameof(LanguageServer);
public const string Workspace = nameof(Workspace);
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
{
internal static class Helper
{
/// <summary>
/// This method will retry the asynchronous action represented by <paramref name="action"/>,
/// waiting for <paramref name="delay"/> time after each retry. If a given retry returns a value
/// other than the default value of <typeparamref name="T"/>, this value is returned.
/// </summary>
/// <param name="action">the asynchronous action to retry</param>
/// <param name="delay">the amount of time to wait between retries</param>
/// <param name="cancellationToken"></param>
/// <typeparam name="T">type of return value</typeparam>
/// <returns>the return value of <paramref name="action"/></returns>
public static Task<T?> RetryAsync<T>(Func<CancellationToken, Task<T>> action, TimeSpan delay, CancellationToken cancellationToken)
{
return RetryCoreAsync(
async cancellationToken =>
{
try
{
return await action(cancellationToken);
}
catch (COMException)
{
// Devenv can throw COMExceptions if it's busy when we make DTE calls.
return default;
}
},
delay,
cancellationToken);
}
private static async Task<T> RetryCoreAsync<T>(Func<CancellationToken, Task<T>> action, TimeSpan delay, CancellationToken cancellationToken)
{
while (true)
{
var retval = await action(cancellationToken);
if (!Equals(default(T), retval))
{
return retval;
}
await Task.Delay(delay, cancellationToken);
}
}
}
}

Просмотреть файл

@ -0,0 +1,93 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
{
public static class LightBulbHelper
{
public static async Task<bool> WaitForLightBulbSessionAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken)
{
var startTime = DateTimeOffset.Now;
var active = await Helper.RetryAsync(async cancellationToken =>
{
if (broker.IsLightBulbSessionActive(view))
{
return true;
}
if (cancellationToken.IsCancellationRequested)
{
throw new InvalidOperationException("Expected a light bulb session to appear.");
}
if (broker.IsLightBulbSessionActive(view))
{
var session = broker.GetSession(view);
var hasSuggestedActions = await broker.HasSuggestedActionsAsync(session.ActionCategories, view, cancellationToken);
return hasSuggestedActions;
}
return false;
}, TimeSpan.FromMilliseconds(1), cancellationToken);
if (!active)
return false;
await WaitForItemsAsync(broker, view, cancellationToken);
return true;
}
public static async Task<IEnumerable<SuggestedActionSet>> WaitForItemsAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken)
{
var activeSession = broker.GetSession(view);
if (activeSession is null)
{
var bufferType = view.TextBuffer.ContentType.DisplayName;
throw new InvalidOperationException($"No expanded light bulb session found after View.ShowSmartTag. Buffer content type={bufferType}");
}
var asyncSession = (IAsyncLightBulbSession)activeSession;
var tcs = new TaskCompletionSource<List<SuggestedActionSet>>();
void Handler(object s, SuggestedActionsUpdatedArgs e)
{
// ignore these. we care about when the lightbulb items are all completed.
if (e.Status == QuerySuggestedActionCompletionStatus.InProgress)
return;
if (e.Status == QuerySuggestedActionCompletionStatus.Completed || e.Status == QuerySuggestedActionCompletionStatus.CompletedWithoutData)
tcs.SetResult(e.ActionSets.ToList());
else
tcs.SetException(new InvalidOperationException($"Light bulb transitioned to non-complete state: {e.Status}"));
asyncSession.SuggestedActionsUpdated -= Handler;
}
asyncSession.SuggestedActionsUpdated += Handler;
asyncSession.Dismissed += (_, _) => tcs.TrySetCanceled(new CancellationToken(true));
if (asyncSession.IsDismissed)
tcs.TrySetCanceled(new CancellationToken(true));
// Calling PopulateWithData ensures the underlying session will call SuggestedActionsUpdated at least once
// with the latest data computed. This is needed so that if the lightbulb computation is already complete
// that we hear about the results.
asyncSession.PopulateWithData(overrideRequestedActionCategories: null, operationContext: null);
return await tcs.Task.WithCancellation(cancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
{
internal static class MarkupTestFile
{
internal static void GetPosition(string markupCode, out string code, out int caretPosition)
{
caretPosition = markupCode.IndexOf("$$");
code = markupCode.Replace("$$", "");
}
}
}

Просмотреть файл

@ -0,0 +1,252 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license 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.Razor.Integration.Test;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using NuGet.SolutionRestoreManager;
using Xunit;
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);
}
public Task AddProjectAsync(string projectName, string projectTemplate, string languageName, CancellationToken cancellationToken)
=> AddProjectAsync(projectName, projectTemplate, groupId: null, templateId: null, languageName, cancellationToken);
public async Task AddProjectAsync(string projectName, string projectTemplate, string? groupId, string? templateId, string languageName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName);
var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, ConvertLanguageName(languageName), cancellationToken);
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution6>(cancellationToken);
var args = new List<object>();
if (groupId is not null)
args.Add($"$groupid$={groupId}");
if (groupId is not null)
args.Add($"$templateid$={templateId}");
ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, args.Any() ? args.ToArray() : null, null, projectPath, projectName, null, out _));
}
public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
var solution = (EnvDTE80.Solution2)dte.Solution;
foreach (var project in solution.Projects.OfType<EnvDTE.Project>())
{
await RestoreNuGetPackagesAsync(project.FullName, cancellationToken);
}
}
public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
await TestServices.Workspace.WaitForProjectSystemAsync(cancellationToken);
var solutionRestoreService = await GetComponentModelServiceAsync<IVsSolutionRestoreService>(cancellationToken);
await solutionRestoreService.CurrentRestoreOperation;
var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName;
var solutionRestoreStatusProvider = await GetComponentModelServiceAsync<IVsSolutionRestoreStatusProvider>(cancellationToken);
if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken))
{
return;
}
var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService;
await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken);
// Check IsRestoreCompleteAsync until it returns true (this stops the retry because true != default(bool))
await Helper.RetryAsync(
cancellationToken => solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken),
TimeSpan.FromMilliseconds(50),
cancellationToken);
}
public async Task OpenFileAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}
VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, filePath, VSConstants.LOGVIEWID.Code_guid, out _, out _, out _, out var view);
// Reliably set focus using NavigateToLineAndColumn
var textManager = await GetRequiredGlobalServiceAsync<SVsTextManager, IVsTextManager>(cancellationToken);
ErrorHandler.ThrowOnFailure(view.GetBuffer(out var textLines));
ErrorHandler.ThrowOnFailure(view.GetCaretPos(out var line, out var column));
ErrorHandler.ThrowOnFailure(textManager.NavigateToLineAndColumn(textLines, VSConstants.LOGVIEWID.Code_guid, line, column, line, column));
}
/// <summary>
/// Add new file to project.
/// </summary>
/// <param name="projectName">The project that contains the file.</param>
/// <param name="fileName">The name of the file to add.</param>
/// <param name="contents">The contents of the file to overwrite. An empty file is create if null is passed.</param>
/// <param name="open">Whether to open the file after it has been updated.</param>
/// <param name="cancellationToken"></param>
public async Task AddFileAsync(string projectName, string fileName, string? contents = null, bool open = false, CancellationToken cancellationToken = default)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var project = await GetProjectAsync(projectName, cancellationToken);
var projectDirectory = Path.GetDirectoryName(project.FullName);
var filePath = Path.Combine(projectDirectory, fileName);
var directoryPath = Path.GetDirectoryName(filePath);
Directory.CreateDirectory(directoryPath);
if (contents is not null)
{
File.WriteAllText(filePath, contents);
}
else if (!File.Exists(filePath))
{
File.Create(filePath).Dispose();
}
_ = project.ProjectItems.AddFromFile(filePath);
if (open)
{
await OpenFileAsync(projectName, fileName, 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);
// Make sure the shell debugger package is loaded so it doesn't try to load during the synchronous portion
// of IVsSolution.CreateSolution.
//
// TODO: Identify the correct tracking bug
_ = await GetRequiredGlobalServiceAsync<SVsShellDebugger, IVsDebugger>(cancellationToken);
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution>(cancellationToken);
ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT));
ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0));
}
private async Task<string> GetProjectTemplatePathAsync(string projectTemplate, string languageName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
var solution = (EnvDTE80.Solution2)dte.Solution;
if (string.Equals(languageName, "csharp", StringComparison.OrdinalIgnoreCase)
&& GetCSharpProjectTemplates().TryGetValue(projectTemplate, out var csharpProjectTemplate))
{
return solution.GetProjectTemplate(csharpProjectTemplate, languageName);
}
throw new NotImplementedException();
static ImmutableDictionary<string, string> GetCSharpProjectTemplates()
{
var builder = ImmutableDictionary.CreateBuilder<string, string>();
builder[WellKnownProjectTemplates.BlazorProject] = "BlazorTemplate";
return builder.ToImmutable();
}
}
private static string ConvertLanguageName(string languageName)
{
return languageName switch
{
LanguageNames.CSharp => "CSharp",
LanguageNames.Razor => "CSharp",
_ => throw new ArgumentException($"'{languageName}' is not supported.", nameof(languageName)),
};
}
private async Task<string> GetAbsolutePathForProjectRelativeFilePathAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
var solution = dte.Solution;
Assumes.Present(solution);
var project = solution.Projects.Cast<EnvDTE.Project>().FirstOrDefault(x => x.Name == projectName);
if (project is null)
{
Assert.True(false, $"{projectName} doesn't exist, had {string.Join(",", solution.Projects.Cast<EnvDTE.Project>().Select(p => p.Name))}");
throw new NotImplementedException("Prevent null fallthrough");
}
Assert.NotNull(project);
var projectPath = Path.GetDirectoryName(project.FullName);
return Path.Combine(projectPath, relativeFilePath);
}
private async Task<string> GetDirectoryNameAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution>(cancellationToken);
ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _));
if (string.IsNullOrEmpty(solutionFileFullPath))
{
throw new InvalidOperationException();
}
return Path.GetDirectoryName(solutionFileFullPath);
}
private static string CreateTemporaryPath()
{
return Path.Combine(Path.GetTempPath(), "razor-test", Path.GetRandomFileName());
}
private async Task<EnvDTE.Project> GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
var solution = (EnvDTE80.Solution2)dte.Solution;
return solution.Projects.OfType<EnvDTE.Project>().First(
project =>
{
ThreadHelper.ThrowIfNotOnUIThread();
return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase)
|| string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase);
});
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
{
public static class WellKnownCommandNames
{
public const string Build_BuildSolution = "Build.BuildSolution";
public const string View_ErrorList = "View.ErrorList";
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class WorkspaceInProcess
{
public Task WaitForAsyncOperationsAsync(string featuresToWaitFor, CancellationToken cancellationToken)
=> WaitForAsyncOperationsAsync(featuresToWaitFor, waitForWorkspaceFirst: true, cancellationToken);
public async Task WaitForAsyncOperationsAsync(string featuresToWaitFor, bool waitForWorkspaceFirst, CancellationToken cancellationToken)
{
if (waitForWorkspaceFirst || featuresToWaitFor == FeatureAttribute.Workspace)
{
await WaitForProjectSystemAsync(cancellationToken);
}
// TODO: This currently no-ops on the FeaturesToWaitFor portion
// because we lack any system to wait on it with
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Reflection;
using Xunit.Sdk;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public class IntializeTestFileAttribute : BeforeAfterTestAttribute
{
public override void Before(MethodInfo methodUnderTest)
{
var typeName = methodUnderTest.ReflectedType.Name;
if (typeof(RazorSemanticTokensTests).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
{
RazorSemanticTokensTests.FileName = $"Semantic/TestFiles/{typeName}/{methodUnderTest.Name}";
}
}
public override void After(MethodInfo methodUnderTest)
{
if (typeof(RazorSemanticTokensTests).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
{
RazorSemanticTokensTests.FileName = null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public static class LanguageNames
{
public const string Razor = "Razor";
public const string CSharp = "CSharp";
}
}

Просмотреть файл

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.VisualStudio.Razor.Integration.Test</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.AspNetCore.Razor.Test.Common\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup>
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.RazorExtension\Microsoft.VisualStudio.RazorExtension.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj" />
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(MicrosoftVisualStudioEditorPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.Xunit" Version="$(MicrosoftVisualStudioExtensibilityTestingXunitVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator" Version="$(MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Language.Intellisense" Version="$(MicrosoftVisualStudioLanguageIntellisensePackageVersion)" />
<PackageReference Include="NuGet.SolutionRestoreManager.Interop" Version="$(NuGetSolutionRestoreManagerInteropVersion)" />
<PackageReference Include="Microsoft.Internal.VisualStudio.Shell.Framework" Version="$(MicrosoftInternalVisualStudioShellFrameworkPackageVersion)" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Semantic\TestFiles\**\*" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public class ProjectTests : AbstractRazorEditorTest
{
[IdeFact]
public async Task CreateFromTemplateAsync()
{
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
await TestServices.SolutionExplorer.CloseSolutionAsync(HangMitigatingCancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public class RazorCodeActionsTests : AbstractRazorEditorTest
{
[IdeFact(Skip = "Behavior not yet testable")]
public async Task RazorCodeActions_Show()
{
// Create Warnings by removing usings
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, ImportsRazorFile, HangMitigatingCancellationToken);
await TestServices.Editor.SetTextAsync("", HangMitigatingCancellationToken);
// Open the file
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
await TestServices.Editor.SetTextAsync("<SurveyPrompt></SurveyPrompt>", HangMitigatingCancellationToken);
await TestServices.Editor.MoveCaretAsync(3, HangMitigatingCancellationToken);
// Act
var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(HangMitigatingCancellationToken);
// Assert
var codeActionSet = Assert.Single(codeActions);
var usingString = $"@using {BlazorProjectName}.Shared";
var codeAction = Assert.Single(codeActionSet.Actions, a => a.DisplayText.Equals(usingString));
await TestServices.Editor.InvokeCodeActionAsync(codeAction, HangMitigatingCancellationToken);
await TestServices.Editor.VerifyTextContainsAsync(usingString, HangMitigatingCancellationToken);
}
}
}

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
internal static class RazorDebug
{
/// <inheritdoc cref="Debug.Assert(bool)"/>
[Conditional("DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool b) => Debug.Assert(b);
/// <inheritdoc cref="Debug.Assert(bool, string)"/>
[Conditional("DEBUG")]
public static void Assert([DoesNotReturnIf(false)] bool b, string message)
=> Debug.Assert(b, message);
[Conditional("DEBUG")]
public static void AssertNotNull<T>([NotNull] T value)
{
Assert(value is object, "Unexpected null reference");
}
/// <summary>
/// Generally <see cref="Debug.Assert(bool)"/> is a sufficient method for enforcing DEBUG
/// only invariants in our code. When it triggers that provides a nice stack trace for
/// investigation. Generally that is enough.
///
/// <para>There are cases for which a stack is not enough and we need a full heap dump to
/// investigate the failure. This method takes care of that. The behavior is that when running
/// in our CI environment if the assert triggers we will rudely crash the process and
/// produce a heap dump for investigation.</para>
/// </summary>
[Conditional("DEBUG")]
internal static void AssertOrFailFast([DoesNotReturnIf(false)] bool condition, string? message = null)
{
if (!condition)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER")))
{
message ??= $"{nameof(AssertOrFailFast)} failed";
var stackTrace = new StackTrace();
Console.WriteLine(message);
Console.WriteLine(stackTrace);
// Use FailFast so that the process fails rudely and goes through
// windows error reporting (on Windows at least). This will allow our
// Helix environment to capture crash dumps for future investigation
Environment.FailFast(message);
}
else
{
Debug.Assert(false, message);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,242 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Xunit;
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
[IntializeTestFile]
public class RazorSemanticTokensTests : AbstractRazorEditorTest
{
private static readonly AsyncLocal<string?> s_fileName = new();
private static readonly string s_projectPath = TestProject.GetProjectDirectory(typeof(RazorSemanticTokensTests), useCurrentDirectory: true);
protected bool GenerateBaselines { get; set; } = false;
// Used by the test framework to set the 'base' name for test files.
public static string? FileName
{
get { return s_fileName.Value; }
set { s_fileName.Value = value; }
}
public override async Task InitializeAsync()
{
await base.InitializeAsync();
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.Classification, HangMitigatingCancellationToken);
}
[IdeFact]
public async Task Components_AreColored()
{
// Arrange
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, MainLayoutFile, HangMitigatingCancellationToken);
// Act
await TestServices.Editor.WaitForClassificationAsync(HangMitigatingCancellationToken, "RazorComponentElement", 3);
// Assert
var expectedClassifications = await GetExpectedClassificationSpansAsync(nameof(Components_AreColored), HangMitigatingCancellationToken);
await TestServices.Editor.VerifyGetClassificationsAsync(expectedClassifications, HangMitigatingCancellationToken);
}
[IdeFact]
public async Task Directives_AreColored()
{
// Arrange
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
await TestServices.Editor.WaitForClassificationAsync(HangMitigatingCancellationToken);
// Act and Assert
var expectedClassifications = await GetExpectedClassificationSpansAsync(nameof(Directives_AreColored), HangMitigatingCancellationToken);
await TestServices.Editor.VerifyGetClassificationsAsync(expectedClassifications, HangMitigatingCancellationToken);
}
private async Task<IEnumerable<ClassificationSpan>> GetExpectedClassificationSpansAsync(string testName, CancellationToken cancellationToken)
{
var snapshot = await TestServices.Editor.GetActiveSnapshotAsync(HangMitigatingCancellationToken);
if (GenerateBaselines)
{
var actual = await TestServices.Editor.GetClassificationsAsync(cancellationToken);
GenerateSemanticBaseline(actual, testName);
}
var expectedClassifications = await ReadSemanticBaselineAsync(snapshot, cancellationToken);
return expectedClassifications;
}
private async Task<IEnumerable<ClassificationSpan>> ReadSemanticBaselineAsync(ITextSnapshot snapshot, CancellationToken cancellationToken)
{
var baselinePath = Path.ChangeExtension(FileName, ".txt");
var assembly = GetType().GetTypeInfo().Assembly;
var semanticFile = TestFile.Create(baselinePath, assembly);
var semanticStr = await semanticFile.ReadAllTextAsync(cancellationToken);
return ParseSemanticBaseline(semanticStr, snapshot);
static IEnumerable<ClassificationSpan> ParseSemanticBaseline(string semanticStr, ITextSnapshot snapshot)
{
var result = new List<ClassificationSpan>();
var strArray = semanticStr.Split(new[] { Separator.ToString(), Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < strArray.Length; i += 3)
{
if (!int.TryParse(strArray[i], out var position))
{
throw new InvalidOperationException($"{strArray[i]} was not an int {i}");
}
if (!int.TryParse(strArray[i + 1], out var length))
{
throw new InvalidOperationException($"{strArray[i + 1]} was not an int {i}");
}
var snapshotSpan = new SnapshotSpan(snapshot, position, length);
var classification = strArray[i + 2];
var classificationType = new ClassificationType(classification);
result.Add(new ClassificationSpan(snapshotSpan, classificationType));
}
return result;
}
}
private const char Separator = ',';
private static void GenerateSemanticBaseline(IEnumerable<ClassificationSpan> actual, string baselineFileName)
{
var builder = new StringBuilder();
foreach (var baseline in actual)
{
builder.Append(baseline.Span.Start.Position).Append(Separator);
builder.Append(baseline.Span.Length).Append(Separator);
var classification = baseline.ClassificationType;
string? classificationStr = null;
if (classification.BaseTypes.Count() > 1)
{
foreach (ILayeredClassificationType baseType in classification.BaseTypes)
{
if (baseType.Layer == ClassificationLayer.Semantic)
{
classificationStr = baseType.Classification;
break;
}
}
if (classificationStr is null)
{
Assert.True(false, "Tried to write layered classifications without Semantic layer");
throw new Exception();
}
}
else
{
classificationStr = classification.Classification;
}
builder.Append(classificationStr).Append(Separator);
builder.AppendLine();
}
var semanticBaselinePath = GetBaselineFileName(baselineFileName);
File.WriteAllText(semanticBaselinePath, builder.ToString());
}
private static string GetBaselineFileName(string testName)
{
var semanticBaselinePath = Path.Combine(s_projectPath, "Semantic", "TestFiles", nameof(RazorSemanticTokensTests), testName + ".txt");
return semanticBaselinePath;
}
private class ClassificationComparer : IEqualityComparer<ClassificationSpan>
{
public static ClassificationComparer Instance = new();
public bool Equals(ClassificationSpan x, ClassificationSpan y)
{
var spanEquals = x.Span.Equals(y.Span);
var classificationEquals = ClassificationTypeComparer.Instance.Equals(x.ClassificationType, y.ClassificationType);
return classificationEquals && spanEquals;
}
public int GetHashCode(ClassificationSpan obj)
{
throw new NotImplementedException();
}
}
private class ClassificationTypeComparer : IEqualityComparer<IClassificationType>
{
public static ClassificationTypeComparer Instance = new();
public bool Equals(IClassificationType x, IClassificationType y)
{
string xString;
string yString;
if (x is ILayeredClassificationType xLayered)
{
var baseType = xLayered.BaseTypes.Single(b => b is ILayeredClassificationType bLayered && bLayered.Layer == ClassificationLayer.Semantic);
xString = baseType.Classification;
}
else
{
xString = x.Classification;
}
if (y is ILayeredClassificationType yLayered)
{
var baseType = yLayered.BaseTypes.Single(b => b is ILayeredClassificationType bLayered && bLayered.Layer == ClassificationLayer.Semantic);
yString = baseType.Classification;
}
else
{
yString = y.Classification;
}
return xString.Equals(yString);
}
public int GetHashCode(IClassificationType obj)
{
throw new NotImplementedException();
}
}
private class ClassificationType : IClassificationType
{
public ClassificationType(string classification)
{
Classification = classification;
}
public string Classification { get; }
public IEnumerable<IClassificationType> BaseTypes => throw new System.NotImplementedException();
public bool IsOfType(string type)
{
throw new System.NotImplementedException();
}
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
0,1,RazorDirective,
1,8,RazorDirective,
10,19,class name,
33,1,HTML Tag Delimiter,
34,9,RazorComponentElement,
43,1,HTML Tag Delimiter,
57,1,HTML Tag Delimiter,
58,1,HTML Tag Delimiter,
59,9,RazorComponentElement,
68,1,HTML Tag Delimiter,
73,1,HTML Tag Delimiter,
74,3,HTML Element Name,
78,5,HTML Attribute Name,
83,1,HTML Operator,
84,1,HTML Attribute Value,
85,4,HTML Attribute Value,
89,1,HTML Attribute Value,
90,1,HTML Tag Delimiter,
97,1,HTML Tag Delimiter,
98,3,HTML Element Name,
102,5,HTML Attribute Name,
107,1,HTML Operator,
108,1,HTML Attribute Value,
109,7,HTML Attribute Value,
116,1,HTML Attribute Value,
117,1,HTML Tag Delimiter,
128,1,HTML Tag Delimiter,
129,7,RazorComponentElement,
136,1,HTML Tag Delimiter,
137,1,HTML Tag Delimiter,
138,1,HTML Tag Delimiter,
145,1,HTML Tag Delimiter,
146,1,HTML Tag Delimiter,
147,3,HTML Element Name,
150,1,HTML Tag Delimiter,
159,1,HTML Tag Delimiter,
160,4,HTML Element Name,
164,1,HTML Tag Delimiter,
175,1,HTML Tag Delimiter,
176,3,HTML Element Name,
180,5,HTML Attribute Name,
185,1,HTML Operator,
186,1,HTML Attribute Value,
187,7,HTML Attribute Value,
194,5,HTML Attribute Value,
199,1,HTML Attribute Value,
200,1,HTML Tag Delimiter,
215,1,HTML Tag Delimiter,
216,1,HTML Element Name,
218,4,HTML Attribute Name,
222,1,HTML Operator,
223,1,HTML Attribute Value,
224,34,HTML Attribute Value,
258,1,HTML Attribute Value,
260,6,HTML Attribute Name,
266,1,HTML Operator,
267,1,HTML Attribute Value,
268,6,HTML Attribute Value,
274,1,HTML Attribute Value,
275,1,HTML Tag Delimiter,
281,1,HTML Tag Delimiter,
282,1,HTML Tag Delimiter,
283,1,HTML Element Name,
284,1,HTML Tag Delimiter,
295,1,HTML Tag Delimiter,
296,1,HTML Tag Delimiter,
297,3,HTML Element Name,
300,1,HTML Tag Delimiter,
313,1,HTML Tag Delimiter,
314,7,HTML Element Name,
322,5,HTML Attribute Name,
327,1,HTML Operator,
328,1,HTML Attribute Value,
329,7,HTML Attribute Value,
336,5,HTML Attribute Value,
341,1,HTML Attribute Value,
342,1,HTML Tag Delimiter,
357,1,RazorDirective,
358,4,property name,
372,1,HTML Tag Delimiter,
373,1,HTML Tag Delimiter,
374,7,HTML Element Name,
381,1,HTML Tag Delimiter,
388,1,HTML Tag Delimiter,
389,1,HTML Tag Delimiter,
390,4,HTML Element Name,
394,1,HTML Tag Delimiter,
397,1,HTML Tag Delimiter,
398,1,HTML Tag Delimiter,
399,3,HTML Element Name,
402,1,HTML Tag Delimiter,

Просмотреть файл

@ -0,0 +1,70 @@
0,1,RazorDirective,
1,4,RazorDirective,
6,10,string,
20,1,HTML Tag Delimiter,
21,9,RazorComponentElement,
30,1,HTML Tag Delimiter,
38,1,HTML Tag Delimiter,
39,1,HTML Tag Delimiter,
40,9,RazorComponentElement,
49,1,HTML Tag Delimiter,
54,1,HTML Tag Delimiter,
55,2,HTML Element Name,
57,1,HTML Tag Delimiter,
65,1,HTML Tag Delimiter,
66,1,HTML Tag Delimiter,
67,2,HTML Element Name,
69,1,HTML Tag Delimiter,
74,1,HTML Tag Delimiter,
75,1,HTML Element Name,
77,4,HTML Attribute Name,
81,1,HTML Operator,
82,1,HTML Attribute Value,
83,6,HTML Attribute Value,
89,1,HTML Attribute Value,
90,1,HTML Tag Delimiter,
106,1,RazorDirective,
107,12,field name,
119,1,HTML Tag Delimiter,
120,1,HTML Tag Delimiter,
121,1,HTML Element Name,
122,1,HTML Tag Delimiter,
127,1,HTML Tag Delimiter,
128,6,HTML Element Name,
135,5,HTML Attribute Name,
140,1,HTML Operator,
141,1,HTML Attribute Value,
142,3,HTML Attribute Value,
145,12,HTML Attribute Value,
157,1,HTML Attribute Value,
159,1,RazorDirective,
160,7,RazorDirectiveAttribute,
167,1,HTML Operator,
168,1,HTML Attribute Value,
169,14,method name,
183,1,HTML Attribute Value,
184,1,HTML Tag Delimiter,
193,1,HTML Tag Delimiter,
194,1,HTML Tag Delimiter,
195,6,HTML Element Name,
201,1,HTML Tag Delimiter,
206,1,RazorDirective,
207,4,RazorDirective,
212,1,RazorDirective,
219,7,keyword,
227,3,keyword,
231,12,field name,
244,1,operator,
246,1,literal,
247,1,punctuation,
256,7,keyword,
264,4,keyword,
269,14,method name,
283,1,punctuation,
284,1,punctuation,
291,1,punctuation,
302,12,field name,
314,2,operator,
316,1,punctuation,
323,1,punctuation,
326,1,RazorDirective,

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.Razor.Integration.Test
{
public static class WellKnownProjectTemplates
{
public const string BlazorProject = "Microsoft.WAP.CSharp.ASPNET.Blazor";
public static class GroupIdentifiers
{
public const string Server = "Microsoft.Web.Blazor.Server";
public const string Wasm = "Microsoft.Web.Blazor.Wasm";
}
public static class TemplateIdentifiers
{
public const string Server31 = "Microsoft.Web.Blazor.Server.CSharp.3.1";
public const string Server50 = "Microsoft.Web.Blazor.Server.CSharp.5.0";
public const string Server60 = "Microsoft.Web.Blazor.Server.CSharp.6.0";
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]

Просмотреть файл

@ -0,0 +1,4 @@
{
"methodDisplay": "method",
"shadowCopy": false
}

Просмотреть файл

@ -2,4 +2,4 @@
<p>Words!</p>
<Counter />
<NavMenu />