| // 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" |
| "sort" |
| "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"}, |
| {"transpose", g1, "transpose", nil, "belt pants\njacket sweater\npants shorts\nshoes pants\nshoes socks\nsweater shirt\ntie shirt\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 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 |
| 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) |
| } |
| } |
| } |
| |
| func TestFocus(t *testing.T) { |
| for _, test := range []struct { |
| name string |
| in string |
| focus string |
| want string |
| }{ |
| { |
| name: "Basic", |
| in: "A B", |
| focus: "B", |
| want: "A B\n", |
| }, |
| { |
| name: "Some Nodes Not Included", |
| // C does not have a path involving B, and should not be included |
| // in the output. |
| in: "A B\nA C", |
| focus: "B", |
| want: "A B\n", |
| }, |
| { |
| name: "Cycle In Path", |
| // A <-> B -> C |
| in: "A B\nB A\nB C", |
| focus: "C", |
| want: "A B\nB A\nB C\n", |
| }, |
| { |
| name: "Cycle Out Of Path", |
| // C <- A <->B |
| in: "A B\nB A\nB C", |
| focus: "C", |
| want: "A B\nB A\nB C\n", |
| }, |
| { |
| name: "Complex", |
| // Paths in and out from focus. |
| // /-> F |
| // /-> B -> D -- |
| // A -- \-> E |
| // \-> C |
| in: "A B\nA C\nB D\nD F\nD E", |
| focus: "D", |
| want: "A B\nB D\nD E\nD F\n", |
| }, |
| } { |
| t.Run(test.name, func(t *testing.T) { |
| stdin = strings.NewReader(test.in) |
| stdout = new(bytes.Buffer) |
| if err := digraph("focus", []string{test.focus}); err != nil { |
| t.Fatal(err) |
| } |
| got := stdout.(fmt.Stringer).String() |
| if got != test.want { |
| t.Errorf("digraph(focus, %s) = got %q, want %q", test.focus, got, test.want) |
| } |
| }) |
| } |
| } |