Add CycleAwareDependencyGraphDumper to visualize cycles (resolves #1561)#1758
Add CycleAwareDependencyGraphDumper to visualize cycles (resolves #1561)#1758Jimin15 wants to merge 1 commit into
Conversation
|
PR has formatting issues, did you build it locally? |
|
Where is this supposed to be used? |
|
Usage can be really anywhere, for example Toolbox dirty-tree mojo... But any reporting plugin that may show dirty tree (like m-dependency-p?) |
gnodet
left a comment
There was a problem hiding this comment.
AI Review — PR #1758: Add CycleAwareDependencyGraphDumper
Hi @Jimin15, thanks for tackling cycle visualization in dependency graphs — this addresses a real need (issue #1561)!
After review and independent verification, a few issues stood out:
⚠️ CI is red — needs rebase (high)
All 12 Verify jobs fail. The PR branch is significantly behind master. A local build/test run of the PR code itself passes (413 tests, 0 failures), so the failure is likely caused by the stale branch base rather than your code. A rebase onto current master should fix CI.
⚠️ Cycle index semantics are misleading (medium)
findCycleInPath returns an index where 0 is the parent (most recent ancestor), not the root — because ArrayDeque.push adds to the front. The code comment on line 144 says "0-based, root is 0" which is incorrect. The ^N notation in output is internally consistent but could confuse users who expect a root-relative index. The test assertion (cycleIndex >= 0 && cycleIndex < output.size()) is also too weak to validate semantic correctness of the index.
⚠️ isLastChild false positive risk (medium)
The equalsVersionlessId fallback in isLastChild compares groupId:artifactId:extension:classifier but not version. If siblings share the same GA but differ in version (plausible in FULL verbosity mode), isLastChild could return true for a non-last child, producing incorrect tree indentation (\- instead of +-).
💡 Design: composition with tight coupling (medium)
The class uses composition (new DependencyGraphDumper(consumer)) but calls protected methods (dumper.formatNode) and relies on knowledge of internal deque semantics. This works because both classes are in the same package, but it's fragile — if formatNode were changed to reference internal state beyond peek(), this would break. Consider inheritance or a cleaner decorator pattern.
What works well:
- The core cycle detection logic is correct — the verifier confirmed that
formatNode(currentPath)correctly peeks the cycle node - The enter/leave delegation is properly paired (both skipped for cycle nodes, both called for non-cycle nodes)
- Test coverage for basic scenarios is solid
🤖 This review was generated by ForgeBot using a maker/checker pattern (reviewer + independent verifier). 2 of 6 original findings were removed as false positives after verification.
gnodet
left a comment
There was a problem hiding this comment.
Review Summary
Thanks for working on this, @Jimin15! A cycle-aware graph dumper is a useful addition for debugging dependency resolution. The composition-based approach wrapping DependencyGraphDumper is a reasonable design choice.
After review and independent verification, I found several confirmed issues:
Medium Severity
-
Misleading cycle index semantics — The comment on
findCycleInPathsays "0-based, root is 0" butArrayDeque.push()adds to the head, and the for-each iterator starts from the head. So index 0 actually corresponds to the most recently pushed node (direct parent), not the root. The^Nnotation in output will show an index relative to the top of the stack, which may confuse users. -
isLastChildversionless ID fallback — TheequalsVersionlessId()fallback ignores version, so if two siblings share the samegroupId:artifactIdbut differ in version (possible in FULL verbosity mode), the wrong sibling could match, producing incorrect tree indentation. -
Incorrect
@since 2.0.0tag — Version 2.0.0 was released long ago (latest is 2.0.20). Recently added classes use accurate version tags like@since 2.0.19. This should be updated to the next release version. -
Weak test assertion in
cycleReferencePointsToCorrectIndex— The test checkscycleIndex >= 0 && cycleIndex < output.size(), comparing a path-depth index against the number of output lines. These are unrelated quantities, so the test would pass even with an incorrect cycle index as long as it's a small positive number.
Low Severity
-
Test simplification — The element-by-element loop comparison in
dumpSimplecould be simplified toassertEquals(standardOutput, cycleAwareOutput)for clearer failure messages. -
Missing test coverage — The two-argument constructor accepting decorators has no test coverage.
Notes
- The reviewer initially flagged a potential "stack desynchronization bug" in
visitLeave, but independent verification confirmed this is not a bug — theDependencyVisitorcontract guarantees matchingvisitEnter/visitLeavepairs, so both stacks remain synchronized. - The composition approach using
protectedmethods fromDependencyGraphDumperis a valid design choice since both classes are co-located in the same package.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
gnodet
left a comment
There was a problem hiding this comment.
Thanks for working on cycle visualization — this addresses a real need (issue #1561). The implementation has a few issues that should be addressed before merge.
Correctness bug:
formatCycleIndentation(line 172) uses identity comparisonchild == cycleNodeto determine tree-drawing characters. In cycle graphs,DependencyGraphParserreuses the sameDependencyNodeJava object for^Nreferences, so the cycle node IS the same object as an earlier ancestor in the path. This causesend=trueprematurely at the wrong depth, producing incorrect indentation (e.g.,+-instead of|). Fix: replace with a positional check likedepth == path.size() - 2.
Other issues:
isLastChildversionless ID fallback viaequalsVersionlessId()(line 205) ignores version. In FULL verbosity mode, siblings with the samegroupId:artifactIdbut different versions would be misidentified as the last child, producing incorrect\-instead of+-.- The comment "0-based, root is 0" in
findCycleInPath(line 144) is incorrect —ArrayDeque.push()adds to the head, so index 0 is the direct parent, not the root. @since 2.0.0should be@since 2.0.22(2.0.0 was released long ago; current dev is 2.0.22-SNAPSHOT).- Test
cycleReferencePointsToCorrectIndexcompares a path-depth index againstoutput.size()(line count) — these are unrelated quantities, so the assertion is vacuously true. Tests should validate against expected exact output strings. - All 12 CI Verify jobs are failing — a rebase onto current master is needed.
- The two-argument constructor accepting decorators has no test coverage.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of gnodet
gnodet
left a comment
There was a problem hiding this comment.
The cycle-aware graph dumper has a correctness bug and several issues that should be addressed:
High severity:
- 🐛 Correctness bug in
formatCycleIndentation(line 183): The identity comparisonchild == cycleNodeproduces incorrect indentation when the same Java object appears at multiple positions in the path (the normal case for cycle graphs). Fix: Replaceboolean end = child == cycleNode;withboolean end = !iter.hasNext();.
Medium severity:
@since 2.0.0tag is incorrect — the current development version is2.0.22-SNAPSHOT. Should be@since 2.0.22.- Comment at line 154 says "0-based, root is 0" but
ArrayDeque's for-each iterator goes from head (most recently pushed = direct parent) to tail, so index 0 is the direct parent, not the root. The comment is factually wrong. isLastChildversionless ID fallback viaequalsVersionlessId()ignores version. In FULL verbosity mode, siblings with the samegroupId:artifactIdbut different versions would be misidentified as the last child.cycleReferencePointsToCorrectIndextest comparescycleIndex(path-depth index) againstoutput.size()(total line count) — these are unrelated quantities, making the assertion vacuously true.
Low severity:
- Element-by-element loop comparison in
dumpSimplecan be simplified toassertEquals(standardOutput, cycleAwareOutput)for clearer failure messages. - Two-argument constructor accepting decorators has no test coverage.
dumpCyclestest only checks pattern match, not exact output format. A test with expected output strings would have caught the indentation bug.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
Following this checklist to help us incorporate your
contribution quickly and easily:
Summary
This PR adds
CycleAwareDependencyGraphDumper, a new dependency visitor that visualizes cycles in dependency graphs while preventing StackOverflow errors. This addresses issue #1561.What
Added
CycleAwareDependencyGraphDumperclass that wrapsDependencyGraphDumperand adds cycle detection capabilities- When a cycle is detected, it displays the cycle with^Nnotation where N is the index of the node in the path that it cycles back to- The visitor stops traversing children of cycle nodes to prevent infinite recursionWhy
Currently,
DependencyGraphDumperfails withStackOverflowErrorwhen visualizing dependency graphs in FULL verbosity mode that contain cycles. WhileTreeDependencyVisitorcan prevent the error, it doesn't visualize cycles. This new visitor provides both cycle visualization and StackOverflow prevention.How
The implementation uses a
Dequeto track the current path during traversal- Cycle detection is performed by comparing versionless artifact IDs usingArtifactIdUtils.equalsVersionlessId()- When a cycle is detected, custom indentation formatting is applied to match the tree structure- The visitor delegates toDependencyGraphDumperfor normal nodes and handles cycle nodes separatelyTesting
Added comprehensive unit tests in
CycleAwareDependencyGraphDumperTest- Tests verify: cycle detection, visualization format, StackOverflow prevention, and compatibility with non-cycle graphs- All tests pass successfullyRelated Issue
Resolves #1561
Note that commits might be squashed by a maintainer on merge.
This may not always be possible but is a best-practice.
mvn verifyto make sure basic checks pass.A more thorough check will be performed on your pull request automatically.
mvn -Prun-its verify).If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.
To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.