mirror of
https://github.com/golang/go
synced 2024-11-18 14:24:44 -07:00
fefcef05ab
Refactors allpaths to print an adjacency list from "from" to "to", instead of just the nodes. This allows other tools like modgraphviz to print the subgraph. For example, the following command: cd $GOPATH/src/cloud.google.com/go && \ go mod graph | \ digraph allpaths cloud.google.com/go golang.org/x/text@v0.3.2 | \ modgraphviz | \ dot -Tpng -o graph.png Generates the following graph.png: https://user-images.githubusercontent.com/3584893/60481727-df0a8680-9c4b-11e9-8df9-c581d599edd1.png Also splits out the allpaths tests into their own test, and adds many test cases. 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 allpaths <from> <to> | digraph nodes Change-Id: I2eb7c377f5fe1e1e90c5b74eaa78d5211192bb2a Reviewed-on: https://go-review.googlesource.com/c/tools/+/184337 Reviewed-by: Alan Donovan <adonovan@google.com>
228 lines
5.5 KiB
Go
228 lines
5.5 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDigraph(t *testing.T) {
|
|
const g1 = `
|
|
socks shoes
|
|
shorts pants
|
|
pants belt shoes
|
|
shirt tie sweater
|
|
sweater jacket
|
|
hat
|
|
`
|
|
|
|
const g2 = `
|
|
a b c
|
|
b d
|
|
c d
|
|
d c
|
|
`
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
input string
|
|
cmd string
|
|
args []string
|
|
want string
|
|
}{
|
|
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
|
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
|
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
|
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
|
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
|
|
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
|
|
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
|
|
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
|
|
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stdin = strings.NewReader(test.input)
|
|
stdout = new(bytes.Buffer)
|
|
if err := digraph(test.cmd, test.args); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := stdout.(fmt.Stringer).String()
|
|
if got != test.want {
|
|
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TODO(adonovan):
|
|
// - test somepath (it's nondeterministic).
|
|
// - test errors
|
|
}
|
|
|
|
func TestAllpaths(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
in string
|
|
to string // from is always "A"
|
|
want string
|
|
}{
|
|
{
|
|
name: "Basic",
|
|
in: "A B\nB C",
|
|
to: "B",
|
|
want: "A B\n",
|
|
},
|
|
{
|
|
name: "Long",
|
|
in: "A B\nB C\n",
|
|
to: "C",
|
|
want: "A B\nB C\n",
|
|
},
|
|
{
|
|
name: "Cycle Basic",
|
|
in: "A B\nB A",
|
|
to: "B",
|
|
want: "A B\nB A\n",
|
|
},
|
|
{
|
|
name: "Cycle Path Out",
|
|
// A <-> B -> C -> D
|
|
in: "A B\nB A\nB C\nC D",
|
|
to: "C",
|
|
want: "A B\nB A\nB C\n",
|
|
},
|
|
{
|
|
name: "Cycle Path Out Further Out",
|
|
// A -> B <-> C -> D -> E
|
|
in: "A B\nB C\nC D\nC B\nD E",
|
|
to: "D",
|
|
want: "A B\nB C\nC B\nC D\n",
|
|
},
|
|
{
|
|
name: "Two Paths Basic",
|
|
// /-> C --\
|
|
// A -> B -- -> E -> F
|
|
// \-> D --/
|
|
in: "A B\nB C\nC E\nB D\nD E\nE F",
|
|
to: "E",
|
|
want: "A B\nB C\nB D\nC E\nD E\n",
|
|
},
|
|
{
|
|
name: "Two Paths With One Immediately From Start",
|
|
// /-> B -+ -> D
|
|
// A -- |
|
|
// \-> C <+
|
|
in: "A B\nA C\nB C\nB D",
|
|
to: "C",
|
|
want: "A B\nA C\nB C\n",
|
|
},
|
|
{
|
|
name: "Two Paths Further Up",
|
|
// /-> B --\
|
|
// A -- -> D -> E -> F
|
|
// \-> C --/
|
|
in: "A B\nA C\nB D\nC D\nD E\nE F",
|
|
to: "E",
|
|
want: "A B\nA C\nB D\nC D\nD E\n",
|
|
},
|
|
{
|
|
// We should include A - C - D even though it's further up the
|
|
// second path than D (which would already be in the graph by
|
|
// the time we get around to integrating the second path).
|
|
name: "Two Splits",
|
|
// /-> B --\ /-> E --\
|
|
// A -- -> D -- -> G -> H
|
|
// \-> C --/ \-> F --/
|
|
in: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
|
|
to: "G",
|
|
want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
|
|
},
|
|
{
|
|
// D - E should not be duplicated.
|
|
name: "Two Paths - Two Splits With Gap",
|
|
// /-> B --\ /-> F --\
|
|
// A -- -> D -> E -- -> H -> I
|
|
// \-> C --/ \-> G --/
|
|
in: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
|
|
to: "H",
|
|
want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stdin = strings.NewReader(test.in)
|
|
stdout = new(bytes.Buffer)
|
|
if err := digraph("allpaths", []string{"A", test.to}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := stdout.(fmt.Stringer).String()
|
|
if got != test.want {
|
|
t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSplit(t *testing.T) {
|
|
for _, test := range []struct {
|
|
line string
|
|
want []string
|
|
}{
|
|
{`one "2a 2b" three`, []string{"one", "2a 2b", "three"}},
|
|
{`one tw"\n\x0a\u000a\012"o three`, []string{"one", "tw\n\n\n\no", "three"}},
|
|
} {
|
|
got, err := split(test.line)
|
|
if err != nil {
|
|
t.Errorf("split(%s) failed: %v", test.line, err)
|
|
}
|
|
if !reflect.DeepEqual(got, test.want) {
|
|
t.Errorf("split(%s) = %v, want %v", test.line, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQuotedLength(t *testing.T) {
|
|
for _, test := range []struct {
|
|
input string
|
|
want int
|
|
}{
|
|
{`"abc"`, 5},
|
|
{`"abc"def`, 5},
|
|
{`"abc\"d"ef`, 8}, // "abc\"d" is consumed, ef is residue
|
|
{`"\012\n\x0a\u000a\U0000000a"`, 28},
|
|
{"\"\xff\"", 3}, // bad UTF-8 is ok
|
|
{`"\xff"`, 6}, // hex escape for bad UTF-8 is ok
|
|
} {
|
|
got, ok := quotedLength(test.input)
|
|
if !ok {
|
|
got = 0
|
|
}
|
|
if got != test.want {
|
|
t.Errorf("quotedLength(%s) = %d, want %d", test.input, got, test.want)
|
|
}
|
|
}
|
|
|
|
// errors
|
|
for _, input := range []string{
|
|
``, // not a quotation
|
|
`a`, // not a quotation
|
|
`'a'`, // not a quotation
|
|
`"a`, // not terminated
|
|
`"\0"`, // short octal escape
|
|
`"\x1"`, // short hex escape
|
|
`"\u000"`, // short \u escape
|
|
`"\U0000000"`, // short \U escape
|
|
`"\k"`, // invalid escape
|
|
"\"ab\nc\"", // newline
|
|
} {
|
|
if n, ok := quotedLength(input); ok {
|
|
t.Errorf("quotedLength(%s) = %d, want !ok", input, n)
|
|
}
|
|
}
|
|
}
|