Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
run: |
total=$(go tool cover -func=coverage.out | awk '/^total:/ {gsub("%","",$3); print $3}')
# Current enforced coverage floor. Codex PRs raise this incrementally toward 90%.
min=45.0
min=50.0
awk -v t="$total" -v m="$min" 'BEGIN {
if (t+0 < m+0) {
Comment on lines 51 to 55
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI coverage floor is raised to 50.0 here, but the coverage badge configuration later in this workflow still uses minColorRange: 45. If the intent is for the badge color scale to reflect the enforced floor, consider updating minColorRange to 50 to keep the workflow consistent.

Copilot uses AI. Check for mistakes.
printf "Coverage %.1f%% is below floor %.1f%%\n", t, m
Expand Down
72 changes: 72 additions & 0 deletions render/depgraph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package render

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -103,3 +105,73 @@ func TestDepgraphRendersExternalDepsAndSummarySection(t *testing.T) {
}
}
}

func writeDepgraphFixture(t *testing.T, root string) {
t.Helper()

files := map[string]string{
"go.mod": "module example.com/demo\n\ngo 1.24.0\n",
"app/main.go": "package app\n\nimport (\n\t\"example.com/demo/core/extra1\"\n\t\"example.com/demo/core/extra2\"\n\t\"example.com/demo/core/leaf\"\n\t\"example.com/demo/core/mid\"\n\t\"example.com/demo/core/root\"\n)\n\nfunc Main() {\n\textra1.Extra1()\n\textra2.Extra2()\n\tleaf.Leaf()\n\tmid.Mid()\n\troot.Root()\n}\n",
"core/root/root.go": "package root\n\nimport \"example.com/demo/core/mid\"\n\nfunc Root() {\n\tmid.Mid()\n}\n",
"core/mid/mid.go": "package mid\n\nimport \"example.com/demo/core/leaf\"\n\nfunc Mid() {\n\tleaf.Leaf()\n}\n",
"core/leaf/leaf.go": "package leaf\n\nfunc Leaf() {}\n",
"core/extra1/extra1.go": "package extra1\n\nimport \"example.com/demo/core/leaf\"\n\nfunc Extra1() {\n\tleaf.Leaf()\n}\n",
"core/extra2/extra2.go": "package extra2\n\nimport \"example.com/demo/core/leaf\"\n\nfunc Extra2() {\n\tleaf.Leaf()\n}\n",
}

for path, content := range files {
full := filepath.Join(root, path)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
}

func TestDepgraphRendersChainsFanoutAndHubs(t *testing.T) {
if !scanner.NewAstGrepAnalyzer().Available() {
t.Skip("ast-grep not available")
Comment on lines +134 to +135
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make ast-grep availability check robust before running test

This guard can pass even when ast-grep is not actually installed (for example on many Linux systems where /usr/bin/sg exists but is the unrelated setgroups tool). In that case the test does not skip, Depgraph cannot build internal dependencies, and the assertion fails with 0 deps (reproducible via go test ./render -run TestDepgraphRendersChainsFanoutAndHubs). Please tighten the precondition so the test skips unless a real ast-grep scanner can parse the fixture.

Useful? React with 👍 / 👎.

}

root := t.TempDir()
writeDepgraphFixture(t, root)

project := scanner.DepsProject{
Root: root,
Files: []scanner.FileAnalysis{
{Path: "app/main.go", Functions: []string{"Main"}},
{Path: "core/root/root.go", Functions: []string{"Root"}},
{Path: "core/mid/mid.go", Functions: []string{"Mid"}},
{Path: "core/leaf/leaf.go", Functions: []string{"Leaf"}},
{Path: "core/extra1/extra1.go", Functions: []string{"Extra1"}},
{Path: "core/extra2/extra2.go", Functions: []string{"Extra2"}},
},
ExternalDeps: map[string][]string{
"go": {"example.com/very/long/module/name/v2"},
},
}

var buf bytes.Buffer
Depgraph(&buf, project)
output := buf.String()

expectedSnippets := []string{
"Dependency Flow",
"Go: name",
"App",
"Core",
"main ──┬──▶ core/extra1/extra1",
"└──▶ core/root/root",
Comment on lines +165 to +166
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestDepgraphRendersChainsFanoutAndHubs asserts a specific fanout ordering (expecting the first branch to be core/extra1/extra1 and the last to be core/root/root). The dependency list comes from ast-grep scan results and is not explicitly sorted before rendering, so the import/target order can vary across ast-grep versions/platforms, making this test potentially flaky. Consider making the assertions order-insensitive (e.g., assert that the fanout form is used and that all expected targets appear somewhere under main), or sort targets in the renderer if deterministic ordering is required.

Suggested change
"main ──┬──▶ core/extra1/extra1",
"└──▶ core/root/root",
"main ──┬──▶",
"core/extra1/extra1",
"core/root/root",

Copilot uses AI. Check for mistakes.
"root ───▶ core/mid/mid",
"HUBS: core/leaf/leaf (4←), core/mid/mid (2←)",
"6 files · 6 functions · 9 deps",
}

for _, snippet := range expectedSnippets {
if !strings.Contains(output, snippet) {
t.Fatalf("expected output to contain %q, got:\n%s", snippet, output)
}
}
}
Loading