Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release/dev17.1 to main #58061

Merged
merged 2 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<MicrosoftPortableTargetsVersion>0.1.2-dev</MicrosoftPortableTargetsVersion>
<MicrosoftServiceHubClientVersion>3.1.4</MicrosoftServiceHubClientVersion>
<MicrosoftServiceHubFrameworkVersion>3.1.4</MicrosoftServiceHubFrameworkVersion>
<MicrosoftSourceLinkToolsVersion>1.1.1-beta-21566-01</MicrosoftSourceLinkToolsVersion>
<MicrosoftVisualBasicVersion>10.1.0</MicrosoftVisualBasicVersion>
<MicrosoftVisualStudioCacheVersion>17.0.13-alpha</MicrosoftVisualStudioCacheVersion>
<MicrosoftVisualStudioCallHierarchyPackageDefinitionsVersion>15.8.27812-alpha</MicrosoftVisualStudioCallHierarchyPackageDefinitionsVersion>
Expand Down
1 change: 1 addition & 0 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.UnusedReferenceAnalysis" ClassName="Microsoft.CodeAnalysis.Remote.RemoteUnusedReferenceAnalysisService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.ProcessTelemetry" ClassName="Microsoft.CodeAnalysis.Remote.RemoteProcessTelemetryService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.CompilationAvailable" ClassName="Microsoft.CodeAnalysis.Remote.RemoteCompilationAvailableService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.LanguageServices.StackTraceExplorer" ClassName="Microsoft.CodeAnalysis.Remote.Services.StackTraceExplorer.RemoteStackTraceExplorerService+Factory" />
</ItemGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.PdbSourceDocument;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.PdbSourceDocument
{
[UseExportProvider]
public abstract class AbstractPdbSourceDocumentTests
{
public enum Location
{
OnDisk,
Embedded
}

protected static Task TestAsync(
Location pdbLocation,
Location sourceLocation,
string metadataSource,
Func<Compilation, ISymbol> symbolMatcher,
string[]? preprocessorSymbols = null,
bool buildReferenceAssembly = false,
bool expectNullResult = false)
{
return RunTestAsync(path => TestAsync(
path,
pdbLocation,
sourceLocation,
metadataSource,
symbolMatcher,
preprocessorSymbols,
buildReferenceAssembly,
expectNullResult));
}

protected static async Task RunTestAsync(Func<string, Task> testRunner)
{
var path = Path.Combine(Path.GetTempPath(), nameof(PdbSourceDocumentTests));

try
{
Directory.CreateDirectory(path);

await testRunner(path);
}
finally
{
if (Directory.Exists(path))
{
Directory.Delete(path, recursive: true);
}
}
}

protected static async Task TestAsync(
string path,
Location pdbLocation,
Location sourceLocation,
string metadataSource,
Func<Compilation, ISymbol> symbolMatcher,
string[]? preprocessorSymbols,
bool buildReferenceAssembly,
bool expectNullResult)
{
MarkupTestFile.GetSpan(metadataSource, out var source, out var expectedSpan);

var (project, symbol) = await CompileAndFindSymbolAsync(
path,
pdbLocation,
sourceLocation,
source,
symbolMatcher,
preprocessorSymbols,
buildReferenceAssembly,
windowsPdb: false);

await GenerateFileAndVerifyAsync(project, symbol, source, expectedSpan, expectNullResult);
}

protected static async Task GenerateFileAndVerifyAsync(
Project project,
ISymbol symbol,
string expected,
Text.TextSpan expectedSpan,
bool expectNullResult)
{
var (actual, actualSpan) = await GetGeneratedSourceTextAsync(project, symbol, expectNullResult);

if (actual is null)
return;

// Compare exact texts and verify that the location returned is exactly that
// indicated by expected
AssertEx.EqualOrDiff(expected, actual.ToString());
Assert.Equal(expectedSpan.Start, actualSpan.Start);
Assert.Equal(expectedSpan.End, actualSpan.End);
}

protected static async Task<(SourceText?, TextSpan)> GetGeneratedSourceTextAsync(
Project project,
ISymbol symbol,
bool expectNullResult)
{
using var workspace = (TestWorkspace)project.Solution.Workspace;

var service = workspace.GetService<IMetadataAsSourceFileService>();
try
{
var file = await service.GetGeneratedFileAsync(project, symbol, signaturesOnly: false, allowDecompilation: false, CancellationToken.None).ConfigureAwait(false);

if (expectNullResult)
{
Assert.Same(NullResultMetadataAsSourceFileProvider.NullResult, file);
return (null, default);
}
else
{
Assert.NotSame(NullResultMetadataAsSourceFileProvider.NullResult, file);
}

AssertEx.NotNull(file, $"No source document was found in the pdb for the symbol.");

var masWorkspace = service.TryGetWorkspace();

var document = masWorkspace!.CurrentSolution.Projects.First().Documents.First();

Assert.Equal(document.FilePath, file.FilePath);

var actual = await document.GetTextAsync();
var actualSpan = file!.IdentifierLocation.SourceSpan;

return (actual, actualSpan);
}
finally
{
service.CleanupGeneratedFiles();
service.TryGetWorkspace()?.Dispose();
}
}

protected static Task<(Project, ISymbol)> CompileAndFindSymbolAsync(
string path,
Location pdbLocation,
Location sourceLocation,
string source,
Func<Compilation, ISymbol> symbolMatcher,
string[]? preprocessorSymbols = null,
bool buildReferenceAssembly = false,
bool windowsPdb = false,
Encoding? encoding = null)
{
var sourceText = SourceText.From(source, encoding: encoding ?? Encoding.UTF8);
return CompileAndFindSymbolAsync(path, pdbLocation, sourceLocation, sourceText, symbolMatcher, preprocessorSymbols, buildReferenceAssembly, windowsPdb);
}

protected static async Task<(Project, ISymbol)> CompileAndFindSymbolAsync(
string path,
Location pdbLocation,
Location sourceLocation,
SourceText source,
Func<Compilation, ISymbol> symbolMatcher,
string[]? preprocessorSymbols = null,
bool buildReferenceAssembly = false,
bool windowsPdb = false,
Encoding? fallbackEncoding = null)
{
var preprocessorSymbolsAttribute = preprocessorSymbols?.Length > 0
? $"PreprocessorSymbols=\"{string.Join(";", preprocessorSymbols)}\""
: "";

// We construct our own composition here because we only want the decompilation metadata as source provider
// to be available.
var composition = EditorTestCompositions.EditorFeatures
.WithExcludedPartTypes(ImmutableHashSet.Create(typeof(IMetadataAsSourceFileProvider)))
.AddParts(typeof(PdbSourceDocumentMetadataAsSourceFileProvider), typeof(NullResultMetadataAsSourceFileProvider));

var workspace = TestWorkspace.Create(@$"
<Workspace>
<Project Language=""{LanguageNames.CSharp}"" CommonReferences=""true"" ReferencesOnDisk=""true"" {preprocessorSymbolsAttribute}>
</Project>
</Workspace>", composition: composition);

var project = workspace.CurrentSolution.Projects.First();

CompileTestSource(path, source, project, pdbLocation, sourceLocation, buildReferenceAssembly, windowsPdb, fallbackEncoding);

project = project.AddMetadataReference(MetadataReference.CreateFromFile(GetDllPath(path)));

var mainCompilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false);

var symbol = symbolMatcher(mainCompilation);

AssertEx.NotNull(symbol, $"Couldn't find symbol to go-to-def for.");

return (project, symbol);
}

protected static void CompileTestSource(string path, SourceText source, Project project, Location pdbLocation, Location sourceLocation, bool buildReferenceAssembly, bool windowsPdb, Encoding? fallbackEncoding = null)
{
var dllFilePath = GetDllPath(path);
var sourceCodePath = GetSourceFilePath(path);
var pdbFilePath = GetPdbPath(path);

var assemblyName = "ReferencedAssembly";

var languageServices = project.Solution.Workspace.Services.GetLanguageServices(LanguageNames.CSharp);
var compilationFactory = languageServices.GetRequiredService<ICompilationFactoryService>();
var options = compilationFactory.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var parseOptions = project.ParseOptions;

var compilation = compilationFactory
.CreateCompilation(assemblyName, options)
.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(source, options: parseOptions, path: sourceCodePath))
.AddReferences(project.MetadataReferences);

IEnumerable<EmbeddedText>? embeddedTexts;
if (sourceLocation == Location.OnDisk)
{
embeddedTexts = null;
File.WriteAllText(sourceCodePath, source.ToString(), source.Encoding);
}
else
{
embeddedTexts = new[] { EmbeddedText.FromSource(sourceCodePath, source) };
}

EmitOptions emitOptions;
if (buildReferenceAssembly)
{
pdbFilePath = null;
emitOptions = new EmitOptions(metadataOnly: true, includePrivateMembers: false);
}
else if (pdbLocation == Location.OnDisk)
{
emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: pdbFilePath);
}
else
{
pdbFilePath = null;
emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded);
}

