Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.
/ NuGet.Jobs Public archive

Commit

Permalink
FailValidationWithMixedSymbols (#656)
Browse files Browse the repository at this point in the history
* Fix mixed(failing and passing) symbols validation.

* Feedback - add better telemetry.
  • Loading branch information
cristinamanum authored Nov 7, 2018
1 parent a627b4d commit 68eaabd
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 74 deletions.
13 changes: 11 additions & 2 deletions src/Validation.Symbols/ITelemetryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@ public interface ITelemetryService : ISubscriptionProcessorTelemetryService
IDisposable TrackSymbolValidationDurationEvent(string packageId, string packageNormalizedVersion, int symbolCount);

/// <summary>
/// Tracks the status of the validation.
/// Tracks the status of the validation per assembly.
/// </summary>
/// <param name="packageId">The package id.</param>
/// <param name="packageNormalizedVersion">The package normalized version.</param>
/// <param name="validationStatus">The validation result.</param>
/// <param name="issue">Information about the issue id failed or empty if passed..</param>
void TrackSymbolsValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus, string issue);
/// <param name="assemblyName">The assembly name.</param>
void TrackSymbolsAssemblyValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus, string issue, string assemblyName);

/// <summary>
/// Tracks the status of the validation per package.
/// </summary>
/// <param name="packageId">The package id.</param>
/// <param name="packageNormalizedVersion">The package normalized version.</param>
/// <param name="validationStatus">The validation result.</param>
void TrackSymbolsValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus);
}
}
144 changes: 78 additions & 66 deletions src/Validation.Symbols/SymbolsValidatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class SymbolsValidatorService : ISymbolsValidatorService
{
private static TimeSpan _cleanWorkingDirectoryTimeSpan = TimeSpan.FromSeconds(20);
private static readonly string[] PEExtensionsPatterns = new string[] { "*.dll", "*.exe" };
private static readonly string SymbolExtensionPattern = "*.pdb";
private static readonly string[] PEExtensions = new string[] { ".dll", ".exe" };
private static readonly string[] SymbolExtension = new string[] { ".pdb" };

Expand Down Expand Up @@ -62,9 +61,14 @@ public async Task<IValidationResult> ValidateSymbolsAsync(SymbolsValidatorMessag

using (_telemetryService.TrackSymbolValidationDurationEvent(message.PackageId, message.PackageNormalizedVersion, pdbs.Count))
{
if (!SymbolsHaveMatchingPEFiles(pdbs, pes))
List<string> orphanSymbolFiles;
if (!SymbolsHaveMatchingPEFiles(pdbs, pes, out orphanSymbolFiles))
{
_telemetryService.TrackSymbolsValidationResultEvent(message.PackageId, message.PackageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound));
orphanSymbolFiles.ForEach((symbol) =>
{
_telemetryService.TrackSymbolsAssemblyValidationResultEvent(message.PackageId, message.PackageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound), assemblyName: symbol);
});
_telemetryService.TrackSymbolsValidationResultEvent(message.PackageId, message.PackageNormalizedVersion, ValidationStatus.Failed);
return ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound);
}
var targetDirectory = Settings.GetWorkingDirectory();
Expand Down Expand Up @@ -151,79 +155,85 @@ public virtual IValidationResult ValidateSymbolMatching(string targetDirectory,
{
foreach (string peFile in Directory.GetFiles(targetDirectory, extension, SearchOption.AllDirectories))
{
using (var peStream = File.OpenRead(peFile))
using (var peReader = new PEReader(peStream))
IValidationResult validationResult;
if (!IsChecksumMatch(peFile, packageId, packageNormalizedVersion, out validationResult))
{
// This checks if portable PDB is associated with the PE file and opens it for reading.
// It also validates that it matches the PE file.
// It does not validate that the checksum matches, so we need to do that in the following block.
if (peReader.TryOpenAssociatedPortablePdb(peFile, File.OpenRead, out var pdbReaderProvider, out var pdbPath) &&
// No need to validate embedded PDB (pdbPath == null for embedded)
pdbPath != null)
{
// Get all checksum entries. There can be more than one. At least one must match the PDB.
var checksumRecords = peReader.ReadDebugDirectory().Where(entry => entry.Type == DebugDirectoryEntryType.PdbChecksum)
.Select(e => peReader.ReadPdbChecksumDebugDirectoryData(e))
.ToArray();
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed);
return validationResult;
}
}
}
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Succeeded);
return ValidationResult.Succeeded;
}

if (checksumRecords.Length == 0)
{
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch));
return ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch);
}
private bool IsChecksumMatch(string peFilePath, string packageId, string packageNormalizedVersion, out IValidationResult validationResult)
{
validationResult = ValidationResult.Succeeded;
using (var peStream = File.OpenRead(peFilePath))
using (var peReader = new PEReader(peStream))
{
// This checks if portable PDB is associated with the PE file and opens it for reading.
// It also validates that it matches the PE file.
// It does not validate that the checksum matches, so we need to do that in the following block.
if (peReader.TryOpenAssociatedPortablePdb(peFilePath, File.OpenRead, out var pdbReaderProvider, out var pdbPath) &&
// No need to validate embedded PDB (pdbPath == null for embedded)
pdbPath != null)
{
// Get all checksum entries. There can be more than one. At least one must match the PDB.
var checksumRecords = peReader.ReadDebugDirectory().Where(entry => entry.Type == DebugDirectoryEntryType.PdbChecksum)
.Select(e => peReader.ReadPdbChecksumDebugDirectoryData(e))
.ToArray();

var pdbBytes = File.ReadAllBytes(pdbPath);
var hashes = new Dictionary<string, byte[]>();
if (checksumRecords.Length == 0)
{
_telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch), assemblyName: Path.GetFileName(peFilePath));
validationResult = ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch);
return false;
}

using (pdbReaderProvider)
{
var pdbReader = pdbReaderProvider.GetMetadataReader();
int idOffset = pdbReader.DebugMetadataHeader.IdStartOffset;
var pdbBytes = File.ReadAllBytes(pdbPath);
var hashes = new Dictionary<string, byte[]>();

using (pdbReaderProvider)
{
var pdbReader = pdbReaderProvider.GetMetadataReader();
int idOffset = pdbReader.DebugMetadataHeader.IdStartOffset;

foreach (var checksumRecord in checksumRecords)
foreach (var checksumRecord in checksumRecords)
{
if (!hashes.TryGetValue(checksumRecord.AlgorithmName, out var hash))
{
HashAlgorithmName han = new HashAlgorithmName(checksumRecord.AlgorithmName);
using (var hashAlg = IncrementalHash.CreateHash(han))
{
if (!hashes.TryGetValue(checksumRecord.AlgorithmName, out var hash))
{
HashAlgorithmName han = new HashAlgorithmName(checksumRecord.AlgorithmName);
using (var hashAlg = IncrementalHash.CreateHash(han))
{
hashAlg.AppendData(pdbBytes, 0, idOffset);
hashAlg.AppendData(new byte[20]);
int offset = idOffset + 20;
int count = pdbBytes.Length - offset;
hashAlg.AppendData(pdbBytes, offset, count);
hash = hashAlg.GetHashAndReset();
}
hashes.Add(checksumRecord.AlgorithmName, hash);
}
if (checksumRecord.Checksum.ToArray().SequenceEqual(hash))
{
// found the right checksum
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Succeeded, "");
return ValidationResult.Succeeded;
}
hashAlg.AppendData(pdbBytes, 0, idOffset);
hashAlg.AppendData(new byte[20]);
int offset = idOffset + 20;
int count = pdbBytes.Length - offset;
hashAlg.AppendData(pdbBytes, offset, count);
hash = hashAlg.GetHashAndReset();
}

// Not found any checksum record that matches the PDB.
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch));
return ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch);
hashes.Add(checksumRecord.AlgorithmName, hash);
}
if (checksumRecord.Checksum.ToArray().SequenceEqual(hash))
{
// found the right checksum
_telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Succeeded, issue:"", assemblyName:Path.GetFileName(peFilePath));
return true;
}
}

