From 4da9b5edfc72a75f8d84f7431dd60d910063bc22 Mon Sep 17 00:00:00 2001 From: Ben Weintraub Date: Tue, 28 May 2024 13:45:02 -0700 Subject: [PATCH] add test covering viz command, ensure deterministic output --- pkg/commands/testdata/cases/viz/expected.txt | 65 ++++++++++++++++++++ pkg/commands/testdata/cases/viz/meta.yaml | 1 + pkg/graph/graph.go | 39 +++++++++--- 3 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 pkg/commands/testdata/cases/viz/expected.txt create mode 100644 pkg/commands/testdata/cases/viz/meta.yaml diff --git a/pkg/commands/testdata/cases/viz/expected.txt b/pkg/commands/testdata/cases/viz/expected.txt new file mode 100644 index 0000000..6d1257d --- /dev/null +++ b/pkg/commands/testdata/cases/viz/expected.txt @@ -0,0 +1,65 @@ +digraph { + rankdir=LR + ranksep=2 + node [shape=box fontname=Courier] + n_Apple [shape=plain, label=< + + + + + +
object Apple
varietyAppleVariety
measurementsMeasurements
caloriesInt
>] + n_AppleVariety [shape=plain, label=< + \n \n \n
enum AppleVariety
FUJI
COSMIC_CRISP
GRANNY_SMITH
>] + n_Biscuit [shape=plain, label=< + + + +
object Biscuit
caloriesInt
>] + n_Edible [shape=plain, label=< + + + +
interface Edible
caloriesInt
>] + n_Filter [shape=plain, label=< + + + +
input Filter
nameLikeString
limitInt
>] + n_Fruit [shape=plain, label=< + \n \n
union Fruit
Apple
Orange
>] + n_Measurements [shape=plain, label=< + + + + + +
object Measurements
heightInt
widthInt
depthInt
>] + n_Orange [shape=plain, label=< + + + + +
object Orange
varietyOrangeVariety
caloriesInt
>] + n_OrangeVariety [shape=plain, label=< + \n \n \n
enum OrangeVariety
VALENCIA
NAVEL
CARA_CARA
>] + n_Query [shape=plain, label=< + + + + + + + + +
object Query
fruitFruit
nameString
edibleEdible
nameString
edibles[Edible!]!
filterFilter
>] + n_Apple:p_variety -> n_AppleVariety:main + n_Apple:p_measurements -> n_Measurements:main + n_Fruit:p_Apple -> n_Apple:main + n_Fruit:p_Orange -> n_Orange:main + n_Orange:p_variety -> n_OrangeVariety:main + n_Query:p_fruit -> n_Fruit:main + n_Query:p_edible -> n_Edible:main + n_Query:p_edibles -> n_Edible:main + n_Query:p_edibles_filter -> n_Filter:main +} diff --git a/pkg/commands/testdata/cases/viz/meta.yaml b/pkg/commands/testdata/cases/viz/meta.yaml new file mode 100644 index 0000000..3f106ae --- /dev/null +++ b/pkg/commands/testdata/cases/viz/meta.yaml @@ -0,0 +1 @@ +args: ["viz", "testdata/in.graphql"] \ No newline at end of file diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index 4de7a01..b1b497b 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -2,6 +2,7 @@ package graph import ( "fmt" + "sort" "strings" "github.com/benweint/gquil/pkg/astutil" @@ -212,24 +213,33 @@ func (g *Graph) ReachableFrom(roots []*model.NameReference, maxDepth int) *Graph } func (g *Graph) ToDot() string { - var nodeDefs []string - for _, node := range g.nodes { - if astutil.IsBuiltinType(node.Name) && !g.renderBuiltins { + nodeDefs := g.buildNodeDefs() + edgeDefs := g.buildEdgeDefs() + + return "digraph {\n rankdir=LR\n ranksep=2\n node [shape=box fontname=Courier]\n" + strings.Join(nodeDefs, "\n") + "\n" + strings.Join(edgeDefs, "\n") + "\n}\n" +} + +func (g *Graph) buildNodeDefs() []string { + var result []string + for _, name := range sortedKeys(g.nodes) { + if astutil.IsBuiltinType(name) && !g.renderBuiltins { + continue + } + node := g.nodes[name] + if node.Kind == ast.Scalar { continue } nodeDef := fmt.Sprintf(" %s [shape=plain, label=<%s>]", node.ID(), g.makeNodeLabel(node)) - nodeDefs = append(nodeDefs, nodeDef) + result = append(result, nodeDef) } - - edgeDefs := g.buildEdgeDefs() - - return "digraph {\nrankdir=LR\nranksep=2\nnode [shape=box fontname=Courier]\n" + strings.Join(nodeDefs, "\n") + "\n" + strings.Join(edgeDefs, "\n") + "\n}\n" + return result } func (g *Graph) buildEdgeDefs() []string { - var result []string - for _, edges := range g.edges { + var result []string + for _, sourceNodeName := range sortedKeys(g.edges) { + edges := g.edges[sourceNodeName] for _, edge := range edges { srcPortSuffix := "" dstPortSuffix := ":main" @@ -261,6 +271,15 @@ func (g *Graph) buildEdgeDefs() []string { return result } +func sortedKeys[T any](m map[string]T) []string { + var result []string + for k := range m { + result = append(result, k) + } + sort.Strings(result) + return result +} + func (g *Graph) makeNodeLabel(node *node) string { switch normalizeKind(node.Kind, g.interfacesAsUnions) { case ast.Object: