...

Source file src/github.com/google/pprof/internal/graph/dotgraph_test.go

Documentation: github.com/google/pprof/internal/graph

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package graph
    16  
    17  import (
    18  	"bytes"
    19  	"flag"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/google/pprof/internal/proftest"
    29  )
    30  
    31  var updateFlag = flag.Bool("update", false, "Update the golden files")
    32  
    33  func TestComposeWithStandardGraph(t *testing.T) {
    34  	g := baseGraph()
    35  	a, c := baseAttrsAndConfig()
    36  
    37  	var buf bytes.Buffer
    38  	ComposeDot(&buf, g, a, c)
    39  
    40  	compareGraphs(t, buf.Bytes(), "compose1.dot")
    41  }
    42  
    43  func TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) {
    44  	g := baseGraph()
    45  	a, c := baseAttrsAndConfig()
    46  
    47  	// Set NodeAttributes for Node 1.
    48  	a.Nodes[g.Nodes[0]] = &DotNodeAttributes{
    49  		Shape:       "folder",
    50  		Bold:        true,
    51  		Peripheries: 2,
    52  		URL:         "www.google.com",
    53  		Formatter: func(ni *NodeInfo) string {
    54  			return strings.ToUpper(ni.Name)
    55  		},
    56  	}
    57  
    58  	// Set Flat value to zero on Node 2.
    59  	g.Nodes[1].Flat = 0
    60  
    61  	var buf bytes.Buffer
    62  	ComposeDot(&buf, g, a, c)
    63  
    64  	compareGraphs(t, buf.Bytes(), "compose2.dot")
    65  }
    66  
    67  func TestComposeWithTagsAndResidualEdge(t *testing.T) {
    68  	g := baseGraph()
    69  	a, c := baseAttrsAndConfig()
    70  
    71  	// Add tags to Node 1.
    72  	g.Nodes[0].LabelTags["a"] = &Tag{
    73  		Name: "tag1",
    74  		Cum:  10,
    75  		Flat: 10,
    76  	}
    77  	g.Nodes[0].NumericTags[""] = TagMap{
    78  		"b": &Tag{
    79  			Name: "tag2",
    80  			Cum:  20,
    81  			Flat: 20,
    82  			Unit: "ms",
    83  		},
    84  	}
    85  
    86  	// Set edge to be Residual.
    87  	g.Nodes[0].Out[g.Nodes[1]].Residual = true
    88  
    89  	var buf bytes.Buffer
    90  	ComposeDot(&buf, g, a, c)
    91  
    92  	compareGraphs(t, buf.Bytes(), "compose3.dot")
    93  }
    94  
    95  func TestComposeWithNestedTags(t *testing.T) {
    96  	g := baseGraph()
    97  	a, c := baseAttrsAndConfig()
    98  
    99  	// Add tags to Node 1.
   100  	g.Nodes[0].LabelTags["tag1"] = &Tag{
   101  		Name: "tag1",
   102  		Cum:  10,
   103  		Flat: 10,
   104  	}
   105  	g.Nodes[0].NumericTags["tag1"] = TagMap{
   106  		"tag2": &Tag{
   107  			Name: "tag2",
   108  			Cum:  20,
   109  			Flat: 20,
   110  			Unit: "ms",
   111  		},
   112  	}
   113  
   114  	var buf bytes.Buffer
   115  	ComposeDot(&buf, g, a, c)
   116  
   117  	compareGraphs(t, buf.Bytes(), "compose5.dot")
   118  }
   119  
   120  func TestComposeWithEmptyGraph(t *testing.T) {
   121  	g := &Graph{}
   122  	a, c := baseAttrsAndConfig()
   123  
   124  	var buf bytes.Buffer
   125  	ComposeDot(&buf, g, a, c)
   126  
   127  	compareGraphs(t, buf.Bytes(), "compose4.dot")
   128  }
   129  
   130  func TestComposeWithStandardGraphAndURL(t *testing.T) {
   131  	g := baseGraph()
   132  	a, c := baseAttrsAndConfig()
   133  	c.LegendURL = "http://example.com"
   134  
   135  	var buf bytes.Buffer
   136  	ComposeDot(&buf, g, a, c)
   137  
   138  	compareGraphs(t, buf.Bytes(), "compose6.dot")
   139  }
   140  
   141  func TestComposeWithNamesThatNeedEscaping(t *testing.T) {
   142  	g := baseGraph()
   143  	a, c := baseAttrsAndConfig()
   144  	g.Nodes[0].Info = NodeInfo{Name: `var"src"`}
   145  	g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`}
   146  
   147  	var buf bytes.Buffer
   148  	ComposeDot(&buf, g, a, c)
   149  
   150  	compareGraphs(t, buf.Bytes(), "compose7.dot")
   151  }
   152  
   153  func TestComposeWithCommentsWithNewlines(t *testing.T) {
   154  	g := baseGraph()
   155  	a, c := baseAttrsAndConfig()
   156  	// comments that could be added with the -add_comment command line tool
   157  	// the first label is used as the dot "node name"; the others are escaped as labels
   158  	c.Labels = []string{"comment line 1\ncomment line 2 \"unterminated double quote", `second comment "double quote"`}
   159  
   160  	var buf bytes.Buffer
   161  	ComposeDot(&buf, g, a, c)
   162  
   163  	compareGraphs(t, buf.Bytes(), "compose9.dot")
   164  }
   165  
   166  func baseGraph() *Graph {
   167  	src := &Node{
   168  		Info:        NodeInfo{Name: "src"},
   169  		Flat:        10,
   170  		Cum:         25,
   171  		In:          make(EdgeMap),
   172  		Out:         make(EdgeMap),
   173  		LabelTags:   make(TagMap),
   174  		NumericTags: make(map[string]TagMap),
   175  	}
   176  	dest := &Node{
   177  		Info:        NodeInfo{Name: "dest"},
   178  		Flat:        15,
   179  		Cum:         25,
   180  		In:          make(EdgeMap),
   181  		Out:         make(EdgeMap),
   182  		LabelTags:   make(TagMap),
   183  		NumericTags: make(map[string]TagMap),
   184  	}
   185  	edge := &Edge{
   186  		Src:    src,
   187  		Dest:   dest,
   188  		Weight: 10,
   189  	}
   190  	src.Out[dest] = edge
   191  	src.In[src] = edge
   192  	return &Graph{
   193  		Nodes: Nodes{
   194  			src,
   195  			dest,
   196  		},
   197  	}
   198  }
   199  
   200  func baseAttrsAndConfig() (*DotAttributes, *DotConfig) {
   201  	a := &DotAttributes{
   202  		Nodes: make(map[*Node]*DotNodeAttributes),
   203  	}
   204  	c := &DotConfig{
   205  		Title:  "testtitle",
   206  		Labels: []string{"label1", "label2", `label3: "foo"`},
   207  		Total:  100,
   208  		FormatValue: func(v int64) string {
   209  			return strconv.FormatInt(v, 10)
   210  		},
   211  	}
   212  	return a, c
   213  }
   214  
   215  func compareGraphs(t *testing.T, got []byte, wantFile string) {
   216  	wantFile = filepath.Join("testdata", wantFile)
   217  	want, err := os.ReadFile(wantFile)
   218  	if err != nil {
   219  		t.Fatalf("error reading test file %s: %v", wantFile, err)
   220  	}
   221  
   222  	if string(got) != string(want) {
   223  		d, err := proftest.Diff(got, want)
   224  		if err != nil {
   225  			t.Fatalf("error finding diff: %v", err)
   226  		}
   227  		t.Errorf("Compose incorrectly wrote %s", string(d))
   228  		if *updateFlag {
   229  			err := os.WriteFile(wantFile, got, 0644)
   230  			if err != nil {
   231  				t.Errorf("failed to update the golden file %q: %v", wantFile, err)
   232  			}
   233  		}
   234  	}
   235  }
   236  
   237  func TestNodeletCountCapping(t *testing.T) {
   238  	labelTags := make(TagMap)
   239  	for i := 0; i < 10; i++ {
   240  		name := fmt.Sprintf("tag-%d", i)
   241  		labelTags[name] = &Tag{
   242  			Name: name,
   243  			Flat: 10,
   244  			Cum:  10,
   245  		}
   246  	}
   247  	numTags := make(TagMap)
   248  	for i := 0; i < 10; i++ {
   249  		name := fmt.Sprintf("num-tag-%d", i)
   250  		numTags[name] = &Tag{
   251  			Name:  name,
   252  			Unit:  "mb",
   253  			Value: 16,
   254  			Flat:  10,
   255  			Cum:   10,
   256  		}
   257  	}
   258  	node1 := &Node{
   259  		Info:        NodeInfo{Name: "node1-with-tags"},
   260  		Flat:        10,
   261  		Cum:         10,
   262  		NumericTags: map[string]TagMap{"": numTags},
   263  		LabelTags:   labelTags,
   264  	}
   265  	node2 := &Node{
   266  		Info: NodeInfo{Name: "node2"},
   267  		Flat: 15,
   268  		Cum:  15,
   269  	}
   270  	node3 := &Node{
   271  		Info: NodeInfo{Name: "node3"},
   272  		Flat: 15,
   273  		Cum:  15,
   274  	}
   275  	g := &Graph{
   276  		Nodes: Nodes{
   277  			node1,
   278  			node2,
   279  			node3,
   280  		},
   281  	}
   282  	for n := 1; n <= 3; n++ {
   283  		input := maxNodelets + n
   284  		if got, want := len(g.SelectTopNodes(input, true)), n; got != want {
   285  			t.Errorf("SelectTopNodes(%d): got %d nodes, want %d", input, got, want)
   286  		}
   287  	}
   288  }
   289  
   290  func TestMultilinePrintableName(t *testing.T) {
   291  	ni := &NodeInfo{
   292  		Name:    "test1.test2::test3",
   293  		File:    "src/file.cc",
   294  		Address: 123,
   295  		Lineno:  999,
   296  	}
   297  
   298  	want := fmt.Sprintf(`%016x\ntest1\ntest2\ntest3\nfile.cc:999\n`, 123)
   299  	if got := multilinePrintableName(ni); got != want {
   300  		t.Errorf("multilinePrintableName(%#v) == %q, want %q", ni, got, want)
   301  	}
   302  }
   303  
   304  func TestTagCollapse(t *testing.T) {
   305  
   306  	makeTag := func(name, unit string, value, flat, cum int64) *Tag {
   307  		return &Tag{name, unit, value, flat, 0, cum, 0}
   308  	}
   309  
   310  	tagSource := []*Tag{
   311  		makeTag("12mb", "mb", 12, 100, 100),
   312  		makeTag("1kb", "kb", 1, 1, 1),
   313  		makeTag("1mb", "mb", 1, 1000, 1000),
   314  		makeTag("2048mb", "mb", 2048, 1000, 1000),
   315  		makeTag("1b", "b", 1, 100, 100),
   316  		makeTag("2b", "b", 2, 100, 100),
   317  		makeTag("7b", "b", 7, 100, 100),
   318  	}
   319  
   320  	tagWant := [][]*Tag{
   321  		{
   322  			makeTag("1B..2GB", "", 0, 2401, 2401),
   323  		},
   324  		{
   325  			makeTag("2GB", "", 0, 1000, 1000),
   326  			makeTag("1B..12MB", "", 0, 1401, 1401),
   327  		},
   328  		{
   329  			makeTag("2GB", "", 0, 1000, 1000),
   330  			makeTag("12MB", "", 0, 100, 100),
   331  			makeTag("1B..1MB", "", 0, 1301, 1301),
   332  		},
   333  		{
   334  			makeTag("2GB", "", 0, 1000, 1000),
   335  			makeTag("1MB", "", 0, 1000, 1000),
   336  			makeTag("2B..1kB", "", 0, 201, 201),
   337  			makeTag("1B", "", 0, 100, 100),
   338  			makeTag("12MB", "", 0, 100, 100),
   339  		},
   340  	}
   341  
   342  	for _, tc := range tagWant {
   343  		var got, want []*Tag
   344  		b := builder{nil, &DotAttributes{}, &DotConfig{}}
   345  		got = b.collapsedTags(tagSource, len(tc), true)
   346  		want = SortTags(tc, true)
   347  
   348  		if !reflect.DeepEqual(got, want) {
   349  			t.Errorf("collapse to %d, got:\n%v\nwant:\n%v", len(tc), tagString(got), tagString(want))
   350  		}
   351  	}
   352  }
   353  
   354  func TestEscapeForDot(t *testing.T) {
   355  	for _, tc := range []struct {
   356  		desc  string
   357  		input []string
   358  		want  []string
   359  	}{
   360  		{
   361  			desc:  "with multiple doubles quotes",
   362  			input: []string{`label: "foo" and "bar"`},
   363  			want:  []string{`label: \"foo\" and \"bar\"`},
   364  		},
   365  		{
   366  			desc:  "with graphviz center line character",
   367  			input: []string{"label: foo \n bar"},
   368  			want:  []string{`label: foo \l bar`},
   369  		},
   370  		{
   371  			desc:  "with two backslashes",
   372  			input: []string{`label: \\`},
   373  			want:  []string{`label: \\\\`},
   374  		},
   375  		{
   376  			desc:  "with two double quotes together",
   377  			input: []string{`label: ""`},
   378  			want:  []string{`label: \"\"`},
   379  		},
   380  		{
   381  			desc:  "with multiple labels",
   382  			input: []string{`label1: "foo"`, `label2: "bar"`},
   383  			want:  []string{`label1: \"foo\"`, `label2: \"bar\"`},
   384  		},
   385  	} {
   386  		t.Run(tc.desc, func(t *testing.T) {
   387  			if got := escapeAllForDot(tc.input); !reflect.DeepEqual(got, tc.want) {
   388  				t.Errorf("escapeAllForDot(%s) = %s, want %s", tc.input, got, tc.want)
   389  			}
   390  		})
   391  	}
   392  }
   393  
   394  func tagString(t []*Tag) string {
   395  	var ret []string
   396  	for _, s := range t {
   397  		ret = append(ret, fmt.Sprintln(s))
   398  	}
   399  	return strings.Join(ret, ":")
   400  }
   401  

View as plain text