Skip to content

Commit

Permalink
Merge pull request #58061 from dotnet/merges/release/dev17.1-to-main
Browse files Browse the repository at this point in the history
Merge release/dev17.1 to main
  • Loading branch information
dotnet-bot authored Dec 2, 2021
2 parents 8b71240 + f17419c commit 8a92d8a
Show file tree
Hide file tree
Showing 20 changed files with 1,100 additions and 462 deletions.
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
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

0 comments on commit 8a92d8a

Please sign in to comment.