// TODO: When supported, move this to pdbLocation
if (windowsPdb)
{
emitOptions = emitOptions.WithDebugInformationFormat(DebugInformationFormat.Pdb);
}

if (fallbackEncoding is null)
{
emitOptions = emitOptions.WithDefaultSourceFileEncoding(source.Encoding);
}
else
{
emitOptions = emitOptions.WithFallbackSourceFileEncoding(fallbackEncoding);
}

using (var dllStream = FileUtilities.CreateFileStreamChecked(File.Create, dllFilePath, nameof(dllFilePath)))
using (var pdbStream = (pdbFilePath == null ? null : FileUtilities.CreateFileStreamChecked(File.Create, pdbFilePath, nameof(pdbFilePath))))
{
var result = compilation.Emit(dllStream, pdbStream, options: emitOptions, embeddedTexts: embeddedTexts);
Assert.Empty(result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
}
}

protected static string GetDllPath(string path)
{
return Path.Combine(path, "reference.dll");
}

protected static string GetSourceFilePath(string path)
{
return Path.Combine(path, "source.cs");
}

protected static string GetPdbPath(string path)
{
return Path.Combine(path, "reference.pdb");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.PdbSourceDocument;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.PdbSourceDocument
{
/// <summary>
/// IMetadataAsSourceFileService has to always return a result, but for our testing
/// we remove the decompilation provider that would normally ensure that. This provider
/// takes it place to ensure we always return a known null result, so we can also verify
/// against it in tests.
/// </summary>
[ExportMetadataAsSourceFileProvider("Dummy"), Shared]
[ExtensionOrder(After = PdbSourceDocumentMetadataAsSourceFileProvider.ProviderName)]
internal class NullResultMetadataAsSourceFileProvider : IMetadataAsSourceFileProvider
{
// Represents a null result
public static MetadataAsSourceFile NullResult = new("", null, null, null);

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public NullResultMetadataAsSourceFileProvider()
{
}

public void CleanupGeneratedFiles(Workspace? workspace)
{
}

public Task<MetadataAsSourceFile?> GetGeneratedFileAsync(Workspace workspace, Project project, ISymbol symbol, bool signaturesOnly, bool allowDecompilation, string tempPath, CancellationToken cancellationToken)
{
return Task.FromResult<MetadataAsSourceFile?>(NullResult);
}

public Project? MapDocument(Document document)
{
return null;
}

public bool TryAddDocumentToWorkspace(Workspace workspace, string filePath, Text.SourceTextContainer sourceTextContainer)
{
return true;
}

public bool TryRemoveDocumentFromWorkspace(Workspace workspace, string filePath)
{
return true;
}
}
}
Loading