mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
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:
parent
e713427fea
commit
a4e10e6d0e
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user