gonum.org/v1/gonum@v0.14.0/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  	"gonum.org/v1/gonum/graph"
    18  	"gonum.org/v1/gonum/graph/encoding"
    19  	"gonum.org/v1/gonum/graph/encoding/dot"
    20  	"gonum.org/v1/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  }