Skip to content

Commit

Permalink
Add deprecation info decorator, add dgml visualization tests (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkolev92 authored Sep 12, 2023
1 parent a7cbfd8 commit e947bae
Show file tree
Hide file tree
Showing 18 changed files with 280 additions and 46 deletions.
15 changes: 2 additions & 13 deletions src/Common/DependencyNodeIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,11 @@ public class DependencyNodeIdentity : PackageIdentity

public bool Vulnerable { get; set; }

public bool Deprecated { get; set; }

public DependencyNodeIdentity(string id, NuGetVersion version, DependencyType type) : base(id, version)
{
Type = type;
}

public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(), Type, Vulnerable);
}

public override bool Equals(object? obj)
{
return obj is DependencyNodeIdentity identity &&
base.Equals(obj) &&
Type == identity.Type &&
Vulnerable == identity.Vulnerable;
}
}
}
106 changes: 106 additions & 0 deletions src/Common/DeprecationInfoDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using Logging;
using Microsoft.Extensions.Logging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

namespace Common
{
public class DeprecationInfoDecorator : IPackageDependencyNodeDecorator
{
private readonly List<SourceRepository> _sourceRepositories;
private readonly SourceCacheContext _sourceCacheContext;
private readonly Dictionary<PackageIdentity, bool> PackageDeprecationData = new();
private readonly List<PackageMetadataResource> _packageMetadataResources = new();
private bool _packageMetadataResourcesAcquired;

public DeprecationInfoDecorator(List<SourceRepository> sourceRepositories, SourceCacheContext sourceCacheContext)
{
_sourceRepositories = sourceRepositories ?? throw new ArgumentNullException(nameof(sourceRepositories));
_sourceCacheContext = sourceCacheContext ?? throw new ArgumentNullException(nameof(sourceCacheContext));
}

public async Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken)
{
if (PackageDeprecationData.TryGetValue(dependencyNode.Identity, out var isPackageDeprecated))
{
dependencyNode.Identity.Deprecated = isPackageDeprecated;
return;
}

if (_packageMetadataResourcesAcquired)
{
await InitializeMetadataResource(cancellationToken);
_packageMetadataResourcesAcquired = true;
}

bool isDeprecated = await IsPackageDeprecatedAsync(dependencyNode.Identity, cancellationToken);
PackageDeprecationData.Add(dependencyNode.Identity, isDeprecated);
dependencyNode.Identity.Deprecated = isDeprecated;
}

private async Task<bool> IsPackageDeprecatedAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken)
{
List<Task<IPackageSearchMetadata>>? results = new(_packageMetadataResources.Count);

bool isDeprecated = false;
foreach (PackageMetadataResource packageMetadataResource in _packageMetadataResources)
{
var packageMetadata = packageMetadataResource.GetMetadataAsync(packageIdentity, _sourceCacheContext, NuGet.Common.NullLogger.Instance, cancellationToken);
if (packageMetadata != null)
{
results.Add(packageMetadata);
}
}
await Task.WhenAll(results);
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
foreach (var result in results)
{
if (result?.Result != null)
{
isDeprecated = isDeprecated || await result.Result.GetDeprecationMetadataAsync() != null;
}
}

return isDeprecated;
}

private async Task InitializeMetadataResource(CancellationToken cancellationToken)
{
List<Task<PackageMetadataResource?>>? results = new(_sourceRepositories.Count);

foreach (SourceRepository source in _sourceRepositories)
{
var metadataResource = GetMetadataResourceAsync(source, _sourceCacheContext, cancellationToken);
if (metadataResource != null)
{
results.Add(metadataResource);
}
}

await Task.WhenAll(results);

if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
foreach (var result in results)
{
if (result?.Result != null)
{
_packageMetadataResources.Add(result.Result);
}
}
}

