digraph: refactor somepath to print an adjacency list

Refactors somepath to print an adjacency list from "from" to "to", instead of
just the nodes. This allows other tools like modgraphviz to print the subgraph.

This is a breaking change. The previous behavior returned vertices; now it
returns edges. If you relied on the previous behavior, use:

    my-application | digraph somepath <from> <to> | digraph nodes

Change-Id: Id5e77e19eea79849ec6f25018f627c8dc4e2a831
Reviewed-on: https://go-review.googlesource.com/c/tools/+/184338
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Jean de Klerk 2019-07-16 16:12:11 -06:00 committed by Alan Donovan
parent e713427fea
commit a4e10e6d0e
2 changed files with 92 additions and 21 deletions

View File

@ -303,6 +303,36 @@ func (g graph) allpaths(from, to string) error {
return nil
}
func (g graph) somepath(from, to string) error {
type edge struct{ from, to string }
seen := make(nodeset)
var dfs func(path []edge, from string) bool
dfs = func(path []edge, from string) bool {
if !seen[from] {
seen[from] = true
if from == to {
// fmt.Println(path, len(path), cap(path))
// Print and unwind.
for _, e := range path {
fmt.Fprintln(stdout, e.from+" "+e.to)
}
return true
}
for e := range g[from] {
if dfs(append(path, edge{from: from, to: e}), e) {
return true
}
}
}
return false
}
maxEdgesInGraph := len(g) * (len(g) - 1)
if !dfs(make([]edge, 0, maxEdgesInGraph), from) {
return fmt.Errorf("no path from %q to %q", from, to)
}
return nil
}
func parse(rd io.Reader) (graph, error) {
g := make(graph)
@ -407,26 +437,8 @@ func digraph(cmd string, args []string) error {
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
seen := make(nodeset)
var visit func(path nodelist, label string) bool
visit = func(path nodelist, label string) bool {
if !seen[label] {
seen[label] = true
if label == to {
append(path, label).println("\n")
return true // unwind
}
for e := range g[label] {
if visit(append(path, label), e) {
return true
}
}
}
return false
}
if !visit(make(nodelist, 0, 100), from) {
return fmt.Errorf("no path from %q to %q", args[0], args[1])
if err := g.somepath(from, to); err != nil {
return err
}
case "allpaths":
@ -440,7 +452,9 @@ func digraph(cmd string, args []string) error {
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
g.allpaths(from, to)
if err := g.allpaths(from, to); err != nil {
return err
}
case "sccs":
if len(args) != 0 {

View File

@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"reflect"
"sort"
"strings"
"testing"
)
@ -168,6 +169,62 @@ func TestAllpaths(t *testing.T) {
}
}
func TestSomepath(t *testing.T) {
for _, test := range []struct {
name string
in string
to string
// somepath is non-deterministic, so we have to provide all the
// possible options. Each option is separated with |.
wantAnyOf string
}{
{
name: "Basic",
in: "A B\n",
to: "B",
wantAnyOf: "A B",
},
{
name: "Basic With Cycle",
in: "A B\nB A",
to: "B",
wantAnyOf: "A B",
},
{
name: "Two Paths",
// /-> B --\
// A -- -> D
// \-> C --/
in: "A B\nA C\nB D\nC D",
to: "D",
wantAnyOf: "A B\nB D|A C\nC D",
},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.in)
stdout = new(bytes.Buffer)
if err := digraph("somepath", []string{"A", test.to}); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
lines := strings.Split(got, "\n")
sort.Strings(lines)
got = strings.Join(lines[1:], "\n")
var oneMatch bool
for _, want := range strings.Split(test.wantAnyOf, "|") {
if got == want {
oneMatch = true
}
}
if !oneMatch {
t.Errorf("digraph(somepath, A, %s) = got %q, want any of\n%s", test.to, got, test.wantAnyOf)
}
})
}
}
func TestSplit(t *testing.T) {
for _, test := range []struct {
line string