github.com/gopherd/gonum@v0.0.4/graph/encoding/graphql/decode_test.go (about) 1 // Copyright ©2017 The Gonum 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 5 package graphql 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "os/exec" 12 "sort" 13 "strconv" 14 "strings" 15 "testing" 16 17 "github.com/gopherd/gonum/graph" 18 "github.com/gopherd/gonum/graph/encoding" 19 "github.com/gopherd/gonum/graph/encoding/dot" 20 "github.com/gopherd/gonum/graph/simple" 21 ) 22 23 var decodeTests = []struct { 24 name string 25 json string 26 roots map[uint64]bool 27 wantDOT string 28 wantErr error 29 }{ 30 { 31 name: "starwars", 32 json: starwars, 33 roots: map[uint64]bool{ 34 0xa3cff1a4c3ef3bb6: true, 35 0xb39aa14d66aedad5: true, 36 }, 37 wantDOT: `strict digraph { 38 // Node definitions. 39 "0x8a10d5a2611fd03f" [name="Richard Marquand"]; 40 "0xa3cff1a4c3ef3bb6" [ 41 name="Star Wars: Episode V - The Empire Strikes Back" 42 release_date="1980-05-21T00:00:00Z" 43 revenue=534000000 44 running_time=124 45 ]; 46 "0xb39aa14d66aedad5" [ 47 name="Star Wars: Episode VI - Return of the Jedi" 48 release_date="1983-05-25T00:00:00Z" 49 revenue=572000000 50 running_time=131 51 ]; 52 "0x0312de17a7ee89f9" [name="Luke Skywalker"]; 53 "0x3da8d1dcab1bb381" [name="Han Solo"]; 54 "0x4a7d0b5fe91e78a4" [name="Irvin Kernshner"]; 55 "0x718337b9dcbaa7d9" [name="Princess Leia"]; 56 57 // Edge definitions. 58 "0xa3cff1a4c3ef3bb6" -> "0x0312de17a7ee89f9" [label=starring]; 59 "0xa3cff1a4c3ef3bb6" -> "0x3da8d1dcab1bb381" [label=starring]; 60 "0xa3cff1a4c3ef3bb6" -> "0x4a7d0b5fe91e78a4" [label=director]; 61 "0xa3cff1a4c3ef3bb6" -> "0x718337b9dcbaa7d9" [label=starring]; 62 "0xb39aa14d66aedad5" -> "0x8a10d5a2611fd03f" [label=director]; 63 "0xb39aa14d66aedad5" -> "0x0312de17a7ee89f9" [label=starring]; 64 "0xb39aa14d66aedad5" -> "0x3da8d1dcab1bb381" [label=starring]; 65 "0xb39aa14d66aedad5" -> "0x718337b9dcbaa7d9" [label=starring]; 66 }`, 67 }, 68 { 69 name: "tutorial", 70 json: dgraphTutorial, 71 roots: map[uint64]bool{ 72 0xfd90205a458151f: true, 73 0x52a80955d40ec819: true, 74 }, 75 wantDOT: `strict digraph { 76 // Node definitions. 77 "0x892a6da7ee1fbdec" [ 78 age=55 79 name=Sarah 80 ]; 81 "0x99b74c1b5ab100ec" [ 82 age=35 83 name=Artyom 84 ]; 85 "0xb9e12a67e34d6acc" [ 86 age=19 87 name=Catalina 88 ]; 89 "0xbf104824c777525d" [name=Perro]; 90 "0xf590a923ea1fccaa" [name=Goldie]; 91 "0xf92d7dbe272d680b" [name="Hyung Sin"]; 92 "0x0fd90205a458151f" [ 93 age=39 94 name=Michael 95 ]; 96 "0x37734fcf0a6fcc69" [name="Rammy the sheep"]; 97 "0x52a80955d40ec819" [ 98 age=35 99 name=Amit 100 ]; 101 "0x5e9ad1cd9466228c" [ 102 age=24 103 name="Sang Hyun" 104 ]; 105 106 // Edge definitions. 107 "0xb9e12a67e34d6acc" -> "0xbf104824c777525d" [label=owns_pet]; 108 "0xb9e12a67e34d6acc" -> "0x5e9ad1cd9466228c" [label=friend]; 109 "0xf92d7dbe272d680b" -> "0x5e9ad1cd9466228c" [label=friend]; 110 "0x0fd90205a458151f" -> "0x892a6da7ee1fbdec" [label=friend]; 111 "0x0fd90205a458151f" -> "0x99b74c1b5ab100ec" [label=friend]; 112 "0x0fd90205a458151f" -> "0xb9e12a67e34d6acc" [label=friend]; 113 "0x0fd90205a458151f" -> "0x37734fcf0a6fcc69" [label=owns_pet]; 114 "0x0fd90205a458151f" -> "0x52a80955d40ec819" [label=friend]; 115 "0x0fd90205a458151f" -> "0x5e9ad1cd9466228c" [label=friend]; 116 "0x52a80955d40ec819" -> "0x99b74c1b5ab100ec" [label=friend]; 117 "0x52a80955d40ec819" -> "0x0fd90205a458151f" [label=friend]; 118 "0x52a80955d40ec819" -> "0x5e9ad1cd9466228c" [label=friend]; 119 "0x5e9ad1cd9466228c" -> "0xb9e12a67e34d6acc" [label=friend]; 120 "0x5e9ad1cd9466228c" -> "0xf590a923ea1fccaa" [label=owns_pet]; 121 "0x5e9ad1cd9466228c" -> "0xf92d7dbe272d680b" [label=friend]; 122 "0x5e9ad1cd9466228c" -> "0x52a80955d40ec819" [label=friend]; 123 }`, 124 }, 125 { 126 name: "tutorial missing IDs", 127 json: dgraphTutorialMissingIDs, 128 wantErr: errors.New("graphql: no UID for node"), // Incomplete error string. 129 }, 130 } 131 132 func TestDecode(t *testing.T) { 133 for _, test := range decodeTests { 134 dst := newDirectedGraph() 135 err := Unmarshal([]byte(test.json), "_uid_", dst) 136 if test.wantErr == nil && err != nil { 137 t.Errorf("failed to unmarshal GraphQL JSON graph for %q: %v", test.name, err) 138 } else if test.wantErr != nil { 139 if err == nil { 140 t.Errorf("expected error for %q: got:%v want:%v", test.name, err, test.wantErr) 141 } 142 continue 143 } 144 b, err := dot.Marshal(dst, "", "", " ") 145 if err != nil { 146 t.Fatalf("failed to DOT marshal graph %q: %v", test.name, err) 147 } 148 gotDOT := string(b) 149 if gotDOT != test.wantDOT { 150 t.Errorf("unexpected DOT encoding for %q:\ngot:\n%s\nwant:\n%s", test.name, gotDOT, test.wantDOT) 151 } 152 checkDOT(t, b) 153 } 154 } 155 156 type directedGraph struct { 157 *simple.DirectedGraph 158 } 159 160 func newDirectedGraph() *directedGraph { 161 return &directedGraph{DirectedGraph: simple.NewDirectedGraph()} 162 } 163 164 func (g *directedGraph) NewNode() graph.Node { 165 return &node{attributes: make(attributes)} 166 } 167 168 func (g *directedGraph) NewEdge(from, to graph.Node) graph.Edge { 169 return &edge{Edge: g.DirectedGraph.NewEdge(from, to)} 170 } 171 172 type node struct { 173 id uint64 174 attributes 175 } 176 177 func (n *node) ID() int64 { return int64(n.id) } 178 func (n *node) DOTID() string { return fmt.Sprintf("0x%016x", n.id) } 179 180 func (n *node) SetIDFromString(uid string) error { 181 if !strings.HasPrefix(uid, "0x") { 182 return fmt.Errorf("uid is not hex value: %q", uid) 183 } 184 var err error 185 n.id, err = strconv.ParseUint(uid[2:], 16, 64) 186 return err 187 } 188 189 type edge struct { 190 graph.Edge 191 label string 192 } 193 194 func (e *edge) SetLabel(l string) { 195 e.label = l 196 } 197 198 func (e *edge) Attributes() []encoding.Attribute { 199 return []encoding.Attribute{{Key: "label", Value: e.label}} 200 } 201 202 type attributes map[string]encoding.Attribute 203 204 func (a attributes) SetAttribute(attr encoding.Attribute) error { 205 a[attr.Key] = attr 206 return nil 207 } 208 209 func (a attributes) Attributes() []encoding.Attribute { 210 keys := make([]string, 0, len(a)) 211 for k := range a { 212 keys = append(keys, k) 213 } 214 sort.Strings(keys) 215 attr := make([]encoding.Attribute, 0, len(keys)) 216 for _, k := range keys { 217 v := a[k] 218 if strings.Contains(v.Value, " ") { 219 v.Value = `"` + v.Value + `"` 220 } 221 attr = append(attr, v) 222 } 223 return attr 224 } 225 226 // checkDOT hands b to the dot executable if it exists and fails t if dot 227 // returns an error. 228 func checkDOT(t *testing.T, b []byte) { 229 dot, err := exec.LookPath("dot") 230 if err != nil { 231 t.Logf("skipping DOT syntax check: %v", err) 232 return 233 } 234 cmd := exec.Command(dot) 235 cmd.Stdin = bytes.NewReader(b) 236 stderr := &bytes.Buffer{} 237 cmd.Stderr = stderr 238 err = cmd.Run() 239 if err != nil { 240 t.Errorf("invalid DOT syntax: %v\n%s\ninput:\n%s", err, stderr.String(), b) 241 } 242 }