// Not found any checksum record that matches the PDB.
_telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch), assemblyName: Path.GetFileName(peFilePath));
validationResult = ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_ChecksumDoesNotMatch);
return false;
}
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound));
return ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound);
}
}
// If did not return there were not any PE files to validate. In this case return error to not proceeed with an ingestion.
_logger.LogError("{ValidatorName}: There were not any dll or exe files found locally." +
"This could indicate an issue in the execution or the package was not correct created. PackageId {PackageId} PackageNormalizedVersion {PackageNormalizedVersion}. " +
"SymbolCount: {SymbolCount}",
ValidatorName.SymbolsValidator,
packageId,
packageNormalizedVersion,
Directory.GetFiles(targetDirectory, SymbolExtensionPattern, SearchOption.AllDirectories));
_telemetryService.TrackSymbolsValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound));
return ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound);
_telemetryService.TrackSymbolsAssemblyValidationResultEvent(packageId, packageNormalizedVersion, ValidationStatus.Failed, nameof(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound), assemblyName: Path.GetFileName(peFilePath));
validationResult = ValidationResult.FailedWithIssues(ValidationIssue.SymbolErrorCode_MatchingPortablePDBNotFound);
return false;
}

/// <summary>
Expand All @@ -232,7 +242,7 @@ public virtual IValidationResult ValidateSymbolMatching(string targetDirectory,
/// <param name="symbols">Symbol list extracted from the compressed folder.</param>
/// <param name="PEs">The list of PE files extracted from the compressed folder.</param>
/// <returns></returns>
public static bool SymbolsHaveMatchingPEFiles(IEnumerable<string> symbols, IEnumerable<string> PEs)
public static bool SymbolsHaveMatchingPEFiles(IEnumerable<string> symbols, IEnumerable<string> PEs, out List<string> orphanSymbolFiles)
{
if(symbols == null)
{
Expand All @@ -244,7 +254,9 @@ public static bool SymbolsHaveMatchingPEFiles(IEnumerable<string> symbols, IEnum
}
var symbolsWithoutExtension = ZipArchiveService.RemoveExtension(symbols);
var PEsWithoutExtensions = ZipArchiveService.RemoveExtension(PEs);
return !symbolsWithoutExtension.Except(PEsWithoutExtensions, StringComparer.OrdinalIgnoreCase).Any();
orphanSymbolFiles = symbolsWithoutExtension.Except(PEsWithoutExtensions, StringComparer.OrdinalIgnoreCase).ToList();

return !orphanSymbolFiles.Any();
}
}
}
20 changes: 18 additions & 2 deletions src/Validation.Symbols/TelemetryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public class TelemetryService : ITelemetryService
private const string MessageDeliveryLag = Prefix + "MessageDeliveryLag";
private const string MessageEnqueueLag = Prefix + "MessageEnqueueLag";
private const string SymbolValidationResult = Prefix + "SymbolValidationResult";
private const string SymbolAssemblyValidationResult = Prefix + "SymbolAssemblyValidationResult";

private const string PackageId = "PackageId";
private const string PackageNormalizedVersion = "PackageNormalizedVersion";
private const string MessageType = "MessageType";
private const string SymbolCount = "SymbolCount";
private const string ValidationResult = "ValidationResult";
private const string Issue = "Issue";
private const string AssemblyName = "AssemblyName";

private readonly ITelemetryClient _telemetryClient;

Expand Down Expand Up @@ -68,16 +70,30 @@ public IDisposable TrackSymbolValidationDurationEvent(string packageId, string p
});
}

public void TrackSymbolsValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus, string issue)
public void TrackSymbolsAssemblyValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus, string issue, string assemblyName)
{
_telemetryClient.TrackMetric(
SymbolValidationResult,
SymbolAssemblyValidationResult,
1,
new Dictionary<string, string>
{
{ ValidationResult, validationStatus.ToString() },
{ Issue, issue },
{ PackageId, packageId },
{ PackageNormalizedVersion, packageNormalizedVersion },
{ AssemblyName, assemblyName }
});
}

public void TrackSymbolsValidationResultEvent(string packageId, string packageNormalizedVersion, ValidationStatus validationStatus)
{
_telemetryClient.TrackMetric(
SymbolValidationResult,
1,
new Dictionary<string, string>
{
{ ValidationResult, validationStatus.ToString() },
{ PackageId, packageId },
{ PackageNormalizedVersion, packageNormalizedVersion }
});
}
Expand Down
Loading

0 comments on commit 68eaabd

Please sign in to comment.