Add SDK Symbols tests (#19528)

Co-authored-by: Viktor Hofer <viktor.hofer@microsoft.com>
This commit is contained in:
Nikola Milosavljevic 2024-04-21 06:43:48 -07:00 committed by GitHub
parent bf738fd99f
commit 94d9faf698
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 162 additions and 37 deletions

View file

@ -78,48 +78,23 @@ namespace Microsoft.DotNet.UnifiedBuild.Tasks
foreach (string file in Directory.GetFiles(SdkLayoutPath, "*", SearchOption.AllDirectories))
{
if (file.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) &&
!file.EndsWith(".resources.dll", StringComparison.InvariantCultureIgnoreCase))
if (PdbUtilities.FileInSdkLayoutRequiresAPdb(file, out string guid))
{
string guid = string.Empty;
using var pdbStream = File.OpenRead(file);
using var peReader = new PEReader(pdbStream);
try
string debugId = GetDebugId(guid, file);
if (!allPdbGuids.ContainsKey(debugId))
{
// Check if pdb is embedded
if (peReader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb))
{
continue;
}
var debugDirectory = peReader.ReadDebugDirectory().First(entry => entry.Type == DebugDirectoryEntryType.CodeView);
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(debugDirectory);
guid = $"{codeViewData.Guid.ToString("N").Replace("-", string.Empty)}";
filesWithoutPDBs.Add(file.Substring(SdkLayoutPath.Length + 1));
}
catch (Exception e) when (e is BadImageFormatException || e is InvalidOperationException)
else
{
// Ignore binaries without debug info
continue;
}
// Copy matching pdb to symbols path, preserving sdk binary's hierarchy
string sourcePath = (string)allPdbGuids[debugId]!;
string destinationPath =
file.Replace(SdkLayoutPath, SdkSymbolsLayoutPath)
.Replace(Path.GetFileName(file), Path.GetFileName(sourcePath));
if (guid != string.Empty)
{
string debugId = GetDebugId(guid, file);
if (!allPdbGuids.ContainsKey(debugId))
{
filesWithoutPDBs.Add(file.Substring(SdkLayoutPath.Length + 1));
}
else
{
// Copy matching pdb to symbols path, preserving sdk binary's hierarchy
string sourcePath = (string)allPdbGuids[debugId]!;
string destinationPath =
file.Replace(SdkLayoutPath, SdkSymbolsLayoutPath)
.Replace(Path.GetFileName(file), Path.GetFileName(sourcePath));
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
File.Copy(sourcePath, destinationPath, true);
}
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
File.Copy(sourcePath, destinationPath, true);
}
}
}

View file

@ -0,0 +1,60 @@
// 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.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.DotNet.UnifiedBuild.Tasks
{
public static class PdbUtilities
{
// Checks if a file in Sdk layout requires an external Pdb.
// Also returns the Pdb GUID, if one was found in PE.
public static bool FileInSdkLayoutRequiresAPdb(string file, out string guid)
{
guid = string.Empty;
// Files under packs/ are used for build only, no need for Pdbs
return !file.Contains(Path.DirectorySeparatorChar + "packs" + Path.DirectorySeparatorChar) ?
FileHasCompanionPdbInfo(file, out guid) :
false;
}
// Checks if a file has debug data indicating an external companion Pdb.
// Also returns the Pdb GUID, if one was found in PE.
private static bool FileHasCompanionPdbInfo(string file, out string guid)
{
guid = string.Empty;
if (file.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) &&
!file.EndsWith(".resources.dll", StringComparison.InvariantCultureIgnoreCase))
{
using var pdbStream = File.OpenRead(file);
using var peReader = new PEReader(pdbStream);
try
{
// Check if pdb is embedded
if (peReader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb))
{
return false;
}
var debugDirectory = peReader.ReadDebugDirectory().First(entry => entry.Type == DebugDirectoryEntryType.CodeView);
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(debugDirectory);
guid = $"{codeViewData.Guid.ToString("N").Replace("-", string.Empty)}";
}
catch (Exception e) when (e is BadImageFormatException || e is InvalidOperationException)
{
// Ignore binaries without debug info
return false;
}
}
return guid != string.Empty;
}
}
}

View file

@ -12,6 +12,10 @@
<VSTestUseMSBuildOutput>false</VSTestUseMSBuildOutput>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(TasksDir)Microsoft.DotNet.UnifiedBuild.Tasks\PdbUtilities.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TestUtilities\TestUtilities.csproj" />
</ItemGroup>

View file

@ -0,0 +1,86 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using Microsoft.DotNet.UnifiedBuild.Tasks;
namespace Microsoft.DotNet.SourceBuild.SmokeTests;
public class SymbolsTests : SdkTests
{
private static string SymbolsTestsRoot { get; } = Path.Combine(Directory.GetCurrentDirectory(), nameof(SymbolsTests));
public SymbolsTests(ITestOutputHelper outputHelper) : base(outputHelper) { }
/// <summary>
/// Verifies that all symbols have valid sourcelinks.
/// </summary>
[Fact]
public void VerifySdkSymbols()
{
try
{
if (Directory.Exists(SymbolsTestsRoot))
{
Directory.Delete(SymbolsTestsRoot, true);
}
Directory.CreateDirectory(SymbolsTestsRoot);
string symbolsRoot = Directory.CreateDirectory(Path.Combine(SymbolsTestsRoot, "symbols")).FullName;
// We are validating dotnet-symbols-sdk-*.tar.gz which contains source-built sdk symbols
Utilities.ExtractTarball(
Utilities.GetFile(Path.GetDirectoryName(Config.SourceBuiltArtifactsPath)!, "dotnet-symbols-sdk-*.tar.gz"),
symbolsRoot,
OutputHelper);
IList<string> failedFiles = VerifySdkFilesHaveMatchingSymbols(symbolsRoot, Config.DotNetDirectory);
if (failedFiles.Count > 0)
{
OutputHelper.WriteLine($"Did not find PDBs for the following SDK files:");
foreach (string file in failedFiles)
{
OutputHelper.WriteLine(file);
}
}
Assert.True(failedFiles.Count == 0);
}
finally
{
Directory.Delete(SymbolsTestsRoot, true);
}
}
private IList<string> VerifySdkFilesHaveMatchingSymbols(string symbolsRoot, string sdkRoot)
{
Assert.True(Directory.Exists(sdkRoot), $"Path, with SDK files to validate, does not exist: {sdkRoot}");
var failedFiles = new ConcurrentBag<string>();
IEnumerable<string> allFiles = Directory.GetFiles(sdkRoot, "*", SearchOption.AllDirectories);
Parallel.ForEach(allFiles, file =>
{
if (PdbUtilities.FileInSdkLayoutRequiresAPdb(file, out string guid))
{
string symbolFile = Path.ChangeExtension(file.Replace(sdkRoot, symbolsRoot), ".pdb");
if (!File.Exists(symbolFile))
{
failedFiles.Add(file);
}
}
});
return failedFiles.ToList();
}
}