github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/digraph/digraph_test.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"reflect"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func TestDigraph(t *testing.T) {
    16  	const g1 = `
    17  socks shoes
    18  shorts pants
    19  pants belt shoes
    20  shirt tie sweater
    21  sweater jacket
    22  hat
    23  `
    24  
    25  	const g2 = `
    26  a b c
    27  b d
    28  c d
    29  d c
    30  e e
    31  `
    32  
    33  	for _, test := range []struct {
    34  		name  string
    35  		input string
    36  		cmd   string
    37  		args  []string
    38  		want  string
    39  	}{
    40  		{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
    41  		{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
    42  		{"transpose", g1, "transpose", nil, "belt pants\njacket sweater\npants shorts\nshoes pants\nshoes socks\nsweater shirt\ntie shirt\n"},
    43  		{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
    44  		{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
    45  		{"scss", g2, "sccs", nil, "c d\ne\n"},
    46  		{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
    47  		{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
    48  		{"succs-long-token", g2 + "x " + strings.Repeat("x", 96*1024), "succs", []string{"x"}, strings.Repeat("x", 96*1024) + "\n"},
    49  		{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
    50  		{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
    51  	} {
    52  		t.Run(test.name, func(t *testing.T) {
    53  			stdin = strings.NewReader(test.input)
    54  			stdout = new(bytes.Buffer)
    55  			if err := digraph(test.cmd, test.args); err != nil {
    56  				t.Fatal(err)
    57  			}
    58  
    59  			got := stdout.(fmt.Stringer).String()
    60  			if got != test.want {
    61  				t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
    62  			}
    63  		})
    64  	}
    65  
    66  	// TODO(adonovan):
    67  	// - test somepath (it's nondeterministic).
    68  	// - test errors
    69  }
    70  
    71  func TestAllpaths(t *testing.T) {
    72  	for _, test := range []struct {
    73  		name string
    74  		in   string
    75  		to   string // from is always "A"
    76  		want string
    77  	}{
    78  		{
    79  			name: "Basic",
    80  			in:   "A B\nB C",
    81  			to:   "B",
    82  			want: "A B\n",
    83  		},
    84  		{
    85  			name: "Long",
    86  			in:   "A B\nB C\n",
    87  			to:   "C",
    88  			want: "A B\nB C\n",
    89  		},
    90  		{
    91  			name: "Cycle Basic",
    92  			in:   "A B\nB A",
    93  			to:   "B",
    94  			want: "A B\nB A\n",
    95  		},
    96  		{
    97  			name: "Cycle Path Out",
    98  			// A <-> B -> C -> D
    99  			in:   "A B\nB A\nB C\nC D",
   100  			to:   "C",
   101  			want: "A B\nB A\nB C\n",
   102  		},
   103  		{
   104  			name: "Cycle Path Out Further Out",
   105  			// A -> B <-> C -> D -> E
   106  			in:   "A B\nB C\nC D\nC B\nD E",
   107  			to:   "D",
   108  			want: "A B\nB C\nC B\nC D\n",
   109  		},
   110  		{
   111  			name: "Two Paths Basic",
   112  			//           /-> C --\
   113  			// A -> B --          -> E -> F
   114  			//           \-> D --/
   115  			in:   "A B\nB C\nC E\nB D\nD E\nE F",
   116  			to:   "E",
   117  			want: "A B\nB C\nB D\nC E\nD E\n",
   118  		},
   119  		{
   120  			name: "Two Paths With One Immediately From Start",
   121  			//      /-> B -+ -> D
   122  			// A --        |
   123  			//      \-> C <+
   124  			in:   "A B\nA C\nB C\nB D",
   125  			to:   "C",
   126  			want: "A B\nA C\nB C\n",
   127  		},
   128  		{
   129  			name: "Two Paths Further Up",
   130  			//      /-> B --\
   131  			// A --          -> D -> E -> F
   132  			//      \-> C --/
   133  			in:   "A B\nA C\nB D\nC D\nD E\nE F",
   134  			to:   "E",
   135  			want: "A B\nA C\nB D\nC D\nD E\n",
   136  		},
   137  		{
   138  			// We should include A - C  - D even though it's further up the
   139  			// second path than D (which would already be in the graph by
   140  			// the time we get around to integrating the second path).
   141  			name: "Two Splits",
   142  			//      /-> B --\         /-> E --\
   143  			// A --           -> D --          -> G -> H
   144  			//      \-> C --/         \-> F --/
   145  			in:   "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
   146  			to:   "G",
   147  			want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
   148  		},
   149  		{
   150  			// D - E should not be duplicated.
   151  			name: "Two Paths - Two Splits With Gap",
   152  			//      /-> B --\              /-> F --\
   153  			// A --           -> D -> E --          -> H -> I
   154  			//      \-> C --/              \-> G --/
   155  			in:   "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
   156  			to:   "H",
   157  			want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
   158  		},
   159  	} {
   160  		t.Run(test.name, func(t *testing.T) {
   161  			stdin = strings.NewReader(test.in)
   162  			stdout = new(bytes.Buffer)
   163  			if err := digraph("allpaths", []string{"A", test.to}); err != nil {
   164  				t.Fatal(err)
   165  			}
   166  
   167  			got := stdout.(fmt.Stringer).String()
   168  			if got != test.want {
   169  				t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  func TestSomepath(t *testing.T) {
   176  	for _, test := range []struct {
   177  		name string
   178  		in   string
   179  		to   string
   180  		// somepath is non-deterministic, so we have to provide all the
   181  		// possible options. Each option is separated with |.
   182  		wantAnyOf string
   183  	}{
   184  		{
   185  			name:      "Basic",
   186  			in:        "A B\n",
   187  			to:        "B",
   188  			wantAnyOf: "A B",
   189  		},
   190  		{
   191  			name:      "Basic With Cycle",
   192  			in:        "A B\nB A",
   193  			to:        "B",
   194  			wantAnyOf: "A B",
   195  		},
   196  		{
   197  			name: "Two Paths",
   198  			//      /-> B --\
   199  			// A --          -> D
   200  			//      \-> C --/
   201  			in:        "A B\nA C\nB D\nC D",
   202  			to:        "D",
   203  			wantAnyOf: "A B\nB D|A C\nC D",
   204  		},
   205  	} {
   206  		t.Run(test.name, func(t *testing.T) {
   207  			stdin = strings.NewReader(test.in)
   208  			stdout = new(bytes.Buffer)
   209  			if err := digraph("somepath", []string{"A", test.to}); err != nil {
   210  				t.Fatal(err)
   211  			}
   212  
   213  			got := stdout.(fmt.Stringer).String()
   214  			lines := strings.Split(got, "\n")
   215  			sort.Strings(lines)
   216  			got = strings.Join(lines[1:], "\n")
   217  
   218  			var oneMatch bool
   219  			for _, want := range strings.Split(test.wantAnyOf, "|") {
   220  				if got == want {
   221  					oneMatch = true
   222  				}
   223  			}
   224  			if !oneMatch {
   225  				t.Errorf("digraph(somepath, A, %s) = got %q, want any of\n%s", test.to, got, test.wantAnyOf)
   226  			}
   227  		})
   228  	}
   229  }
   230  
   231  func TestSplit(t *testing.T) {
   232  	for _, test := range []struct {
   233  		line string
   234  		want []string
   235  	}{
   236  		{`one "2a 2b" three`, []string{"one", "2a 2b", "three"}},
   237  		{`one tw"\n\x0a\u000a\012"o three`, []string{"one", "tw\n\n\n\no", "three"}},
   238  	} {
   239  		got, err := split(test.line)
   240  		if err != nil {
   241  			t.Errorf("split(%s) failed: %v", test.line, err)
   242  		}
   243  		if !reflect.DeepEqual(got, test.want) {
   244  			t.Errorf("split(%s) = %v, want %v", test.line, got, test.want)
   245  		}
   246  	}
   247  }
   248  
   249  func TestQuotedLength(t *testing.T) {
   250  	for _, test := range []struct {
   251  		input string
   252  		want  int
   253  	}{
   254  		{`"abc"`, 5},
   255  		{`"abc"def`, 5},
   256  		{`"abc\"d"ef`, 8}, // "abc\"d" is consumed, ef is residue
   257  		{`"\012\n\x0a\u000a\U0000000a"`, 28},
   258  		{"\"\xff\"", 3}, // bad UTF-8 is ok
   259  		{`"\xff"`, 6},   // hex escape for bad UTF-8 is ok
   260  	} {
   261  		got, ok := quotedLength(test.input)
   262  		if !ok {
   263  			got = 0
   264  		}
   265  		if got != test.want {
   266  			t.Errorf("quotedLength(%s) = %d, want %d", test.input, got, test.want)
   267  		}
   268  	}
   269  
   270  	// errors
   271  	for _, input := range []string{
   272  		``,            // not a quotation
   273  		`a`,           // not a quotation
   274  		`'a'`,         // not a quotation
   275  		`"a`,          // not terminated
   276  		`"\0"`,        // short octal escape
   277  		`"\x1"`,       // short hex escape
   278  		`"\u000"`,     // short \u escape
   279  		`"\U0000000"`, // short \U escape
   280  		`"\k"`,        // invalid escape
   281  		"\"ab\nc\"",   // newline
   282  	} {
   283  		if n, ok := quotedLength(input); ok {
   284  			t.Errorf("quotedLength(%s) = %d, want !ok", input, n)
   285  		}
   286  	}
   287  }
   288  
   289  func TestFocus(t *testing.T) {
   290  	for _, test := range []struct {
   291  		name  string
   292  		in    string
   293  		focus string
   294  		want  string
   295  	}{
   296  		{
   297  			name:  "Basic",
   298  			in:    "A B",
   299  			focus: "B",
   300  			want:  "A B\n",
   301  		},
   302  		{
   303  			name: "Some Nodes Not Included",
   304  			// C does not have a path involving B, and should not be included
   305  			// in the output.
   306  			in:    "A B\nA C",
   307  			focus: "B",
   308  			want:  "A B\n",
   309  		},
   310  		{
   311  			name: "Cycle In Path",
   312  			// A <-> B -> C
   313  			in:    "A B\nB A\nB C",
   314  			focus: "C",
   315  			want:  "A B\nB A\nB C\n",
   316  		},
   317  		{
   318  			name: "Cycle Out Of Path",
   319  			// C <- A <->B
   320  			in:    "A B\nB A\nB C",
   321  			focus: "C",
   322  			want:  "A B\nB A\nB C\n",
   323  		},
   324  		{
   325  			name: "Complex",
   326  			// Paths in and out from focus.
   327  			//                   /-> F
   328  			//      /-> B -> D --
   329  			// A --              \-> E
   330  			//      \-> C
   331  			in:    "A B\nA C\nB D\nD F\nD E",
   332  			focus: "D",
   333  			want:  "A B\nB D\nD E\nD F\n",
   334  		},
   335  	} {
   336  		t.Run(test.name, func(t *testing.T) {
   337  			stdin = strings.NewReader(test.in)
   338  			stdout = new(bytes.Buffer)
   339  			if err := digraph("focus", []string{test.focus}); err != nil {
   340  				t.Fatal(err)
   341  			}
   342  			got := stdout.(fmt.Stringer).String()
   343  			if got != test.want {
   344  				t.Errorf("digraph(focus, %s) = got %q, want %q", test.focus, got, test.want)
   345  			}
   346  		})
   347  	}
   348  }