diff --git a/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs b/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..40513d3556e27d4ba85d9f543140a09c980ae80a --- /dev/null +++ b/src/EditorFeatures/TestUtilities/MinimalTestExportProvider.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; +using Microsoft.CodeAnalysis.Shared.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.VisualStudio.Composition; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests +{ + public static class MinimalTestExportProvider + { + private static readonly PartDiscovery s_partDiscovery = CreatePartDiscovery(Resolver.DefaultInstance); + private static readonly Lazy s_lazyLanguageNeutralCatalog = new Lazy(() => CreateAssemblyCatalog(GetVisualStudioAssemblies()).WithParts(CreateAssemblyCatalog(GetLanguageNeutralTypes().Select(t => t.Assembly).Distinct()))); + + public static ComposableCatalog LanguageNeutralCatalog + { + get + { + return s_lazyLanguageNeutralCatalog.Value; + } + } + + public static Type[] GetLanguageNeutralTypes() + { + var types = new[] + { + // ROSLYN + typeof(Microsoft.CodeAnalysis.Editor.Implementation.Workspaces.WorkspaceTaskSchedulerFactoryFactory), + typeof(Microsoft.CodeAnalysis.Host.WorkspaceTaskSchedulerFactoryFactory), + typeof(Microsoft.CodeAnalysis.Formatting.Rules.DefaultFormattingRuleFactoryServiceFactory), + typeof(Microsoft.CodeAnalysis.Host.PersistentStorageServiceFactory), + typeof(Microsoft.CodeAnalysis.Text.Implementation.TextBufferFactoryService.TextBufferCloneServiceFactory), + typeof(Microsoft.CodeAnalysis.Host.MetadataServiceFactory), + typeof(Microsoft.CodeAnalysis.Host.TemporaryStorageServiceFactory), + typeof(Microsoft.CodeAnalysis.Host.TextFactoryService), + typeof(Microsoft.CodeAnalysis.Editor.Implementation.Workspaces.ProjectCacheHostServiceFactory), + typeof(Solution), // ServicesCore + typeof(Microsoft.CodeAnalysis.Options.GlobalOptionService), + typeof(Microsoft.CodeAnalysis.Options.OptionServiceFactory), + typeof(Microsoft.CodeAnalysis.Options.Providers.ExportedOptionProvider), + typeof(Microsoft.CodeAnalysis.Editor.Implementation.SmartIndent.SmartIndentProvider), + typeof(Microsoft.CodeAnalysis.Editor.Implementation.ForegroundNotification.ForegroundNotificationService), + typeof(Microsoft.CodeAnalysis.Editor.UnitTests.TestOptionsServiceFactory), + typeof(SymbolMapping.SymbolMappingServiceFactory), + typeof(TestWaitIndicator), + typeof(TestExtensionErrorHandler), + typeof(TestExportProvider) + }; + + return types.Concat(TestHelpers.GetAllTypesWithStaticFieldsImplementingType(typeof(InternalSolutionCrawlerOptions).Assembly, typeof(Microsoft.CodeAnalysis.Options.IOption))) + .Concat(TestHelpers.GetAllTypesWithStaticFieldsImplementingType(typeof(EditorComponentOnOffOptions).Assembly, typeof(Microsoft.CodeAnalysis.Options.IOption))) + .Concat(TestHelpers.GetAllTypesWithStaticFieldsImplementingType(typeof(ServiceComponentOnOffOptions).Assembly, typeof(Microsoft.CodeAnalysis.Options.IOption))) + .Concat(TestHelpers.GetAllTypesWithStaticFieldsImplementingType(typeof(Microsoft.CodeAnalysis.Formatting.FormattingOptions).Assembly, typeof(Microsoft.CodeAnalysis.Options.IOption))) + .Distinct() + .ToArray(); + } + + public static IEnumerable GetVisualStudioAssemblies() + { + var assemblies = new[] + { + // EDITOR + + // Microsoft.VisualStudio.Platform.VSEditor.dll: + typeof(Microsoft.VisualStudio.Platform.VSEditor.EventArgsHelper).Assembly, + + // Microsoft.VisualStudio.Text.Logic.dll: + // Must include this because several editor options are actually stored as exported information + // on this DLL. Including most importantly, the tab size information. + typeof(Microsoft.VisualStudio.Text.Editor.DefaultOptions).Assembly, + + // Microsoft.VisualStudio.Text.UI.dll: + // Include this DLL to get several more EditorOptions including WordWrapStyle. + typeof(Microsoft.VisualStudio.Text.Editor.WordWrapStyle).Assembly, + + // Microsoft.VisualStudio.Text.UI.Wpf.dll: + // Include this DLL to get more EditorOptions values. + typeof(Microsoft.VisualStudio.Text.Editor.HighlightCurrentLineOption).Assembly, + + // BasicUndo.dll: + // Include this DLL to satisfy ITextUndoHistoryRegistry + typeof(BasicUndo.IBasicUndoHistory).Assembly, + + // Microsoft.VisualStudio.Language.StandardClassification.dll: + typeof(Microsoft.VisualStudio.Language.StandardClassification.PredefinedClassificationTypeNames).Assembly + }; + + return assemblies; + } + + public static ComposableCatalog CreateAssemblyCatalog(Assembly assembly) + { + return CreateAssemblyCatalog(SpecializedCollections.SingletonEnumerable(assembly)); + } + + public static ComposableCatalog CreateAssemblyCatalog(IEnumerable assemblies, Resolver resolver = null) + { + var discovery = resolver == null ? s_partDiscovery : CreatePartDiscovery(resolver); + + // If we run CreatePartsAsync on the test thread we may deadlock since it'll schedule stuff back + // on the thread. + var parts = Task.Run(async () => await discovery.CreatePartsAsync(assemblies).ConfigureAwait(false)).Result; + + return ComposableCatalog.Create(resolver ?? Resolver.DefaultInstance).AddParts(parts); + } + + public static ComposableCatalog CreateTypeCatalog(IEnumerable types, Resolver resolver = null) + { + var discovery = resolver == null ? s_partDiscovery : CreatePartDiscovery(resolver); + + // If we run CreatePartsAsync on the test thread we may deadlock since it'll schedule stuff back + // on the thread. + var parts = Task.Run(async () => await discovery.CreatePartsAsync(types).ConfigureAwait(false)).Result; + + return ComposableCatalog.Create(resolver ?? Resolver.DefaultInstance).AddParts(parts); + } + + public static Resolver CreateResolver() + { + // simple assembly loader is stateless, so okay to share + return new Resolver(SimpleAssemblyLoader.Instance); + } + + public static PartDiscovery CreatePartDiscovery(Resolver resolver) + { + return PartDiscovery.Combine(new AttributedPartDiscoveryV1(resolver), new AttributedPartDiscovery(resolver, isNonPublicSupported: true)); + } + + public static ExportProvider CreateExportProvider(ComposableCatalog catalog) + { + var configuration = CompositionConfiguration.Create(catalog.WithDesktopSupport().WithCompositionService()); + var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); + return runtimeComposition.CreateExportProviderFactory().CreateExportProvider(); + } + + public static ComposableCatalog WithParts(this ComposableCatalog @this, ComposableCatalog catalog) + { + return @this.AddParts(catalog.DiscoveredParts); + } + + public static ComposableCatalog WithParts(this ComposableCatalog catalog, IEnumerable types) + { + return catalog.WithParts(CreateTypeCatalog(types)); + } + + public static ComposableCatalog WithParts(this ComposableCatalog catalog, params Type[] types) + { + return WithParts(catalog, (IEnumerable)types); + } + + public static ComposableCatalog WithPart(this ComposableCatalog catalog, Type t) + { + return catalog.WithParts(CreateTypeCatalog(SpecializedCollections.SingletonEnumerable(t))); + } + + private class SimpleAssemblyLoader : IAssemblyLoader + { + public static readonly IAssemblyLoader Instance = new SimpleAssemblyLoader(); + + public Assembly LoadAssembly(AssemblyName assemblyName) + { + return Assembly.Load(assemblyName); + } + + public Assembly LoadAssembly(string assemblyFullName, string codeBasePath) + { + var assemblyName = new AssemblyName(assemblyFullName); + if (!string.IsNullOrEmpty(codeBasePath)) + { + assemblyName.CodeBase = codeBasePath; + } + + return this.LoadAssembly(assemblyName); + } + } + } +} diff --git a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj index 7c1a02f49c768bb278fe247f1bff15f8a1da0e59..6b91ef23daad9eff545b74eb9375c416af6fc374 100644 --- a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj +++ b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj @@ -29,6 +29,10 @@ ARM + + {edc68a0e-c68d-4a74-91b7-bf38ec909888} + Features + {76c6f005-c89d-4348-bb4a-391898dbeb52} TestUtilities.Desktop @@ -37,6 +41,10 @@ {f7712928-1175-47b3-8819-ee086753dee2} TestUtilities.FX45 + + {2e87fa96-50bb-4607-8676-46521599f998} + Workspaces.Desktop + {5f8d2414-064a-4b3a-9b42-8e2a04246be5} Workspaces @@ -45,6 +53,10 @@ {3CDEEAB7-2256-418A-BEB2-620B5CB16302} EditorFeatures + + {18f5fbb8-7570-4412-8cc7-0a86ff13b7ba} + TextEditorFeatures + @@ -66,6 +78,11 @@ + + + + + @@ -99,4 +116,4 @@ - + \ No newline at end of file diff --git a/src/EditorFeatures/TestUtilities/TestExportProvider.cs b/src/EditorFeatures/TestUtilities/TestExportProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..83547bcd74ca5be44322f7d3fd7dc6c2b32d6228 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/TestExportProvider.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.VisualStudio.Composition; +using Roslyn.Test.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests +{ + /// + /// This type caches MEF compositions for our unit tests. MEF composition is a relatively expensive + /// operation and caching yields demonstrable benefits for testing. + /// + /// These caches must be done in a thread static manner. Many of the stored values are non-frozen + /// WPF elements which will throw if shared between threads. It is legal for a given xUnit runner + /// to execute classes on different threads hence we must handle this scenario. + /// + public static class TestExportProvider + { + [ThreadStatic] + private static Lazy t_lazyEntireAssemblyCatalogWithCSharpAndVisualBasic; + + public static ComposableCatalog EntireAssemblyCatalogWithCSharpAndVisualBasic + { + get + { + if (t_lazyEntireAssemblyCatalogWithCSharpAndVisualBasic == null) + { + t_lazyEntireAssemblyCatalogWithCSharpAndVisualBasic = new Lazy(() => CreateAssemblyCatalogWithCSharpAndVisualBasic()); + } + + return t_lazyEntireAssemblyCatalogWithCSharpAndVisualBasic.Value; + } + } + + [ThreadStatic] + private static Lazy t_lazyExportProviderWithCSharpAndVisualBasic; + + public static ExportProvider ExportProviderWithCSharpAndVisualBasic + { + get + { + if (t_lazyExportProviderWithCSharpAndVisualBasic == null) + { + t_lazyExportProviderWithCSharpAndVisualBasic = new Lazy(CreateExportProviderWithCSharpAndVisualBasic); + } + + return t_lazyExportProviderWithCSharpAndVisualBasic.Value; + } + } + + [ThreadStatic] + private static Lazy t_lazyMinimumCatalogWithCSharpAndVisualBasic; + + public static ComposableCatalog MinimumCatalogWithCSharpAndVisualBasic + { + get + { + if (t_lazyMinimumCatalogWithCSharpAndVisualBasic == null) + { + t_lazyMinimumCatalogWithCSharpAndVisualBasic = new Lazy(() => MinimalTestExportProvider.CreateTypeCatalog(GetNeutralAndCSharpAndVisualBasicTypes()) + .WithParts(MinimalTestExportProvider.CreateAssemblyCatalog(MinimalTestExportProvider.GetVisualStudioAssemblies()))); + } + + return t_lazyMinimumCatalogWithCSharpAndVisualBasic.Value; + } + } + + private static Type[] GetNeutralAndCSharpAndVisualBasicTypes() + { + var types = new[] + { + // ROSLYN + typeof(Workspaces.NoCompilationLanguageServiceFactory), + typeof(Workspaces.NoCompilationContentTypeDefinitions), + typeof(Workspaces.NoCompilationContentTypeLanguageService), + typeof(Microsoft.CodeAnalysis.CSharp.IntroduceVariable.CSharpIntroduceVariableService), // Ensures that CSharpFeatures is included in the composition + typeof(Microsoft.CodeAnalysis.VisualBasic.IntroduceVariable.VisualBasicIntroduceVariableService), // Ensures that BasicFeatures is included in the composition + typeof(Microsoft.CodeAnalysis.Editor.CSharp.ContentType.ContentTypeDefinitions), // CSharp Content Type + typeof(Microsoft.CodeAnalysis.Editor.VisualBasic.ContentType.ContentTypeDefinitions), // VB Content Type + typeof(Microsoft.CodeAnalysis.Editor.Implementation.SmartIndent.SmartIndentProvider), + typeof(Microsoft.CodeAnalysis.Editor.VisualBasic.Formatting.Indentation.VisualBasicIndentationService), + typeof(Microsoft.CodeAnalysis.Editor.CSharp.Formatting.Indentation.CSharpIndentationService), + typeof(Microsoft.CodeAnalysis.Editor.Implementation.ForegroundNotification.ForegroundNotificationService), + typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilationFactoryService), + typeof(Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationFactoryService), + typeof(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTreeFactoryServiceFactory), // CSharpServicesCore + typeof(Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTreeFactoryServiceFactory), // BasicServicesCore + typeof(CodeAnalysis.CSharp.CodeGeneration.CSharpCodeGenerationServiceFactory), + typeof(CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicCodeGenerationServiceFactory), + typeof(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxFactsServiceFactory), + typeof(Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxFactsServiceFactory), + typeof(CodeAnalysis.CSharp.CSharpSymbolDeclarationService), + typeof(CodeAnalysis.VisualBasic.VisualBasicSymbolDeclarationService), + typeof(CodeAnalysis.Editor.CSharp.LanguageServices.CSharpSymbolDisplayServiceFactory), + typeof(Microsoft.CodeAnalysis.Editor.CSharp.Interactive.CSharpInteractiveEvaluator), + typeof(CodeAnalysis.Editor.VisualBasic.LanguageServices.VisualBasicSymbolDisplayServiceFactory), + typeof(Microsoft.CodeAnalysis.Editor.VisualBasic.Interactive.VisualBasicInteractiveEvaluator), + typeof(CodeAnalysis.CSharp.Simplification.CSharpSimplificationService), + typeof(CodeAnalysis.VisualBasic.Simplification.VisualBasicSimplificationService), + typeof(CodeAnalysis.CSharp.Rename.CSharpRenameConflictLanguageService), + typeof(CodeAnalysis.VisualBasic.Rename.VisualBasicRenameRewriterLanguageServiceFactory), + typeof(CodeAnalysis.CSharp.CSharpSemanticFactsService), + typeof(CodeAnalysis.VisualBasic.VisualBasicSemanticFactsService), + typeof(CodeAnalysis.CSharp.CodeGeneration.CSharpSyntaxGenerator), + typeof(CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator), + typeof(CSharp.LanguageServices.CSharpContentTypeLanguageService), + typeof(VisualBasic.LanguageServices.VisualBasicContentTypeLanguageService), + typeof(IncrementalCaches.SymbolTreeInfoIncrementalAnalyzerProvider), + typeof(CodeAnalysis.Diagnostics.EngineV2.InProcCodeAnalysisDiagnosticAnalyzerExecutor) + }; + + return MinimalTestExportProvider.GetLanguageNeutralTypes() + .Concat(types) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.CSharp.Formatting.DefaultOperationProvider).Assembly, typeof(ISyntaxFormattingService))) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.VisualBasic.Formatting.DefaultOperationProvider).Assembly, typeof(ISyntaxFormattingService))) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.CSharp.Formatting.DefaultOperationProvider).Assembly, typeof(IFormattingRule))) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.VisualBasic.Formatting.DefaultOperationProvider).Assembly, typeof(IFormattingRule))) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.CSharp.Formatting.DefaultOperationProvider).Assembly, typeof(ICodeGenerationService))) + .Concat(TestHelpers.GetAllTypesImplementingGivenInterface(typeof(Microsoft.CodeAnalysis.VisualBasic.Formatting.DefaultOperationProvider).Assembly, typeof(ICodeGenerationService))) + .Concat(TestHelpers.GetAllTypesWithStaticFieldsImplementingType(typeof(Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOptions).Assembly, typeof(Microsoft.CodeAnalysis.Options.IOption))) + .Distinct() + .ToArray(); + } + + /// + /// Create fresh ExportProvider that doesnt share anything with others. + /// test can use this export provider to create all new MEF components not shared with others. + /// + public static ExportProvider CreateExportProviderWithCSharpAndVisualBasic() + { + return MinimalTestExportProvider.CreateExportProvider(CreateAssemblyCatalogWithCSharpAndVisualBasic()); + } + + /// + /// Create fresh ComposableCatalog that doesnt share anything with others. + /// everything under this catalog should have been created from scratch that doesnt share anything with others. + /// + public static ComposableCatalog CreateAssemblyCatalogWithCSharpAndVisualBasic() + { + return MinimalTestExportProvider.CreateAssemblyCatalog( + GetNeutralAndCSharpAndVisualBasicTypes().Select(t => t.Assembly).Distinct().Concat(MinimalTestExportProvider.GetVisualStudioAssemblies()), + MinimalTestExportProvider.CreateResolver()); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs b/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..1d84cf7aa12db088f4992a8718edf3b24dd99ba3 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/TestExtensionErrorHandler.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests +{ + [Export(typeof(TestExtensionErrorHandler))] + [Export(typeof(IExtensionErrorHandler))] + internal class TestExtensionErrorHandler : IExtensionErrorHandler + { + private List _exceptions = new List(); + + public void HandleError(object sender, Exception exception) + { + if (exception is ArgumentOutOfRangeException && ((ArgumentOutOfRangeException)exception).ParamName == "span") + { + // TODO: this is known bug 655591, fixed by Jack in changeset 931906 + // Remove this workaround once the fix reaches the DP branch and we all move over. + return; + } + + _exceptions.Add(exception); + } + + public ICollection GetExceptions() + { + // We'll clear off our list, so that way we don't report this for other tests + var newExceptions = _exceptions; + _exceptions = new List(); + return newExceptions; + } + } +} diff --git a/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs b/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..1345a40b864bfe7a8517409f6e70d80281cb1006 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests +{ + [ExportWorkspaceServiceFactory(typeof(IOptionService), TestWorkspace.WorkspaceName), Shared] + internal class TestOptionsServiceFactory : IWorkspaceServiceFactory + { + private readonly ImmutableArray> _providers; + + [ImportingConstructor] + public TestOptionsServiceFactory( + [ImportMany] IEnumerable> optionProviders) + { + _providers = optionProviders.ToImmutableArray(); + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + // give out new option service per workspace + return new OptionServiceFactory.OptionService( + new GlobalOptionService(_providers, SpecializedCollections.EmptyEnumerable>()), + workspaceServices); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs b/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs new file mode 100644 index 0000000000000000000000000000000000000000..a1a93c48902d9f9317cecb40590377ed80832474 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/TestWaitIndicator.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Shared.Utilities; +using VisualStudioIndicator = Microsoft.VisualStudio.Language.Intellisense.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities +{ + [Export(typeof(IWaitIndicator))] + [Export(typeof(VisualStudioIndicator.IWaitIndicator))] + public sealed class TestWaitIndicator : IWaitIndicator, VisualStudioIndicator.IWaitIndicator + { + public static readonly TestWaitIndicator Default = new TestWaitIndicator(); + + private readonly IWaitContext _waitContext; + private readonly Microsoft.VisualStudio.Language.Intellisense.Utilities.IWaitContext _platformWaitContext = new UncancellableWaitContext(); + + public TestWaitIndicator() + : this(new UncancellableWaitContext()) + { + } + + internal TestWaitIndicator(IWaitContext waitContext) + { + _waitContext = waitContext; + } + + IWaitContext IWaitIndicator.StartWait(string title, string message, bool allowCancel, bool showProgress) + { + return _waitContext; + } + + WaitIndicatorResult IWaitIndicator.Wait(string title, string message, bool allowCancel, bool showProgress, Action action) + { + try + { + action(_waitContext); + } + catch (OperationCanceledException) + { + return WaitIndicatorResult.Canceled; + } + + return WaitIndicatorResult.Completed; + } + + VisualStudioIndicator.IWaitContext VisualStudioIndicator.IWaitIndicator.StartWait(string title, string message, bool allowCancel) + { + return _platformWaitContext; + } + + VisualStudioIndicator.WaitIndicatorResult VisualStudioIndicator.IWaitIndicator.Wait(string title, string message, bool allowCancel, Action action) + { + try + { + action(_platformWaitContext); + } + catch (OperationCanceledException) + { + return VisualStudioIndicator.WaitIndicatorResult.Canceled; + } + + return VisualStudioIndicator.WaitIndicatorResult.Completed; + } + + private sealed class UncancellableWaitContext : IWaitContext, VisualStudioIndicator.IWaitContext + { + public CancellationToken CancellationToken + { + get { return CancellationToken.None; } + } + + public IProgressTracker ProgressTracker { get; } = new ProgressTracker(); + + public void UpdateProgress() + { + } + + public bool AllowCancel + { + get + { + return false; + } + + set + { + } + } + + public string Message + { + get + { + return ""; + } + + set + { + } + } + + public void Dispose() + { + } + } + } +} diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs index 980d069dc0750a347c3db1ef565d1c7ad325266f..81c6d2fcdd003fafccce76f62e8cb5fcd6e51a73 100644 --- a/src/EditorFeatures/TestUtilities/Traits.cs +++ b/src/EditorFeatures/TestUtilities/Traits.cs @@ -139,6 +139,7 @@ public static class Features public const string ReferenceHighlighting = nameof(ReferenceHighlighting); public const string Rename = nameof(Rename); public const string RenameTracking = nameof(RenameTracking); + public const string RemoteHost = nameof(RemoteHost); public const string RQName = nameof(RQName); public const string SignatureHelp = nameof(SignatureHelp); public const string Simplification = nameof(Simplification); diff --git a/src/EditorFeatures/TestUtilities/project.json b/src/EditorFeatures/TestUtilities/project.json index af350334ab1552e83f624501773fa9d802e0c1fa..104e525e655fbfd19e53e82d33797e276802eb95 100644 --- a/src/EditorFeatures/TestUtilities/project.json +++ b/src/EditorFeatures/TestUtilities/project.json @@ -1,9 +1,12 @@ { - "dependencies": { - "xunit": "2.1.0", - "xunit.runner.console": "2.2.0-beta1-build3239" - }, - "frameworks": { - "net46": { } - } + "dependencies": { + "xunit": "2.1.0", + "xunit.runner.console": "2.2.0-beta1-build3239", + "BasicUndo": "0.9.3", + "Microsoft.VisualStudio.Composition": "14.2.19-pre", + "RoslynDependencies.Microsoft.VisualStudio.Platform.VSEditor": "14.3.25407" + }, + "frameworks": { + "net46": { } + } } \ No newline at end of file diff --git a/src/EditorFeatures/Text/TextEditorFeatures.csproj b/src/EditorFeatures/Text/TextEditorFeatures.csproj index 5b3075b5835e21ed14a8e867b7d7d7c84057f1b9..86a57298ed9af12ba6a1751d68bd7c8f30501c13 100644 --- a/src/EditorFeatures/Text/TextEditorFeatures.csproj +++ b/src/EditorFeatures/Text/TextEditorFeatures.csproj @@ -55,6 +55,7 @@ + diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 90a0a9e54c97c9275dca73d34756c331418be56d..5c8031af4a9c2c8d5e8b53d00b9ccc1e568f9851 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -72,6 +72,9 @@ + + + diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs index a0cb210f1d50331f8f2d0a62a5d498727821177f..30e6cb1a8b5c6c7e104c1d6d44388d3e97faedf0 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs @@ -214,6 +214,12 @@ private void OnConnectionChanged(object sender, bool connected) private bool FeaturesEnabled() { + if (_workspace.Options.GetOption(RemoteHostOptions.RemoteHostTest)) + { + // if it is under test, remote host is always enabled + return true; + } + if (ServiceFeatureOnOffOptions.IsClosedFileDiagnosticsEnabled(_workspace.Options, LanguageNames.CSharp)) { return true; diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostOptions.cs b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostOptions.cs index e541118150ecedd9bec170aac9170586157c3a96..dbb76051c135bb392fe44a4aab8af36e256ee6db 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostOptions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostOptions.cs @@ -7,6 +7,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Remote { internal static class RemoteHostOptions { + [ExportOption] + public static readonly Option RemoteHostTest = new Option(OptionName, nameof(RemoteHostTest), defaultValue: false); + [ExportOption] public static readonly Option RemoteHost = new Option(nameof(InternalFeatureOnOffOptions), nameof(RemoteHost), defaultValue: true, storageLocations: new LocalUserProfileStorageLocation(InternalFeatureOnOffOptions.LocalRegistryPath + nameof(RemoteHost))); @@ -14,5 +17,8 @@ internal static class RemoteHostOptions [ExportOption] public static readonly Option SolutionChecksumMonitorBackOffTimeSpanInMS = new Option(nameof(InternalFeatureOnOffOptions), nameof(SolutionChecksumMonitorBackOffTimeSpanInMS), defaultValue: 10000, storageLocations: new LocalUserProfileStorageLocation(InternalFeatureOnOffOptions.LocalRegistryPath + nameof(SolutionChecksumMonitorBackOffTimeSpanInMS))); + + [ExportOption] + public static readonly Option RemoteHostTest = new Option(nameof(InternalFeatureOnOffOptions), nameof(RemoteHostTest), defaultValue: false); } } diff --git a/src/VisualStudio/Core/Test.Next/Mocks/InProcRemoteHostClientFactory.cs b/src/VisualStudio/Core/Test.Next/Mocks/InProcRemoteHostClientFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..6b04472b615ed7ae49364f3af1c75905be710788 --- /dev/null +++ b/src/VisualStudio/Core/Test.Next/Mocks/InProcRemoteHostClientFactory.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServices.Remote; +using Roslyn.VisualStudio.Test.Utilities.Remote; + +namespace Roslyn.VisualStudio.Next.UnitTests.Mocks +{ + [ExportWorkspaceService(typeof(IRemoteHostClientFactory), layer: ServiceLayer.Host), Shared] + internal class InProcRemoteHostClientFactory : IRemoteHostClientFactory + { + public Task CreateAsync(Workspace workspace, CancellationToken cancellationToken) + { + return InProcRemoteHostClient.CreateAsync(workspace, cancellationToken); + } + } +} diff --git a/src/VisualStudio/Core/Test.Next/Mocks/TestHostServices.cs b/src/VisualStudio/Core/Test.Next/Mocks/TestHostServices.cs new file mode 100644 index 0000000000000000000000000000000000000000..2eaa7bb96b57d613f75049e3983484bbfbceb799 --- /dev/null +++ b/src/VisualStudio/Core/Test.Next/Mocks/TestHostServices.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Composition; + +namespace Roslyn.VisualStudio.Next.UnitTests.Mocks +{ + public static class TestHostServices + { + public static HostServices CreateHostServices() + { + return MefV1HostServices.Create( + MinimalTestExportProvider.CreateExportProvider( + TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(typeof(InProcRemoteHostClientFactory))).AsExportProvider()); + } + } +} diff --git a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..aa26b2a6aabca933cb4cc86f8ba82a6074b39a50 --- /dev/null +++ b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Execution; +using Microsoft.CodeAnalysis.Shared.Options; +using Microsoft.VisualStudio.LanguageServices.Remote; +using Microsoft.VisualStudio.Text.Editor; +using Moq; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Roslyn.VisualStudio.Next.UnitTests.Mocks; +using Xunit; + +namespace Roslyn.VisualStudio.Next.UnitTests.Remote +{ + public class RemoteHostClientServiceFactoryTests + { + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public void Creation() + { + var service = CreateRemoteHostClientService(); + Assert.NotNull(service); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task Enable_Disable() + { + var service = CreateRemoteHostClientService(); + + service.Enable(); + + var enabledClient = await service.GetRemoteHostClientAsync(CancellationToken.None).ConfigureAwait(false); + Assert.NotNull(enabledClient); + + service.Disable(); + + var disabledClient = await service.GetRemoteHostClientAsync(CancellationToken.None).ConfigureAwait(false); + Assert.Null(disabledClient); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task GlobalAssets() + { + var workspace = new AdhocWorkspace(TestHostServices.CreateHostServices()); + + var analyzerReference = new AnalyzerFileReference(typeof(object).Assembly.Location, new NullAssemblyAnalyzerLoader()); + var service = CreateRemoteHostClientService(workspace, SpecializedCollections.SingletonEnumerable(analyzerReference)); + + service.Enable(); + + // make sure client is ready + var client = await service.GetRemoteHostClientAsync(CancellationToken.None).ConfigureAwait(false); + + var checksumService = workspace.Services.GetService(); + var asset = checksumService.GetGlobalAsset(analyzerReference, CancellationToken.None); + Assert.NotNull(asset); + + service.Disable(); + + var noAsset = checksumService.GetGlobalAsset(analyzerReference, CancellationToken.None); + Assert.Null(noAsset); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task UpdaterService() + { + var workspace = new AdhocWorkspace(TestHostServices.CreateHostServices()); + workspace.Options = workspace.Options.WithChangedOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS, 1); + + var analyzerReference = new AnalyzerFileReference(typeof(object).Assembly.Location, new NullAssemblyAnalyzerLoader()); + var service = CreateRemoteHostClientService(workspace, SpecializedCollections.SingletonEnumerable(analyzerReference)); + + service.Enable(); + + // make sure client is ready + var client = await service.GetRemoteHostClientAsync(CancellationToken.None).ConfigureAwait(false); + + // add solution + workspace.AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default)); + + // TODO: use waiter to make sure workspace events and updater is ready. + // this delay is temporary until I set all .Next unit test hardness to setup correctly + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + + var checksumService = workspace.Services.GetService(); + + Checksum checksum; + using (var scope = await checksumService.CreateChecksumAsync(workspace.CurrentSolution, CancellationToken.None).ConfigureAwait(false)) + { + // create solution checksum and hold onto the checksum and let it go + checksum = scope.SolutionChecksum.Checksum; + } + + // there should be one held in memory by solution checksum updator + var solutionObject = checksumService.GetChecksumObject(checksum, CancellationToken.None); + Assert.Equal(solutionObject.Checksum, checksum); + + service.Disable(); + } + + private RemoteHostClientServiceFactory.RemoteHostClientService CreateRemoteHostClientService(Workspace workspace = null, IEnumerable hostAnalyzerReferences = null) + { + workspace = workspace ?? new AdhocWorkspace(TestHostServices.CreateHostServices()); + workspace.Options = workspace.Options.WithChangedOption(RemoteHostOptions.RemoteHost, true) + .WithChangedOption(RemoteHostOptions.RemoteHostTest, true) + .WithChangedOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, LanguageNames.CSharp, true) + .WithChangedOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, LanguageNames.VisualBasic, true); + + var analyzerService = GetDiagnosticAnalyzerService(hostAnalyzerReferences ?? SpecializedCollections.EmptyEnumerable()); + + var optionMock = new Mock(MockBehavior.Strict); + var optionFactoryMock = new Mock(MockBehavior.Strict); + optionFactoryMock.SetupGet(i => i.GlobalOptions).Returns(optionMock.Object); + + var factory = new RemoteHostClientServiceFactory(analyzerService, optionFactoryMock.Object); + return factory.CreateService(workspace.Services) as RemoteHostClientServiceFactory.RemoteHostClientService; + } + + private IDiagnosticAnalyzerService GetDiagnosticAnalyzerService(IEnumerable references) + { + var mock = new Mock(MockBehavior.Strict); + mock.Setup(a => a.GetHostAnalyzerReferences()).Returns(references); + return mock.Object; + } + + private class NullAssemblyAnalyzerLoader : IAnalyzerAssemblyLoader + { + public void AddDependencyLocation(string fullPath) + { + } + + public Assembly LoadFromPath(string fullPath) + { + // doesn't matter what it returns + return typeof(object).Assembly; + } + } + } +} \ No newline at end of file diff --git a/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj b/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj index 924420f13124cef70ca17ba58505a1b8204a975e..56b37f4f954f21caace84d220fa698b7c0d82cf9 100644 --- a/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj +++ b/src/VisualStudio/Core/Test.Next/VisualStudioTest.Next.csproj @@ -12,32 +12,54 @@ AnyCPU v4.6 + $(OutDir)Dev15Tests\ + true + + {b501a547-c911-4a05-ac6e-274a50dff30e} + CSharpCodeAnalysis + + + {2523d0e6-df32-4a3e-8ae0-a19bffae2ef6} + BasicCodeAnalysis + + + {b0ce9307-ffdb-4838-a5ec-ce1f7cdc4ac2} + CSharpEditorFeatures + + + {49bfae50-1bce-48ae-bc89-78b7d90a3ecd} + BasicEditorFeatures + + + {edc68a0e-c68d-4a74-91b7-bf38ec909888} + Features + + + {3973b09a-4fbf-44a5-8359-3d22ceb71f71} + CSharpFeatures + + + {a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d} + BasicFeatures + {f7712928-1175-47b3-8819-ee086753dee2} TestUtilities.FX45 + + {ccbd3438-3e84-40a9-83ad-533f23bcfca5} + TestUtilities + {2e87fa96-50bb-4607-8676-46521599f998} Workspaces.Desktop - - {21B239D0-D144-430F-A394-C066D58EE267} - CSharpWorkspace - {1EE8CAD3-55F9-4D91-96B2-084641DA9A6C} CodeAnalysis - - {B501A547-C911-4A05-AC6E-274A50DFF30E} - CSharpCodeAnalysis - - - {B0CE9307-FFDB-4838-A5EC-CE1F7CDC4AC2} - CSharpEditorFeatures - {3CDEEAB7-2256-418A-BEB2-620B5CB16302} EditorFeatures @@ -54,6 +76,10 @@ {86FD5B9A-4FA0-4B10-B59F-CFAF077A859C} ServicesVisualStudio + + {21b239d0-d144-430f-a394-c066d58ee267} + CSharpWorkspace + {f822f72a-cc87-4e31-b57d-853f65cbebf3} RemoteWorkspaces @@ -62,37 +88,30 @@ {80fddd00-9393-47f7-8baf-7e87ce011068} ServiceHub - - {A1455D30-55FC-45EF-8759-3AEBDB13D940} - ServicesVisualStudioTest - - - {5DEFADBD-44EB-47A2-A53E-F1282CC9E4E9} - CSharpVisualStudio + + {57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c} + BasicWorkspace {76C6F005-C89D-4348-BB4A-39189DDBEB52} ServicesTestUtilities - - {3973B09A-4FBF-44A5-8359-3D22CEB71F71} - CSharpFeatures - false - {18F5FBB8-7570-4412-8CC7-0A86FF13B7BA} TextEditorFeatures - false - - {EDC68A0E-C68D-4A74-91B7-BF38EC909888} - Features - false + + {5defadbd-44eb-47a2-a53e-f1282cc9e4e9} + CSharpVisualStudio {78324e87-92d5-412c-9fa8-5cc2fbfbaf6c} VisualStudioTestUtilities.Next + + {d49439d7-56d2-450f-a4f0-74cb95d620e6} + BasicVisualStudio + {fe0d4bdd-1c30-488e-a870-854f5b8c5014} ServicesVisualStudio.Next @@ -103,34 +122,33 @@ - - - - false - - - - Designer - + - + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Test.Next/app.config b/src/VisualStudio/Core/Test.Next/app.config index 7de1ee10bb46ca6cd080c7665efbf41bf2a02443..5c69694d20f0fd79a9614f7250e305ef1c9276d1 100644 --- a/src/VisualStudio/Core/Test.Next/app.config +++ b/src/VisualStudio/Core/Test.Next/app.config @@ -13,12 +13,4 @@ - - - - - - - - diff --git a/src/VisualStudio/Core/Test.Next/project.json b/src/VisualStudio/Core/Test.Next/project.json index f4a82e04dbae078bf135e4bfea0a32cde639f762..b53975a84130878928afa0ee4ad4490cdb8b706c 100644 --- a/src/VisualStudio/Core/Test.Next/project.json +++ b/src/VisualStudio/Core/Test.Next/project.json @@ -1,5 +1,7 @@ { - "dependencies": { }, + "dependencies": { + "Newtonsoft.Json": "8.0.3" + }, "frameworks": { "net46": {} }, diff --git a/src/VisualStudio/TestUtilities.Next/VisualStudioTestUtilities.Next.csproj b/src/VisualStudio/TestUtilities.Next/VisualStudioTestUtilities.Next.csproj index 7b284dab8dcce27a4973805c80a307cf65a25970..17deb8fa1811436310e304167bf6cda988c83cf5 100644 --- a/src/VisualStudio/TestUtilities.Next/VisualStudioTestUtilities.Next.csproj +++ b/src/VisualStudio/TestUtilities.Next/VisualStudioTestUtilities.Next.csproj @@ -34,6 +34,14 @@ + + {1ee8cad3-55f9-4d91-96b2-084641da9a6c} + CodeAnalysis + + + {EDC68A0E-C68D-4A74-91B7-BF38EC909888} + Features + {5f8d2414-064a-4b3a-9b42-8e2a04246be5} Workspaces diff --git a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj index 9803a01c5ee1c159bf1baa825e77830a06366c98..bd6d0b449fdaebec66cf03461faa5e6e67e8ae2b 100644 --- a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj @@ -147,6 +147,7 @@ + diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 75b53c6420bdb110254be7820b33d69c2b846997..e4a91d321d78eb7377fba25a65e21cd9f46357e6 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -282,6 +282,7 @@ +