static async Task<PackageMetadataResource?> GetMetadataResourceAsync(SourceRepository source, SourceCacheContext cacheContext, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
PackageMetadataResource metadataResource =
await source.GetResourceAsync<PackageMetadataResource>(cancellationToken);
return metadataResource;
}
}
}
2 changes: 1 addition & 1 deletion src/Common/IPackageDependencyNodeDecorator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ public interface IPackageDependencyNodeDecorator
{
Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken);
}
}
}
43 changes: 35 additions & 8 deletions src/DependencyVisualizerTool/DGMLDependencyVisualizerTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public static XDocument TransGraphToDGMLXDocument(PackageDependencyGraph graph,
id: firstNode.Identity.ToString(),
label: firstNode.Identity.ToString(),
type: firstNode.Identity.Type,
isVulnerable: firstNode.Identity.Vulnerable);
isVulnerable: firstNode.Identity.Vulnerable,
isDeprecated: firstNode.Identity.Deprecated);
nodes.Add(firstNode.Identity.ToString(), firstNodeDGML);

while (queue.Count > 0)
Expand All @@ -49,7 +50,8 @@ public static XDocument TransGraphToDGMLXDocument(PackageDependencyGraph graph,
id: child.Item1.Identity.ToString(),
label: child.Item1.Identity.ToString(),
type: child.Item1.Identity.Type,
child.Item1.Identity.Vulnerable);
child.Item1.Identity.Vulnerable,
child.Item1.Identity.Deprecated);
nodes.Add(child.Item1.Identity.ToString(), currentDGML);
}
}
Expand Down Expand Up @@ -88,8 +90,18 @@ from item in links
new XAttribute("StrokeThickness", "1")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "VulnerablePackage"),
new XAttribute("Background", "Red"),
new XAttribute("StrokeThickness", "1"))))
new XAttribute("Background", "None"),
new XAttribute("StrokeThickness", "4"),
new XAttribute("Stroke", "Red")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "DeprecatedPackage"),
new XAttribute("Background", "Yellow"),
new XAttribute("StrokeThickness", "1")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "VulnerableAndDeprecatedPackage"),
new XAttribute("Background", "Yellow"),
new XAttribute("StrokeThickness", "4"),
new XAttribute("Stroke", "Red"))))
);
return document;
}
Expand All @@ -102,16 +114,31 @@ private class DGMLNode : IEquatable<DGMLNode>

public string Category { get; set; }

public DGMLNode(string id, string label, DependencyType type, bool isVulnerable)
public DGMLNode(string id, string label, DependencyType type, bool isVulnerable, bool isDeprecated)
{
this.Id = id;
this.Label = label;
this.Category = isVulnerable ? "VulnerablePackage" : type.ToString();
this.Category = GetCategory(type, isVulnerable, isDeprecated);
}

public bool Equals(DGMLNode other)
private static string GetCategory(DependencyType type, bool isVulnerable, bool isDeprecated)
{
return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase);
return isVulnerable && isDeprecated ?
"VulnerableAndDeprecatedPackage" :
isVulnerable ?
"VulnerablePackage" :
isDeprecated ?
"DeprecatedPackage" :
type.ToString();
}

public bool Equals(DGMLNode? other)
{
if (other != null)
{
return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase);
}
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<None Remove="compiler\resources\diamonddependency.assets.json" />
<None Remove="compiler\resources\diamonddependency.withvulnerabilitiesanddeprecations.dgml" />
<None Remove="compiler\resources\diamonddependencywithtoplevel.assets.json" />
<None Remove="compiler\resources\missingpackageversion.assets.json" />
<None Remove="compiler\resources\multipleversions.assets.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using Common;
using FluentAssertions;
using FluentAssertions.Equivalency;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Versioning;
using SharedUtility;

namespace DependencyVisualizerTool.Test
Expand Down Expand Up @@ -143,22 +147,67 @@ public async Task TransGraphToDGMLXDocument_transitiveprojectreference_CreateDGM
actualDGML.Should().Be(expectedDGML, because: actualDGML);
}

