|
// 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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Implementation.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.UnitTests.Squiggles;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Preview
{
[UseExportProvider]
[Trait(Traits.Editor, Traits.Editors.Preview), Trait(Traits.Feature, Traits.Features.Tagging)]
public class PreviewWorkspaceTests
{
[Fact]
public void TestPreviewCreationDefault()
{
using var previewWorkspace = new PreviewWorkspace();
Assert.NotNull(previewWorkspace.CurrentSolution);
}
[Fact]
public void TestPreviewCreationWithExplicitHostServices()
{
var hostServices = FeaturesTestCompositions.Features.GetHostServices();
using var previewWorkspace = new PreviewWorkspace(hostServices);
Assert.NotNull(previewWorkspace.CurrentSolution);
}
[Fact]
public void TestPreviewCreationWithSolution()
{
using var custom = new AdhocWorkspace();
using var previewWorkspace = new PreviewWorkspace(custom.CurrentSolution);
Assert.NotNull(previewWorkspace.CurrentSolution);
}
[Fact]
public void TestPreviewAddRemoveProject()
{
using var previewWorkspace = new PreviewWorkspace();
var solution = previewWorkspace.CurrentSolution;
var project = solution.AddProject("project", "project.dll", LanguageNames.CSharp);
Assert.True(previewWorkspace.TryApplyChanges(project.Solution));
var newSolution = previewWorkspace.CurrentSolution.RemoveProject(project.Id);
Assert.True(previewWorkspace.TryApplyChanges(newSolution));
Assert.Equal(0, previewWorkspace.CurrentSolution.ProjectIds.Count);
}
[Fact]
public void TestPreviewProjectChanges()
{
using var previewWorkspace = new PreviewWorkspace();
var solution = previewWorkspace.CurrentSolution;
var project = solution.AddProject("project", "project.dll", LanguageNames.CSharp);
Assert.True(previewWorkspace.TryApplyChanges(project.Solution));
var addedSolution = previewWorkspace.CurrentSolution.Projects.First()
.AddMetadataReference(TestMetadata.Net451.mscorlib)
.AddDocument("document", "").Project.Solution;
Assert.True(previewWorkspace.TryApplyChanges(addedSolution));
Assert.Equal(1, previewWorkspace.CurrentSolution.Projects.First().MetadataReferences.Count);
Assert.Equal(1, previewWorkspace.CurrentSolution.Projects.First().DocumentIds.Count);
var text = "class C {}";
var changedSolution = previewWorkspace.CurrentSolution.Projects.First().Documents.First().WithText(SourceText.From(text)).Project.Solution;
Assert.True(previewWorkspace.TryApplyChanges(changedSolution));
Assert.Equal(previewWorkspace.CurrentSolution.Projects.First().Documents.First().GetTextAsync().Result.ToString(), text);
var removedSolution = previewWorkspace.CurrentSolution.Projects.First()
.RemoveMetadataReference(previewWorkspace.CurrentSolution.Projects.First().MetadataReferences[0])
.RemoveDocument(previewWorkspace.CurrentSolution.Projects.First().DocumentIds[0]).Solution;
Assert.True(previewWorkspace.TryApplyChanges(removedSolution));
Assert.Equal(0, previewWorkspace.CurrentSolution.Projects.First().MetadataReferences.Count);
Assert.Equal(0, previewWorkspace.CurrentSolution.Projects.First().DocumentIds.Count);
}
[WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/923121")]
[WpfFact]
public void TestPreviewOpenCloseFile()
{
using var previewWorkspace = new PreviewWorkspace();
var solution = previewWorkspace.CurrentSolution;
var project = solution.AddProject("project", "project.dll", LanguageNames.CSharp);
var document = project.AddDocument("document", "");
var sourceTextContainer = SourceText.From("Text").Container;
Assert.True(previewWorkspace.TryApplyChanges(document.Project.Solution));
previewWorkspace.OpenDocument(document.Id, sourceTextContainer);
Assert.Equal(1, previewWorkspace.GetOpenDocumentIds().Count());
Assert.True(previewWorkspace.IsDocumentOpen(document.Id));
previewWorkspace.CloseDocument(document.Id);
Assert.Equal(0, previewWorkspace.GetOpenDocumentIds().Count());
Assert.False(previewWorkspace.IsDocumentOpen(document.Id));
}
[Fact]
public async Task TestPreviewServices()
{
using var previewWorkspace = new PreviewWorkspace(EditorTestCompositions.EditorFeatures.GetHostServices());
var service = previewWorkspace.Services.GetService<ISolutionCrawlerRegistrationService>();
var registrationService = Assert.IsType<SolutionCrawlerRegistrationService>(service);
Assert.False(registrationService.Register(previewWorkspace));
var persistentService = previewWorkspace.Services.SolutionServices.GetPersistentStorageService();
await using var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None);
Assert.IsType<NoOpPersistentStorage>(storage);
}
[WpfFact]
public async Task TestPreviewDiagnosticTagger()
{
using var workspace = TestWorkspace.CreateCSharp("class { }", composition: EditorTestCompositions.EditorFeatures);
using var previewWorkspace = new PreviewWorkspace(workspace.CurrentSolution);
// preview workspace and owner of the solution now share solution and its underlying text buffer
var hostDocument = workspace.Projects.First().Documents.First();
previewWorkspace.TryApplyChanges(previewWorkspace.CurrentSolution.WithAnalyzerReferences(new[] { DiagnosticExtensions.GetCompilerDiagnosticAnalyzerReference(LanguageNames.CSharp) }));
// enable preview diagnostics
previewWorkspace.EnableSolutionCrawler();
var diagnosticsAndErrorsSpans = await SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync<DiagnosticsSquiggleTaggerProvider, IErrorTag>(workspace);
const string AnalyzerCount = "Analyzer Count: ";
Assert.Equal(AnalyzerCount + 1, AnalyzerCount + diagnosticsAndErrorsSpans.Item1.Length);
const string SquigglesCount = "Squiggles Count: ";
Assert.Equal(SquigglesCount + 1, SquigglesCount + diagnosticsAndErrorsSpans.Item2.Length);
}
[WpfFact]
public async Task TestPreviewDiagnosticTaggerInPreviewPane()
{
// TODO: WPF required due to https://github.com/dotnet/roslyn/issues/46153
using var workspace = TestWorkspace.CreateCSharp("class { }", composition: EditorTestCompositions.EditorFeaturesWpf);
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { DiagnosticExtensions.GetCompilerDiagnosticAnalyzerReference(LanguageNames.CSharp) }));
var hostDocument = workspace.Projects.First().Documents.First();
// make a change to remove squiggle
var oldDocument = workspace.CurrentSolution.GetRequiredDocument(hostDocument.Id);
var oldText = oldDocument.GetTextAsync().Result;
var newDocument = oldDocument.WithText(oldText.WithChanges(new TextChange(new TextSpan(0, oldText.Length), "class C { }")));
// create a diff view
WpfTestRunner.RequireWpfFact($"{nameof(TestPreviewDiagnosticTaggerInPreviewPane)} creates a {nameof(DifferenceViewerPreview)}");
var previewFactoryService = (PreviewFactoryService)workspace.ExportProvider.GetExportedValue<IPreviewFactoryService>();
using var diffView = await previewFactoryService.CreateChangedDocumentPreviewViewAsync(oldDocument, newDocument, CancellationToken.None);
AssertEx.NotNull(diffView);
var listenerProvider = workspace.ExportProvider.GetExportedValue<AsynchronousOperationListenerProvider>();
var provider = workspace.ExportProvider.GetExportedValues<ITaggerProvider>().OfType<DiagnosticsSquiggleTaggerProvider>().Single();
// set up tagger for both buffers
var leftBuffer = diffView.Viewer.LeftView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var rightBuffer = diffView.Viewer.RightView.BufferGraph.GetTextBuffers(t => t.ContentType.IsOfType(ContentTypeNames.CSharpContentType)).First();
var leftDocument = leftBuffer.GetRelatedDocuments().Single();
var rightDocument = rightBuffer.GetRelatedDocuments().Single();
// Diagnostic analyzer service, which provides pull capabilities (and not to be confused with
// IDiagnosticService, which is push), doesn't normally register for test workspace. So do it explicitly.
var diagnosticAnalyzer = workspace.ExportProvider.GetExportedValue<IDiagnosticAnalyzerService>();
var incrementalAnalyzer = (IIncrementalAnalyzerProvider)diagnosticAnalyzer;
incrementalAnalyzer.CreateIncrementalAnalyzer(leftDocument.Project.Solution.Workspace);
incrementalAnalyzer.CreateIncrementalAnalyzer(rightDocument.Project.Solution.Workspace);
var leftTagger = provider.CreateTagger<IErrorTag>(leftBuffer);
var rightTagger = provider.CreateTagger<IErrorTag>(rightBuffer);
Contract.ThrowIfNull(leftTagger);
Contract.ThrowIfNull(rightTagger);
using var leftDisposable = leftTagger as IDisposable;
using var rightDisposable = rightTagger as IDisposable;
// wait for diagnostics and taggers
await listenerProvider.WaitAllDispatcherOperationAndTasksAsync(workspace, FeatureAttribute.DiagnosticService, FeatureAttribute.ErrorSquiggles);
// check left buffer
var leftSnapshot = leftBuffer.CurrentSnapshot;
var leftSpans = leftTagger.GetTags(leftSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(1, leftSpans.Count);
// check right buffer
var rightSnapshot = rightBuffer.CurrentSnapshot;
var rightSpans = rightTagger.GetTags(rightSnapshot.GetSnapshotSpanCollection()).ToList();
Assert.Equal(0, rightSpans.Count);
}
[WorkItem("https://github.com/dotnet/roslyn/issues/28639")]
[Fact]
public void TestPreviewWorkspaceDoesNotLeakSolution()
{
// Verify that analyzer execution doesn't leak solution instances from the preview workspace.
var previewWorkspace = new PreviewWorkspace();
Assert.NotNull(previewWorkspace.CurrentSolution);
var solutionObjectReference = ObjectReference.CreateFromFactory(
static previewWorkspace =>
{
var project = previewWorkspace.CurrentSolution.AddProject("project", "project.dll", LanguageNames.CSharp);
Assert.True(previewWorkspace.TryApplyChanges(project.Solution));
return previewWorkspace.CurrentSolution;
},
previewWorkspace);
var analyzers = ImmutableArray.Create<DiagnosticAnalyzer>(new CommonDiagnosticAnalyzers.NotConfigurableDiagnosticAnalyzer());
ExecuteAnalyzers(previewWorkspace, analyzers);
previewWorkspace.Dispose();
solutionObjectReference.AssertReleased();
}
[Fact]
[WorkItem("https://github.com/dotnet/roslyn/pull/67142")]
public void TestPreviewWorkspaceDoesNotLeakItself()
{
var composition = EditorTestCompositions.EditorFeatures;
var exportProvider = composition.ExportProviderFactory.CreateExportProvider();
var previewWorkspaceReference = ObjectReference.CreateFromFactory(
static composition => new PreviewWorkspace(composition.GetHostServices()),
composition);
// Verify the GC can reclaim member for a workspace which has not been disposed.
previewWorkspaceReference.AssertReleased();
// Keep the export provider alive longer than the workspace to further ensure that the workspace is not GC
// rooted within the export provider instance.
GC.KeepAlive(exportProvider);
}
private static void ExecuteAnalyzers(PreviewWorkspace previewWorkspace, ImmutableArray<DiagnosticAnalyzer> analyzers)
{
var analyzerOptions = new AnalyzerOptions(additionalFiles: ImmutableArray<AdditionalText>.Empty);
var project = previewWorkspace.CurrentSolution.Projects.Single();
var ideAnalyzerOptions = IdeAnalyzerOptions.GetDefault(project.Services);
var workspaceAnalyzerOptions = new WorkspaceAnalyzerOptions(analyzerOptions, ideAnalyzerOptions);
var compilationWithAnalyzersOptions = new CompilationWithAnalyzersOptions(workspaceAnalyzerOptions, onAnalyzerException: null, concurrentAnalysis: false, logAnalyzerExecutionTime: false);
var compilation = project.GetRequiredCompilationAsync(CancellationToken.None).Result;
var compilationWithAnalyzers = new CompilationWithAnalyzers(compilation, analyzers, compilationWithAnalyzersOptions);
var result = compilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None).Result;
Assert.Equal(1, result.CompilationDiagnostics.Count);
}
}
}
|