From e89fd3bb189296c5de479eb16ae1169bc343caae Mon Sep 17 00:00:00 2001 From: MarcusArdelean Date: Wed, 24 Apr 2024 15:41:06 +0300 Subject: [PATCH] fix: cyclic deps support for dverbose --- lib/parse/dep-graph.ts | 29 +- .../expected-dverbose-dep-graph.json | 552 ++++++++++++++++++ tests/fixtures/dverbose-project/pom.xml | 28 + tests/jest/system/dverbose-flag.spec.ts | 30 + 4 files changed, 634 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/dverbose-project/expected-dverbose-dep-graph.json create mode 100644 tests/fixtures/dverbose-project/pom.xml create mode 100644 tests/jest/system/dverbose-flag.spec.ts diff --git a/lib/parse/dep-graph.ts b/lib/parse/dep-graph.ts index f0e79d2..0d135f8 100644 --- a/lib/parse/dep-graph.ts +++ b/lib/parse/dep-graph.ts @@ -13,13 +13,14 @@ export function buildDepGraph( const builder = new DepGraphBuilder({ name: 'maven' }, parsedRoot.pkgInfo); const visitedMap: Record = {}; const queue: QueueItem[] = []; - queue.push(...getItems(rootId, nodes[rootId])); + queue.push(...getItems(rootId, [], nodes[rootId])); // breadth first search while (queue.length > 0) { const item = queue.shift(); + if (!item) continue; - const { id, parentId } = item; + const { id, ancestry, parentId } = item; const parsed = parseId(id); const node = nodes[id]; if (!includeTestScope && parsed.scope === 'test' && !node.reachesProdDep) { @@ -34,6 +35,18 @@ export function buildDepGraph( builder.connectDep(parentId, prunedId); continue; // don't queue any more children } + + // If verbose is enabled and our ancestry includes ourselves + // we are cyclic and should be pruned :) + if (verboseEnabled && ancestry.includes(id)) { + const prunedId = visited.id + ':pruned-cycle'; + builder.addPkgNode(visited.pkgInfo, prunedId, { + labels: { pruned: 'cyclic' }, + }); + builder.connectDep(parentId, prunedId); + continue; // don't queue any more children + } + const parentNodeId = parentId === rootId ? builder.rootNodeId : parentId; if (verboseEnabled && visited) { // use visited node when omited dependencies found (verbose) @@ -44,7 +57,8 @@ export function buildDepGraph( builder.connectDep(parentNodeId, id); visitedMap[parsed.key] = parsed; } - queue.push(...getItems(id, node)); + // Remember to push updated ancestry here + queue.push(...getItems(id, [...ancestry, id], node)); } return builder.build(); @@ -52,13 +66,18 @@ export function buildDepGraph( interface QueueItem { id: string; + ancestry: string[]; // This is an easy trick to maintain ancestry at cost of space parentId: string; } -function getItems(parentId: string, node?: MavenGraphNode): QueueItem[] { +function getItems( + parentId: string, + ancestry: string[], + node?: MavenGraphNode, +): QueueItem[] { const items: QueueItem[] = []; for (const id of node?.dependsOn || []) { - items.push({ id, parentId }); + items.push({ id, ancestry, parentId }); } return items; } diff --git a/tests/fixtures/dverbose-project/expected-dverbose-dep-graph.json b/tests/fixtures/dverbose-project/expected-dverbose-dep-graph.json new file mode 100644 index 0000000..c22f84a --- /dev/null +++ b/tests/fixtures/dverbose-project/expected-dverbose-dep-graph.json @@ -0,0 +1,552 @@ +{ + "schemaVersion": "1.2.0", + "pkgManager": { + "name": "maven" + }, + "pkgs": [ + { + "id": "com.example:my-java-project@1.0-SNAPSHOT", + "info": { + "name": "com.example:my-java-project", + "version": "1.0-SNAPSHOT" + } + }, + { + "id": "org.apache.zookeeper:zookeeper@3.9.2", + "info": { + "name": "org.apache.zookeeper:zookeeper", + "version": "3.9.2" + } + }, + { + "id": "org.apache.zookeeper:zookeeper-jute@3.9.2", + "info": { + "name": "org.apache.zookeeper:zookeeper-jute", + "version": "3.9.2" + } + }, + { + "id": "org.apache.yetus:audience-annotations@0.12.0", + "info": { + "name": "org.apache.yetus:audience-annotations", + "version": "0.12.0" + } + }, + { + "id": "io.netty:netty-handler@4.1.105.Final", + "info": { + "name": "io.netty:netty-handler", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-transport-native-epoll@4.1.105.Final", + "info": { + "name": "io.netty:netty-transport-native-epoll", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "info": { + "name": "io.netty:netty-tcnative-boringssl-static", + "version": "2.0.61.Final" + } + }, + { + "id": "org.slf4j:slf4j-api@1.7.30", + "info": { + "name": "org.slf4j:slf4j-api", + "version": "1.7.30" + } + }, + { + "id": "commons-io:commons-io@2.11.0", + "info": { + "name": "commons-io:commons-io", + "version": "2.11.0" + } + }, + { + "id": "io.netty:netty-common@4.1.105.Final", + "info": { + "name": "io.netty:netty-common", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-resolver@4.1.105.Final", + "info": { + "name": "io.netty:netty-resolver", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-buffer@4.1.105.Final", + "info": { + "name": "io.netty:netty-buffer", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-transport@4.1.105.Final", + "info": { + "name": "io.netty:netty-transport", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-transport-native-unix-common@4.1.105.Final", + "info": { + "name": "io.netty:netty-transport-native-unix-common", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-codec@4.1.105.Final", + "info": { + "name": "io.netty:netty-codec", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-transport-classes-epoll@4.1.105.Final", + "info": { + "name": "io.netty:netty-transport-classes-epoll", + "version": "4.1.105.Final" + } + }, + { + "id": "io.netty:netty-tcnative-classes@2.0.61.Final", + "info": { + "name": "io.netty:netty-tcnative-classes", + "version": "2.0.61.Final" + } + } + ], + "graph": { + "rootNodeId": "root-node", + "nodes": [ + { + "nodeId": "root-node", + "pkgId": "com.example:my-java-project@1.0-SNAPSHOT", + "deps": [ + { + "nodeId": "org.apache.zookeeper:zookeeper:jar:3.9.2:compile" + } + ] + }, + { + "nodeId": "org.apache.zookeeper:zookeeper:jar:3.9.2:compile", + "pkgId": "org.apache.zookeeper:zookeeper@3.9.2", + "deps": [ + { + "nodeId": "org.apache.zookeeper:zookeeper-jute:jar:3.9.2:compile" + }, + { + "nodeId": "org.apache.yetus:audience-annotations:jar:0.12.0:compile" + }, + { + "nodeId": "io.netty:netty-handler:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:2.0.61.Final:compile" + }, + { + "nodeId": "org.slf4j:slf4j-api:jar:1.7.30:compile" + }, + { + "nodeId": "commons-io:commons-io:jar:2.11.0:compile" + } + ] + }, + { + "nodeId": "org.apache.zookeeper:zookeeper-jute:jar:3.9.2:compile", + "pkgId": "org.apache.zookeeper:zookeeper-jute@3.9.2", + "deps": [ + { + "nodeId": "org.apache.yetus:audience-annotations:jar:0.12.0:compile" + } + ] + }, + { + "nodeId": "org.apache.yetus:audience-annotations:jar:0.12.0:compile", + "pkgId": "org.apache.yetus:audience-annotations@0.12.0", + "deps": [] + }, + { + "nodeId": "io.netty:netty-handler:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-handler@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-resolver:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport-native-unix-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-codec:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.105.Final:compile", + "pkgId": "io.netty:netty-transport-native-epoll@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport-native-unix-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport-classes-epoll:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-classes:jar:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile" + } + ] + }, + { + "nodeId": "org.slf4j:slf4j-api:jar:1.7.30:compile", + "pkgId": "org.slf4j:slf4j-api@1.7.30", + "deps": [] + }, + { + "nodeId": "commons-io:commons-io:jar:2.11.0:compile", + "pkgId": "commons-io:commons-io@2.11.0", + "deps": [] + }, + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-common@4.1.105.Final", + "deps": [] + }, + { + "nodeId": "io.netty:netty-resolver:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-resolver@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-buffer@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-transport@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-resolver:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-transport-native-unix-common:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-transport-native-unix-common@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-codec:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-codec@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-transport-classes-epoll:jar:4.1.105.Final:compile", + "pkgId": "io.netty:netty-transport-classes-epoll@4.1.105.Final", + "deps": [ + { + "nodeId": "io.netty:netty-common:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-buffer:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport:jar:4.1.105.Final:compile" + }, + { + "nodeId": "io.netty:netty-transport-native-unix-common:jar:4.1.105.Final:compile" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-classes:jar:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-classes@2.0.61.Final", + "deps": [] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile:pruned-cycle" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile:pruned-cycle" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile:pruned-cycle" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile:pruned-cycle" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [ + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile:pruned-cycle" + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile:pruned-cycle" + } + ] + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-x86_64:2.0.61.Final:compile:pruned-cycle", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [], + "info": { + "labels": { + "pruned": "cyclic" + } + } + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:linux-aarch_64:2.0.61.Final:compile:pruned-cycle", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [], + "info": { + "labels": { + "pruned": "cyclic" + } + } + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-x86_64:2.0.61.Final:compile:pruned-cycle", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [], + "info": { + "labels": { + "pruned": "cyclic" + } + } + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:osx-aarch_64:2.0.61.Final:compile:pruned-cycle", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [], + "info": { + "labels": { + "pruned": "cyclic" + } + } + }, + { + "nodeId": "io.netty:netty-tcnative-boringssl-static:jar:windows-x86_64:2.0.61.Final:compile:pruned-cycle", + "pkgId": "io.netty:netty-tcnative-boringssl-static@2.0.61.Final", + "deps": [], + "info": { + "labels": { + "pruned": "cyclic" + } + } + } + ] + } +} diff --git a/tests/fixtures/dverbose-project/pom.xml b/tests/fixtures/dverbose-project/pom.xml new file mode 100644 index 0000000..d4a63c0 --- /dev/null +++ b/tests/fixtures/dverbose-project/pom.xml @@ -0,0 +1,28 @@ + + + + 4.0.0 + com.example + my-java-project + 1.0-SNAPSHOT + + + + org.apache.zookeeper + zookeeper + 3.9.2 + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + + + diff --git a/tests/jest/system/dverbose-flag.spec.ts b/tests/jest/system/dverbose-flag.spec.ts new file mode 100644 index 0000000..d925133 --- /dev/null +++ b/tests/jest/system/dverbose-flag.spec.ts @@ -0,0 +1,30 @@ +import * as plugin from "../../../lib"; +import * as path from 'path'; +import { readFixtureJSON } from '../../helpers/read'; +import * as depGraphLib from '@snyk/dep-graph'; + + +const testsPath = path.join(__dirname, '../..'); +const fixturesPath = path.join(testsPath, 'fixtures'); +const testProjectPath = path.join(fixturesPath, 'dverbose-project'); + +test('inspect on dverbose-project pom using -Dverbose', async () => { + let result: Record = await plugin.inspect( + '.', + path.join(testProjectPath, 'pom.xml'), + { + args: ['-Dverbose'], + }, + ); + + const expectedJSON = await readFixtureJSON( + 'dverbose-project', + 'expected-dverbose-dep-graph.json', + ); + const expectedDepGraph = depGraphLib.createFromJSON(expectedJSON).toJSON(); + result = result.scannedProjects[0].depGraph?.toJSON(); + + expect(result).toEqual(expectedDepGraph); + }, + 10000, +);