private static string RemoveWhitespace(string s)
[Fact]
public async Task TransGraphToDGMLXDocument_WithVulnerableAndDeprecatedPackages_CreateDGMLCorrectly()
{
return Regex.Replace(s, @"\s+", string.Empty);
var packageA = new PackageIdentity("A", new NuGetVersion(1, 0, 0));
var packageB = new PackageIdentity("B", new NuGetVersion(1, 0, 0));
var packageC = new PackageIdentity("C", new NuGetVersion(1, 1, 0));
var decorator = new VulnerabilityAndDeprecationDecorator(
vulnerablePackages: new HashSet<PackageIdentity> { packageA, packageC },
deprecatedPackages: new HashSet<PackageIdentity> { packageB, packageC });

var graph = await GetOnlyDependencyGraphAsync("DependencyVisualizerTool.Test.compiler.resources.diamonddependency.assets.json", new() { decorator });

string actualDGML = RemoveWhitespace(DGMLDependencyVisualizerTool.TransGraphToDGMLXDocument(graph).ToString());

string expectedDGML = RemoveWhitespace(TestHelpers.GetResource("DependencyVisualizerTool.Test.compiler.resources.diamonddependency.withvulnerabilitiesanddeprecations.dgml", GetType()));

actualDGML.Should().Be(expectedDGML, because: actualDGML);
}

private async Task<PackageDependencyGraph> GetOnlyDependencyGraphAsync(string resourceName)
private async Task<PackageDependencyGraph> GetOnlyDependencyGraphAsync(string resourceName, List<IPackageDependencyNodeDecorator> decorators = null)

Check warning on line 169 in src/DependencyVisualizerTool/DependencyVisualizerTool.Test/PackageDependencyVisualizerToolTests.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 169 in src/DependencyVisualizerTool/DependencyVisualizerTool.Test/PackageDependencyVisualizerToolTests.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var assetsFileText = TestHelpers.GetResource(resourceName, GetType());

var assetsFile = new LockFileFormat().Parse(assetsFileText, Path.GetTempPath());
var dependencyGraphSpec = new DependencyGraphSpec();
dependencyGraphSpec.AddProject(assetsFile.PackageSpec);
Dictionary<string, PackageDependencyGraph> graphs = await PackageDependencyGraph.GenerateAllDependencyGraphsFromAssetsFileAsync(assetsFile, dependencyGraphSpec, projectsOnly: false, new(), CancellationToken.None);
Dictionary<string, PackageDependencyGraph> graphs = await PackageDependencyGraph.GenerateAllDependencyGraphsFromAssetsFileAsync(assetsFile, dependencyGraphSpec, projectsOnly: false, decorators ?? new(), CancellationToken.None);
graphs.Should().HaveCount(1);
var graph = graphs.Single().Value;
return graph;
}

private static string RemoveWhitespace(string s)
{
return Regex.Replace(s, @"\s+", string.Empty);
}

private class VulnerabilityAndDeprecationDecorator : IPackageDependencyNodeDecorator
{
private readonly HashSet<PackageIdentity> _vulnerablePackages;
private readonly HashSet<PackageIdentity> _deprecatedPackages;

public VulnerabilityAndDeprecationDecorator(HashSet<PackageIdentity> vulnerablePackages, HashSet<PackageIdentity> deprecatedPackages)
{
_vulnerablePackages = vulnerablePackages ?? throw new ArgumentNullException(nameof(vulnerablePackages));
_deprecatedPackages = deprecatedPackages ?? throw new ArgumentNullException(nameof(deprecatedPackages));
}

public Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken)
{
PackageIdentity identity = dependencyNode.Identity;
if (_vulnerablePackages.Contains(identity))
{
dependencyNode.Identity.Vulnerable = true;
}
if (_deprecatedPackages.Contains(identity))
{
dependencyNode.Identity.Deprecated = true;
}
return Task.CompletedTask;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="TestProject.1.0.0" Label="TestProject.1.0.0" Category="Project" />
<Node Id="A.1.0.0" Label="A.1.0.0" Category="VulnerablePackage" />
<Node Id="B.1.0.0" Label="B.1.0.0" Category="DeprecatedPackage" />
<Node Id="C.1.1.0" Label="C.1.1.0" Category="VulnerableAndDeprecatedPackage" />
</Nodes>
<Links>
<Link Source="TestProject.1.0.0" Target="A.1.0.0" Label="[1.0.0, )" />
<Link Source="TestProject.1.0.0" Target="B.1.0.0" Label="[1.0.0, )" />
<Link Source="A.1.0.0" Target="C.1.1.0" Label="[1.0.0, )" />
<Link Source="B.1.0.0" Target="C.1.1.0" Label="[1.1.0, )" />
</Links>
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Loading

0 comments on commit e947bae

Please sign in to comment.