|
// 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.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.UnitTests;
using Microsoft.CodeAnalysis.MetadataAsSource;
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;
public partial class PdbSourceDocumentTests : AbstractPdbSourceDocumentTests
{
[Theory, CombinatorialData]
public async Task PreprocessorSymbols1(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
#if SOME_DEFINED_CONSTANT
public void [|M|]()
{
}
#else
public void M()
{
}
#endif
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M"), preprocessorSymbols: ["SOME_DEFINED_CONSTANT"]);
}
[Theory, CombinatorialData]
public async Task PreprocessorSymbols2(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
#if SOME_DEFINED_CONSTANT
public void M()
{
}
#else
public void [|M|]()
{
}
#endif
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M"));
}
[Theory, CombinatorialData]
public async Task Method(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public void [|M|]()
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.M"));
}
[Theory, CombinatorialData]
public async Task Constructor(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public [|C|]()
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor"));
}
[Theory, CombinatorialData]
public async Task Parameter(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public void M(int [|a|])
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember<IMethodSymbol>("C.M").Parameters.First());
}
[Theory, CombinatorialData]
public async Task Class_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|C|]
{
// this is a comment that wouldn't appear in decompiled source
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C"));
}
[Theory, CombinatorialData]
public async Task Constructor_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|C|]
{
// this is a comment that wouldn't appear in decompiled source
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor"));
}
[Theory, CombinatorialData]
public async Task NestedClass_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class Outer
{
public class [|C|]
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C"));
}
[Theory, CombinatorialData]
public async Task NestedClassConstructor_FromTypeDefinitionDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class Outer
{
public class [|C|]
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C..ctor"));
}
[Theory, CombinatorialData]
public async Task Class_FromTypeDefinitionDocumentOfNestedClass(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|Outer|]
{
public class C
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer"));
}
[Theory, CombinatorialData]
public async Task Constructor_FromTypeDefinitionDocumentOfNestedClass(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|Outer|]
{
public class C
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer..ctor"));
}
[Theory, CombinatorialData]
public async Task NestedClass_FromMethodDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class Outer
{
public class [|C|]
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C"));
}
[Theory, CombinatorialData]
public async Task NestedClassConstructor_FromMethodDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class Outer
{
public class [|C|]
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer.C..ctor"));
}
[Theory, CombinatorialData]
public async Task Class_FromMethodDocumentOfNestedClass(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|Outer|]
{
public class C
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer"));
}
[Theory, CombinatorialData]
public async Task Constructor_FromMethodDocumentOfNestedClass(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|Outer|]
{
public class C
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("Outer..ctor"));
}
[Theory, CombinatorialData]
public async Task Class_FromMethodDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|C|]
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C"));
}
[Theory, CombinatorialData]
public async Task Constructor_FromMethodDocument(Location pdbLocation, Location sourceLocation)
{
var source = """
public class [|C|]
{
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C..ctor"));
}
[Theory, CombinatorialData]
public async Task Field(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public int [|f|];
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.f"));
}
[Theory, CombinatorialData]
public async Task Property(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public int [|P|] { get; set; }
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.P"));
}
[Theory, CombinatorialData]
public async Task Property_WithBody(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public int [|P|] { get { return 1; } }
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.P"));
}
[Theory, CombinatorialData]
public async Task EventField(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public event System.EventHandler [|E|];
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.E"));
}
[Theory, CombinatorialData]
public async Task EventField_WithMethod(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public event System.EventHandler [|E|];
public void M()
{
// this is a comment that wouldn't appear in decompiled source
}
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.E"));
}
[Theory, CombinatorialData]
public async Task Event(Location pdbLocation, Location sourceLocation)
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await TestAsync(pdbLocation, sourceLocation, source, c => c.GetMember("C.E"));
}
[Fact]
public async Task ReferenceAssembly_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
// A pdb won't be emitted when building a reference assembly so the first two parameters don't actually matter
await TestAsync(Location.OnDisk, Location.OnDisk, source, c => c.GetMember("C.E"), buildReferenceAssembly: true, expectNullResult: true);
}
[Fact]
public async Task NugetPackageLayout()
{
var source = """
public class C
{
// A change
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
// Laziest. Nuget package directory layout. Ever.
Directory.CreateDirectory(Path.Combine(path, "ref"));
Directory.CreateDirectory(Path.Combine(path, "lib"));
// Compile reference assembly
var sourceText = SourceText.From(metadataSource, encoding: Encoding.UTF8);
var (project, symbol) = await CompileAndFindSymbolAsync(Path.Combine(path, "ref"), Location.Embedded, Location.OnDisk, sourceText, c => c.GetMember("C.E"), buildReferenceAssembly: true);
// Compile implementation assembly
CompileTestSource(Path.Combine(path, "lib"), sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, metadataSource.ToString(), expectedSpan, expectNullResult: false);
});
}
[Fact]
public async Task Net6SdkLayout()
{
var source = """
public class C
{
// A change
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var packDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "ref", "net6.0")).FullName;
var dataDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "data")).FullName;
var sharedDir = Directory.CreateDirectory(Path.Combine(path, "shared", "MyPack", "1.0")).FullName;
// Compile reference assembly
var sourceText = SourceText.From(metadataSource, encoding: Encoding.UTF8);
var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C.E"), buildReferenceAssembly: true);
// Compile implementation assembly
CompileTestSource(sharedDir, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
// Create FrameworkList.xml
File.WriteAllText(Path.Combine(dataDir, "FrameworkList.xml"), """
<FileList FrameworkName="MyPack">
</FileList>
""");
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, metadataSource.ToString(), expectedSpan, expectNullResult: false);
});
}
[Fact]
public async Task Net6SdkLayout_WithOtherReferences()
{
var source = """
public class C
{
public void [|M|](string d)
{
}
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var packDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "ref", "net6.0")).FullName;
var dataDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "data")).FullName;
var sharedDir = Directory.CreateDirectory(Path.Combine(path, "shared", "MyPack", "1.0")).FullName;
var sourceText = SourceText.From(metadataSource, Encoding.UTF8);
var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C.M"), buildReferenceAssembly: true);
var workspace = EditorTestWorkspace.Create(@$"
<Workspace>
<Project Language=""{LanguageNames.CSharp}"" CommonReferences=""true"" ReferencesOnDisk=""true"">
</Project>
</Workspace>", composition: GetTestComposition());
var implProject = workspace.CurrentSolution.Projects.First();
// Compile implementation assembly
CompileTestSource(sharedDir, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
// Create FrameworkList.xml
File.WriteAllText(Path.Combine(dataDir, "FrameworkList.xml"), """
<FileList FrameworkName="MyPack">
</FileList>
""");
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, metadataSource.ToString(), expectedSpan, expectNullResult: false);
});
}
[Fact]
public async Task Net6SdkLayout_TypeForward()
{
var source = """
public class [|C|]
{
public void M(string d)
{
}
}
""";
var typeForwardSource = """
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(C))]
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var packDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "ref", "net6.0")).FullName;
var dataDir = Directory.CreateDirectory(Path.Combine(path, "packs", "MyPack.Ref", "1.0", "data")).FullName;
var sharedDir = Directory.CreateDirectory(Path.Combine(path, "shared", "MyPack", "1.0")).FullName;
var sourceText = SourceText.From(metadataSource, Encoding.UTF8);
var (project, symbol) = await CompileAndFindSymbolAsync(packDir, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C"), buildReferenceAssembly: true);
var workspace = EditorTestWorkspace.Create(@$"
<Workspace>
<Project Language=""{LanguageNames.CSharp}"" CommonReferences=""true"" ReferencesOnDisk=""true"">
</Project>
</Workspace>", composition: GetTestComposition());
var implProject = workspace.CurrentSolution.Projects.First();
// Compile implementation assembly
var implementationDllFilePath = Path.Combine(sharedDir, "implementation.dll");
var sourceCodePath = Path.Combine(sharedDir, "implementation.cs");
var pdbFilePath = Path.Combine(sharedDir, "implementation.pdb");
var assemblyName = "implementation";
CompileTestSource(implementationDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
// Compile type forwarding implementation DLL, that looks like reference.dll
var typeForwardDllFilePath = Path.Combine(sharedDir, "reference.dll");
sourceCodePath = Path.Combine(sharedDir, "reference.cs");
pdbFilePath = Path.Combine(sharedDir, "reference.pdb");
assemblyName = "reference";
implProject = implProject.AddMetadataReference(MetadataReference.CreateFromFile(implementationDllFilePath));
sourceText = SourceText.From(typeForwardSource, Encoding.UTF8);
CompileTestSource(typeForwardDllFilePath, sourceCodePath, pdbFilePath, assemblyName, sourceText, implProject, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
// Create FrameworkList.xml
File.WriteAllText(Path.Combine(dataDir, "FrameworkList.xml"), """
<FileList FrameworkName="MyPack">
</FileList>
""");
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, metadataSource.ToString(), expectedSpan, expectNullResult: false);
});
}
[Fact]
public async Task NoPdb_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// Now delete the PDB
File.Delete(GetPdbPath(path));
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task NoDll_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// Now delete the DLL
File.Delete(GetDllPath(path));
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task NoSource_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// Now delete the source
File.Delete(GetSourceFilePath(path));
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task WindowsPdb_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"), windowsPdb: true);
//TODO: This should not be a null result: https://github.com/dotnet/roslyn/issues/55834
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task EmptyPdb_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// Now make the PDB a zero byte file
File.WriteAllBytes(GetPdbPath(path), new byte[0]);
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task CorruptPdb_NullResult()
{
var source = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// The first four bytes of this are BSJB so it is identified as a portable PDB.
// The next two bytes are unimportant, they're just not valid PDB data.
var corruptPdb = new byte[] { 66, 83, 74, 66, 68, 87 };
File.WriteAllBytes(GetPdbPath(path), corruptPdb);
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source, expectedSpan, expectNullResult: true);
});
}
[Fact]
public async Task OldPdb_NullResult()
{
var source1 = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
var source2 = """
public class C
{
// A change
public event System.EventHandler E { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source1, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.OnDisk, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
// Archive off the current PDB so we can restore it later
var pdbFilePath = GetPdbPath(path);
var archivePdbFilePath = pdbFilePath + ".old";
File.Move(pdbFilePath, archivePdbFilePath);
CompileTestSource(path, SourceText.From(source2, Encoding.UTF8), project, Location.OnDisk, Location.OnDisk, buildReferenceAssembly: false, windowsPdb: false);
// Move the old file back, so the PDB is now old
File.Delete(pdbFilePath);
File.Move(archivePdbFilePath, pdbFilePath);
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, source1, expectedSpan, expectNullResult: true);
});
}
[Theory, CombinatorialData]
public async Task SourceFileChecksumIncorrect_NullResult(Location pdbLocation)
{
var source1 = """
public class C
{
public event System.EventHandler [|E|] { add { } remove { } }
}
""";
var source2 = """
public class C
{
// A change
public event System.EventHandler E { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source1, out var metadataSource, out var expectedSpan);
var (project, symbol) = await CompileAndFindSymbolAsync(path, pdbLocation, Location.OnDisk, metadataSource, c => c.GetMember("C.E"));
File.WriteAllText(GetSourceFilePath(path), source2, Encoding.UTF8);
await GenerateFileAndVerifyAsync(project, symbol, Location.OnDisk, metadataSource, expectedSpan, expectNullResult: true);
});
}
[Theory]
[InlineData(Location.Embedded, "utf-16")]
[InlineData(Location.Embedded, "utf-16BE")]
[InlineData(Location.Embedded, "utf-32")]
[InlineData(Location.Embedded, "utf-32BE")]
[InlineData(Location.Embedded, "us-ascii")]
[InlineData(Location.Embedded, "iso-8859-1")]
[InlineData(Location.Embedded, "utf-8")]
[InlineData(Location.OnDisk, "utf-16")]
[InlineData(Location.OnDisk, "utf-16BE")]
[InlineData(Location.OnDisk, "utf-32")]
[InlineData(Location.OnDisk, "utf-32BE")]
[InlineData(Location.OnDisk, "us-ascii")]
[InlineData(Location.OnDisk, "iso-8859-1")]
[InlineData(Location.OnDisk, "utf-8")]
public async Task EncodedEmbeddedSource(Location pdbLocation, string encodingWebName)
{
var source = """
public class C
{
public event System.EventHandler E { add { } remove { } }
}
""";
var encoding = Encoding.GetEncoding(encodingWebName);
await RunTestAsync(async path =>
{
using var ms = new MemoryStream(encoding.GetBytes(source));
var encodedSourceText = EncodedStringText.Create(ms, encoding, canBeEmbedded: true);
var (project, symbol) = await CompileAndFindSymbolAsync(path, pdbLocation, Location.Embedded, encodedSourceText, c => c.GetMember("C.E"));
var (actualText, _) = await GetGeneratedSourceTextAsync(project, symbol, Location.Embedded, expectNullResult: false);
AssertEx.NotNull(actualText);
AssertEx.NotNull(actualText.Encoding);
AssertEx.Equal(encoding.WebName, actualText.Encoding.WebName);
AssertEx.EqualOrDiff(source, actualText.ToString());
});
}
[Theory, CombinatorialData]
public async Task EncodedEmbeddedSource_SJIS(Location pdbLocation)
{
var source = """
public class C
{
// ワ
public event System.EventHandler E { add { } remove { } }
}
""";
var encoding = Encoding.GetEncoding("SJIS");
await RunTestAsync(async path =>
{
using var ms = new MemoryStream(encoding.GetBytes(source));
var encodedSourceText = EncodedStringText.Create(ms, encoding, canBeEmbedded: true);
var (project, symbol) = await CompileAndFindSymbolAsync(path, pdbLocation, Location.Embedded, encodedSourceText, c => c.GetMember("C.E"));
var (actualText, _) = await GetGeneratedSourceTextAsync(project, symbol, Location.Embedded, expectNullResult: false);
AssertEx.NotNull(actualText);
AssertEx.NotNull(actualText.Encoding);
AssertEx.Equal(encoding.WebName, actualText.Encoding.WebName);
AssertEx.EqualOrDiff(source, actualText.ToString());
});
}
[Theory, CombinatorialData]
public async Task EncodedEmbeddedSource_SJIS_FallbackEncoding(Location pdbLocation)
{
var source = """
public class C
{
// ワ
public event System.EventHandler E { add { } remove { } }
}
""";
var encoding = Encoding.GetEncoding("SJIS");
await RunTestAsync(async path =>
{
using var ms = new MemoryStream(encoding.GetBytes(source));
var encodedSourceText = EncodedStringText.Create(ms, encoding, canBeEmbedded: true);
var (project, symbol) = await CompileAndFindSymbolAsync(path, pdbLocation, Location.Embedded, encodedSourceText, c => c.GetMember("C.E"), fallbackEncoding: encoding);
var (actualText, _) = await GetGeneratedSourceTextAsync(project, symbol, Location.Embedded, expectNullResult: false);
AssertEx.NotNull(actualText);
AssertEx.NotNull(actualText.Encoding);
AssertEx.Equal(encoding.WebName, actualText.Encoding.WebName);
AssertEx.EqualOrDiff(source, actualText.ToString());
});
}
[Fact]
public async Task OptionTurnedOff_NullResult()
{
var source = """
public class C
{
public event System.EventHandler E { add { } remove { } }
}
""";
await RunTestAsync(async path =>
{
var sourceText = SourceText.From(source, Encoding.UTF8);
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, sourceText, c => c.GetMember("C.E"));
using var workspace = (EditorTestWorkspace)project.Solution.Workspace;
var service = workspace.GetService<IMetadataAsSourceFileService>();
try
{
var options = MetadataAsSourceOptions.Default with
{
NavigateToSourceLinkAndEmbeddedSources = false
};
var file = await service.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: false, options: options, cancellationToken: CancellationToken.None).ConfigureAwait(false);
Assert.Same(NullResultMetadataAsSourceFileProvider.NullResult, file);
}
finally
{
service.TryGetWorkspace()?.Dispose();
}
});
}
[Fact]
public async Task MethodInPartialType_NavigateToCorrectFile()
{
var source1 = """
public partial class C
{
public void M1()
{
}
}
""";
var source2 = """
using System.Threading.Tasks;
public partial class C
{
public static async Task [|M2|]() => await M3();
private static async Task M3()
{
}
}
""";
await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source2, out source2, out var expectedSpan);
var sourceText1 = SourceText.From(source1, Encoding.UTF8);
var sourceText2 = SourceText.From(source2, Encoding.UTF8);
var workspace = EditorTestWorkspace.Create(@$"
<Workspace>
<Project Language=""{LanguageNames.CSharp}"" CommonReferences=""true"" ReferencesOnDisk=""true"">
</Project>
</Workspace>", composition: GetTestComposition());
var project = workspace.CurrentSolution.Projects.First();
var dllFilePath = GetDllPath(path);
var sourceCodePath = GetSourceFilePath(path);
var pdbFilePath = GetPdbPath(path);
CompileTestSource(dllFilePath, [Path.Combine(path, "source1.cs"), Path.Combine(path, "source2.cs")], pdbFilePath, "reference", [sourceText1, sourceText2], project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
project = project.AddMetadataReference(MetadataReference.CreateFromFile(GetDllPath(path)));
var mainCompilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false);
var symbol = mainCompilation.GetMember("C.M2");
AssertEx.NotNull(symbol, $"Couldn't find symbol to go-to-def for.");
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, source2.ToString(), expectedSpan, expectNullResult: false);
});
}
[Fact, WorkItem("https://github.com/dotnet/vscode-csharp/issues/7532")]
public async Task OpenFileWithDifferentCase()
{
var source = """
public class C
{
public int P { get; set; }
}
""";
await RunTestAsync(async path =>
{
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, source, c => c.GetMember("C.P"));
using var workspace = (EditorTestWorkspace)project.Solution.Workspace;
var service = workspace.GetService<IMetadataAsSourceFileService>();
var file = await service.GetGeneratedFileAsync(project.Solution.Workspace, project, symbol, signaturesOnly: false, options: MetadataAsSourceOptions.Default, cancellationToken: CancellationToken.None);
var requestPath = file.FilePath.ToUpperInvariant();
var result = service.TryAddDocumentToWorkspace(requestPath, new StaticSourceTextContainer(SourceText.From(string.Empty)), out var documentId);
Assert.True(result);
});
}
[Fact]
public async Task OpenThenClose()
{
var source = """
public class C
{
public int P { get; set; }
}
""";
await RunTestAsync(async path =>
{
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, source, c => c.GetMember("C.P"));
using var workspace = (EditorTestWorkspace)project.Solution.Workspace;
var service = workspace.GetService<IMetadataAsSourceFileService>();
var file = await service.GetGeneratedFileAsync(project.Solution.Workspace, project, symbol, signaturesOnly: false, options: MetadataAsSourceOptions.Default, cancellationToken: CancellationToken.None);
var openResult = service.TryAddDocumentToWorkspace(file.FilePath, new StaticSourceTextContainer(SourceText.From(string.Empty)), out var documentId);
Assert.True(openResult);
var closeResult = service.TryRemoveDocumentFromWorkspace(file.FilePath);
Assert.True(closeResult);
});
}
[Fact, WorkItem("https://github.com/dotnet/vscode-csharp/issues/7514")]
public async Task CloseWithoutOpenDoesNotThrow()
{
var source = """
public class C
{
public int P { get; set; }
}
""";
await RunTestAsync(async path =>
{
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, source, c => c.GetMember("C.P"));
using var workspace = (EditorTestWorkspace)project.Solution.Workspace;
var service = workspace.GetService<IMetadataAsSourceFileService>();
var file = await service.GetGeneratedFileAsync(project.Solution.Workspace, project, symbol, signaturesOnly: false, options: MetadataAsSourceOptions.Default, cancellationToken: CancellationToken.None);
var result = service.TryRemoveDocumentFromWorkspace(file.FilePath);
Assert.False(result);
});
}
[Fact, WorkItem("https://github.com/dotnet/vscode-csharp/issues/7514")]
public async Task OpenSameDocument()
{
var source = """
public class C
{
public int P1 { get; set; }
public int P2 { get; set; }
}
""";
await RunTestAsync(async path =>
{
var (project, symbol) = await CompileAndFindSymbolAsync(path, Location.Embedded, Location.Embedded, source, c => c.GetMember("C.P1"));
using var workspace = (EditorTestWorkspace)project.Solution.Workspace;
var service = workspace.GetService<IMetadataAsSourceFileService>();
var fileOne = await service.GetGeneratedFileAsync(project.Solution.Workspace, project, symbol, signaturesOnly: false, options: MetadataAsSourceOptions.Default, cancellationToken: CancellationToken.None);
var openResult = service.TryAddDocumentToWorkspace(fileOne.FilePath, new StaticSourceTextContainer(SourceText.From(string.Empty)), out var documentId);
Assert.True(openResult);
var compilation = await project.GetCompilationAsync(CancellationToken.None);
var symbolTwo = compilation.GetMember("C.P2");
var fileTwo = await service.GetGeneratedFileAsync(project.Solution.Workspace, project, symbolTwo, signaturesOnly: false, MetadataAsSourceOptions.Default, CancellationToken.None);
Assert.Equal(fileOne.FilePath, fileTwo.FilePath);
Assert.NotEqual(fileOne.IdentifierLocation, fileTwo.IdentifierLocation);
// Opening should still throw (should never be called as we should be able to find the previously
// opened document in the MAS workspace).
Assert.Throws<System.InvalidOperationException>(() => service.TryAddDocumentToWorkspace(fileTwo.FilePath, new StaticSourceTextContainer(SourceText.From(string.Empty)), out var documentIdTwo));
});
}
}
|