diff --git a/internal/driver/driver_test.go b/internal/driver/driver_test.go index fa1ea2c86..68c3c03a6 100644 --- a/internal/driver/driver_test.go +++ b/internal/driver/driver_test.go @@ -192,8 +192,14 @@ func TestParse(t *testing.T) { t.Fatalf("reading solution file %s: %v", solution, err) } if runtime.GOOS == "windows" { - sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte("testdata\\"), -1) - sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte("\\path\\to\\"), -1) + if flags[0] == "dot" { + // The .dot test has the paths inside strings, so \ must be escaped. + sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte(`testdata\\`), -1) + sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte(`\\path\\to\\`), -1) + } else { + sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte(`testdata\`), -1) + sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte(`\path\to\`), -1) + } } if flags[0] == "svg" { diff --git a/internal/graph/dotgraph.go b/internal/graph/dotgraph.go index cde648f20..8cb87da9a 100644 --- a/internal/graph/dotgraph.go +++ b/internal/graph/dotgraph.go @@ -127,7 +127,7 @@ func (b *builder) addLegend() { } title := labels[0] fmt.Fprintf(b, `subgraph cluster_L { "%s" [shape=box fontsize=16`, title) - fmt.Fprintf(b, ` label="%s\l"`, strings.Join(escapeForDot(labels), `\l`)) + fmt.Fprintf(b, ` label="%s\l"`, strings.Join(escapeAllForDot(labels), `\l`)) if b.config.LegendURL != "" { fmt.Fprintf(b, ` URL="%s" target="_blank"`, b.config.LegendURL) } @@ -187,7 +187,7 @@ func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) { // Create DOT attribute for node. attr := fmt.Sprintf(`label="%s" id="node%d" fontsize=%d shape=%s tooltip="%s (%s)" color="%s" fillcolor="%s"`, - label, nodeID, fontSize, shape, node.Info.PrintableName(), cumValue, + label, nodeID, fontSize, shape, escapeForDot(node.Info.PrintableName()), cumValue, dotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), false), dotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), true)) @@ -305,7 +305,8 @@ func (b *builder) addEdge(edge *Edge, from, to int, hasNodelets bool) { arrow = "..." } tooltip := fmt.Sprintf(`"%s %s %s (%s)"`, - edge.Src.Info.PrintableName(), arrow, edge.Dest.Info.PrintableName(), w) + escapeForDot(edge.Src.Info.PrintableName()), arrow, + escapeForDot(edge.Dest.Info.PrintableName()), w) attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`, attr, tooltip, tooltip) if edge.Residual { @@ -382,7 +383,7 @@ func dotColor(score float64, isBackground bool) string { func multilinePrintableName(info *NodeInfo) string { infoCopy := *info - infoCopy.Name = ShortenFunctionName(infoCopy.Name) + infoCopy.Name = escapeForDot(ShortenFunctionName(infoCopy.Name)) infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1) infoCopy.Name = strings.Replace(infoCopy.Name, ".", `\n`, -1) if infoCopy.File != "" { @@ -473,13 +474,18 @@ func min64(a, b int64) int64 { return b } -// escapeForDot escapes double quotes and backslashes, and replaces Graphviz's -// "center" character (\n) with a left-justified character. -// See https://graphviz.org/doc/info/attrs.html#k:escString for more info. -func escapeForDot(in []string) []string { +// escapeAllForDot applies escapeForDot to all strings in the given slice. +func escapeAllForDot(in []string) []string { var out = make([]string, len(in)) for i := range in { - out[i] = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(in[i], `\`, `\\`), `"`, `\"`), "\n", `\l`) + out[i] = escapeForDot(in[i]) } return out } + +// escapeForDot escapes double quotes and backslashes, and replaces Graphviz's +// "center" character (\n) with a left-justified character. +// See https://graphviz.org/doc/info/attrs.html#k:escString for more info. +func escapeForDot(str string) string { + return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(str, `\`, `\\`), `"`, `\"`), "\n", `\l`) +} diff --git a/internal/graph/dotgraph_test.go b/internal/graph/dotgraph_test.go index 4232efaa5..08d80eb02 100644 --- a/internal/graph/dotgraph_test.go +++ b/internal/graph/dotgraph_test.go @@ -138,6 +138,18 @@ func TestComposeWithStandardGraphAndURL(t *testing.T) { compareGraphs(t, buf.Bytes(), "compose6.dot") } +func TestComposeWithNamesThatNeedEscaping(t *testing.T) { + g := baseGraph() + a, c := baseAttrsAndConfig() + g.Nodes[0].Info = NodeInfo{Name: `var"src"`} + g.Nodes[1].Info = NodeInfo{Name: `var"#dest#"`} + + var buf bytes.Buffer + ComposeDot(&buf, g, a, c) + + compareGraphs(t, buf.Bytes(), "compose7.dot") +} + func baseGraph() *Graph { src := &Node{ Info: NodeInfo{Name: "src"}, @@ -359,8 +371,8 @@ func TestEscapeForDot(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - if got := escapeForDot(tc.input); !reflect.DeepEqual(got, tc.want) { - t.Errorf("escapeForDot(%s) = %s, want %s", tc.input, got, tc.want) + if got := escapeAllForDot(tc.input); !reflect.DeepEqual(got, tc.want) { + t.Errorf("escapeAllForDot(%s) = %s, want %s", tc.input, got, tc.want) } }) } diff --git a/internal/graph/testdata/compose7.dot b/internal/graph/testdata/compose7.dot new file mode 100644 index 000000000..8f749a779 --- /dev/null +++ b/internal/graph/testdata/compose7.dot @@ -0,0 +1,7 @@ +digraph "testtitle" { +node [style=filled fillcolor="#f8f8f8"] +subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\llabel3: \"foo\"\l" tooltip="testtitle"] } +N1 [label="var\"src\"\n10 (10.00%)\nof 25 (25.00%)" id="node1" fontsize=22 shape=box tooltip="var\"src\" (25)" color="#b23c00" fillcolor="#edddd5"] +N2 [label="var\"#dest#\"\n15 (15.00%)\nof 25 (25.00%)" id="node2" fontsize=24 shape=box tooltip="var\"#dest#\" (25)" color="#b23c00" fillcolor="#edddd5"] +N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="var\"src\" -> var\"#dest#\" (10)" labeltooltip="var\"src\" -> var\"#dest#\" (10)"] +} diff --git a/internal/report/report_test.go b/internal/report/report_test.go index 7c4363fad..7f6821249 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -80,7 +80,12 @@ func TestSource(t *testing.T) { t.Fatalf("%s: %v", tc.want, err) } if runtime.GOOS == "windows" { - gold = bytes.Replace(gold, []byte("testdata/"), []byte("testdata\\"), -1) + if tc.rpt.options.OutputFormat == Dot { + // The .dot test has the paths inside strings, so \ must be escaped. + gold = bytes.Replace(gold, []byte("testdata/"), []byte(`testdata\\`), -1) + } else { + gold = bytes.Replace(gold, []byte("testdata/"), []byte(`testdata\`), -1) + } } if string(b.String()) != string(gold) { d, err := proftest.Diff(gold, b.Bytes())