Skip to content

Commit

Permalink
Add SDK Symbols tests (#19528)
Browse files Browse the repository at this point in the history
Co-authored-by: Viktor Hofer <viktor.hofer@microsoft.com>
  • Loading branch information
NikolaMilosavljevic and ViktorHofer committed Apr 21, 2024
1 parent bf738fd commit 94d9faf
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 38 deletions.
Expand Up @@ -78,48 +78,23 @@ private IList<string> GenerateSymbolsLayout(Hashtable allPdbGuids)

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;
}

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);
}
// 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);
}
}
}
Expand Down
@@ -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;
}
}
}
Expand Up @@ -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>
Expand Down
@@ -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();
}
}

0 comments on commit 94d9faf

Please sign in to comment.