github.com/cayleygraph/cayley@v0.7.7/graph/graphtest/integration.go (about)

     1  // Copyright 2014 The Cayley Authors. 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 graphtest
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"github.com/cayleygraph/cayley/graph"
    30  	"github.com/cayleygraph/cayley/graph/graphtest/testutil"
    31  	"github.com/cayleygraph/cayley/internal"
    32  	"github.com/cayleygraph/cayley/query"
    33  	"github.com/cayleygraph/cayley/query/gizmo"
    34  	_ "github.com/cayleygraph/cayley/writer"
    35  )
    36  
    37  const (
    38  	format  = "nquads"
    39  	timeout = 300 * time.Second
    40  )
    41  
    42  const (
    43  	nSpeed = "Speed"
    44  	nLakeH = "The Lake House"
    45  
    46  	SandraB = "Sandra Bullock"
    47  	KeanuR  = "Keanu Reeves"
    48  )
    49  
    50  func checkIntegration(t testing.TB, force bool) {
    51  	if testing.Short() {
    52  		t.SkipNow()
    53  	}
    54  	if !force && os.Getenv("RUN_INTEGRATION") != "true" {
    55  		t.Skip("skipping integration tests; set RUN_INTEGRATION=true to run them")
    56  	}
    57  }
    58  
    59  func TestIntegration(t *testing.T, gen testutil.DatabaseFunc, force bool) {
    60  	checkIntegration(t, force)
    61  	qs, closer := prepare(t, gen)
    62  	defer closer()
    63  
    64  	checkQueries(t, qs, timeout)
    65  }
    66  
    67  func BenchmarkIntegration(t *testing.B, gen testutil.DatabaseFunc, force bool) {
    68  	checkIntegration(t, force)
    69  	benchmarkQueries(t, gen)
    70  }
    71  
    72  func costarTag(id, c1, c1m, c2, c2m string) map[string]string {
    73  	return map[string]string{
    74  		"id":            id,
    75  		"costar1_actor": c1,
    76  		"costar1_movie": c1m,
    77  		"costar2_actor": c2,
    78  		"costar2_movie": c2m,
    79  	}
    80  }
    81  
    82  var queries = []struct {
    83  	message string
    84  	long    bool
    85  	query   string
    86  	tag     string
    87  	// for testing
    88  	skip   bool
    89  	expect []interface{}
    90  }{
    91  	// Easy one to get us started. How quick is the most straightforward retrieval?
    92  	{
    93  		message: "name predicate",
    94  		query: `
    95  		g.V("Humphrey Bogart").in("<name>").all()
    96  		`,
    97  		expect: []interface{}{
    98  			map[string]string{"id": "</en/humphrey_bogart>"},
    99  		},
   100  	},
   101  
   102  	// Grunty queries.
   103  	// 2014-07-12: This one seems to return in ~20ms in memory;
   104  	// that's going to be measurably slower for every other backend.
   105  	{
   106  		message: "two large sets with no intersection",
   107  		query: `
   108  		function getId(x) { return g.V(x).in("<name>") }
   109  		var actor_to_film = g.M().in("</film/performance/actor>").in("</film/film/starring>")
   110  
   111  		getId("Oliver Hardy").follow(actor_to_film).out("<name>").intersect(
   112  			getId("Mel Blanc").follow(actor_to_film).out("<name>")).all()
   113  			`,
   114  		expect: nil,
   115  	},
   116  
   117  	// 2014-07-12: This one takes about 4 whole seconds in memory. This is a behemoth.
   118  	{
   119  		message: "three huge sets with small intersection",
   120  		long:    true,
   121  		query: `
   122  			function getId(x) { return g.V(x).in("<name>") }
   123  			var actor_to_film = g.M().in("</film/performance/actor>").in("</film/film/starring>")
   124  
   125  			var a = getId("Oliver Hardy").follow(actor_to_film).followR(actor_to_film)
   126  			var b = getId("Mel Blanc").follow(actor_to_film).followR(actor_to_film)
   127  			var c = getId("Billy Gilbert").follow(actor_to_film).followR(actor_to_film)
   128  
   129  			seen = {}
   130  
   131  			a.intersect(b).intersect(c).forEach(function (d) {
   132  				if (!(d.id in seen)) {
   133  					seen[d.id] = true;
   134  					g.emit(d)
   135  				}
   136  			})
   137  			`,
   138  		expect: []interface{}{
   139  			map[string]string{"id": "</en/sterling_holloway>"},
   140  			map[string]string{"id": "</en/billy_gilbert>"},
   141  		},
   142  	},
   143  
   144  	// This is more of an optimization problem that will get better over time. This takes a lot
   145  	// of wrong turns on the walk down to what is ultimately the name, but top AND has it easy
   146  	// as it has a fixed ID. Exercises Contains().
   147  	{
   148  		message: "the helpless checker",
   149  		long:    true,
   150  		query: `
   151  			g.V().as("person").in("<name>").in().in().out("<name>").is("Casablanca").all()
   152  			`,
   153  		tag: "person",
   154  		expect: []interface{}{
   155  			map[string]string{"id": "Casablanca", "person": "Ingrid Bergman"},
   156  			map[string]string{"id": "Casablanca", "person": "Madeleine LeBeau"},
   157  			map[string]string{"id": "Casablanca", "person": "Joy Page"},
   158  			map[string]string{"id": "Casablanca", "person": "Claude Rains"},
   159  			map[string]string{"id": "Casablanca", "person": "S.Z. Sakall"},
   160  			map[string]string{"id": "Casablanca", "person": "Helmut Dantine"},
   161  			map[string]string{"id": "Casablanca", "person": "Conrad Veidt"},
   162  			map[string]string{"id": "Casablanca", "person": "Paul Henreid"},
   163  			map[string]string{"id": "Casablanca", "person": "Peter Lorre"},
   164  			map[string]string{"id": "Casablanca", "person": "Sydney Greenstreet"},
   165  			map[string]string{"id": "Casablanca", "person": "Leonid Kinskey"},
   166  			map[string]string{"id": "Casablanca", "person": "Lou Marcelle"},
   167  			map[string]string{"id": "Casablanca", "person": "Dooley Wilson"},
   168  			map[string]string{"id": "Casablanca", "person": "John Qualen"},
   169  			map[string]string{"id": "Casablanca", "person": "Humphrey Bogart"},
   170  		},
   171  	},
   172  
   173  	// Exercises Not().Contains(), as above.
   174  	{
   175  		message: "the helpless checker, negated (films without Ingrid Bergman)",
   176  		long:    true,
   177  		query: `
   178  			g.V().as("person").in("<name>").in().in().out("<name>").except(g.V("Ingrid Bergman").in("<name>").in().in().out("<name>")).is("Casablanca").all()
   179  			`,
   180  		tag:    "person",
   181  		expect: nil,
   182  	},
   183  	{
   184  		message: "the helpless checker, negated (without actors Ingrid Bergman)",
   185  		long:    true,
   186  		query: `
   187  			g.V().as("person").in("<name>").except(g.V("Ingrid Bergman").in("<name>")).in().in().out("<name>").is("Casablanca").all()
   188  			`,
   189  		tag: "person",
   190  		expect: []interface{}{
   191  			map[string]string{"id": "Casablanca", "person": "Madeleine LeBeau"},
   192  			map[string]string{"id": "Casablanca", "person": "Joy Page"},
   193  			map[string]string{"id": "Casablanca", "person": "Claude Rains"},
   194  			map[string]string{"id": "Casablanca", "person": "S.Z. Sakall"},
   195  			map[string]string{"id": "Casablanca", "person": "Helmut Dantine"},
   196  			map[string]string{"id": "Casablanca", "person": "Conrad Veidt"},
   197  			map[string]string{"id": "Casablanca", "person": "Paul Henreid"},
   198  			map[string]string{"id": "Casablanca", "person": "Peter Lorre"},
   199  			map[string]string{"id": "Casablanca", "person": "Sydney Greenstreet"},
   200  			map[string]string{"id": "Casablanca", "person": "Leonid Kinskey"},
   201  			map[string]string{"id": "Casablanca", "person": "Lou Marcelle"},
   202  			map[string]string{"id": "Casablanca", "person": "Dooley Wilson"},
   203  			map[string]string{"id": "Casablanca", "person": "John Qualen"},
   204  			map[string]string{"id": "Casablanca", "person": "Humphrey Bogart"},
   205  		},
   206  	},
   207  
   208  	//Q: Who starred in both "The Net" and "Speed" ?
   209  	//A: "Sandra Bullock"
   210  	{
   211  		message: "Net and Speed",
   212  		query: common + `m1_actors.intersect(m2_actors).out("<name>").all()
   213  `,
   214  		expect: []interface{}{
   215  			map[string]string{"id": SandraB, "movie1": "The Net", "movie2": nSpeed},
   216  		},
   217  	},
   218  
   219  	//Q: Did "Keanu Reeves" star in "The Net" ?
   220  	//A: No
   221  	{
   222  		message: "Keanu in The Net",
   223  		query: common + `actor2.intersect(m1_actors).out("<name>").all()
   224  `,
   225  		expect: nil,
   226  	},
   227  
   228  	//Q: Did "Keanu Reeves" star in "Speed" ?
   229  	//A: Yes
   230  	{
   231  		message: "Keanu in Speed",
   232  		query: common + `actor2.intersect(m2_actors).out("<name>").all()
   233  `,
   234  		expect: []interface{}{
   235  			map[string]string{"id": KeanuR, "movie2": nSpeed},
   236  		},
   237  	},
   238  
   239  	//Q: Has "Keanu Reeves" co-starred with anyone who starred in "The Net" ?
   240  	//A: "Keanu Reeves" was in "Speed" and "The Lake House" with "Sandra Bullock",
   241  	//   who was in "The Net"
   242  	{
   243  		message: "Keanu with other in The Net",
   244  		long:    true,
   245  		query: common + `actor2.follow(coStars1).intersect(m1_actors).out("<name>").all()
   246  `,
   247  		expect: []interface{}{
   248  			map[string]string{"id": SandraB, "movie1": "The Net", "costar1_movie": nSpeed},
   249  			map[string]string{"movie1": "The Net", "costar1_movie": nLakeH, "id": SandraB},
   250  		},
   251  	},
   252  
   253  	//Q: Do "Keanu Reeves" and "Sandra Bullock" have any commons co-stars?
   254  	//A: Yes, many. For example: SB starred with "Steve Martin" in "The Prince
   255  	//    of Egypt", and KR starred with Steven Martin in "Parenthood".
   256  	{
   257  		message: "Keanu and Bullock with other",
   258  		long:    true,
   259  		query: common + `actor1.save("<name>","costar1_actor").follow(coStars1).intersect(actor2.save("<name>","costar2_actor").follow(coStars2)).out("<name>").all()
   260  `,
   261  		expect: []interface{}{
   262  			costarTag(SandraB, SandraB, "The Proposal", KeanuR, nSpeed),
   263  			costarTag(SandraB, SandraB, "The Proposal", KeanuR, nLakeH),
   264  			costarTag("Mary Steenburgen", SandraB, "The Proposal", KeanuR, "Parenthood"),
   265  			costarTag("Craig T. Nelson", SandraB, "The Proposal", KeanuR, "The Devil's Advocate"),
   266  			costarTag(SandraB, SandraB, "Crash", KeanuR, nSpeed),
   267  			costarTag(SandraB, SandraB, "Crash", KeanuR, nLakeH),
   268  			costarTag(SandraB, SandraB, "Gun Shy", KeanuR, nSpeed),
   269  			costarTag(SandraB, SandraB, "Gun Shy", KeanuR, nLakeH),
   270  			costarTag(SandraB, SandraB, "Demolition Man", KeanuR, nSpeed),
   271  			costarTag(SandraB, SandraB, "Demolition Man", KeanuR, nLakeH),
   272  			costarTag("Benjamin Bratt", SandraB, "Demolition Man", KeanuR, "Thumbsucker"),
   273  			costarTag(SandraB, SandraB, "Divine Secrets of the Ya-Ya Sisterhood", KeanuR, nSpeed),
   274  			costarTag(SandraB, SandraB, "Divine Secrets of the Ya-Ya Sisterhood", KeanuR, nLakeH),
   275  			costarTag("Shirley Knight", SandraB, "Divine Secrets of the Ya-Ya Sisterhood", KeanuR, "The Private Lives of Pippa Lee"),
   276  			costarTag(SandraB, SandraB, "A Time to Kill", KeanuR, nSpeed),
   277  			costarTag(SandraB, SandraB, "A Time to Kill", KeanuR, nLakeH),
   278  			costarTag(SandraB, SandraB, "Forces of Nature", KeanuR, nSpeed),
   279  			costarTag(SandraB, SandraB, "Forces of Nature", KeanuR, nLakeH),
   280  			costarTag(SandraB, SandraB, "Hope Floats", KeanuR, nSpeed),
   281  			costarTag(SandraB, SandraB, "Hope Floats", KeanuR, nLakeH),
   282  			costarTag(SandraB, SandraB, "Infamous", KeanuR, nSpeed),
   283  			costarTag(SandraB, SandraB, "Infamous", KeanuR, nLakeH),
   284  			costarTag("Jeff Daniels", SandraB, "Infamous", KeanuR, nSpeed),
   285  			costarTag(SandraB, SandraB, "Love Potion No. 9", KeanuR, nSpeed),
   286  			costarTag(SandraB, SandraB, "Love Potion No. 9", KeanuR, nLakeH),
   287  			costarTag(SandraB, SandraB, "Miss Congeniality", KeanuR, nSpeed),
   288  			costarTag(SandraB, SandraB, "Miss Congeniality", KeanuR, nLakeH),
   289  			costarTag("Benjamin Bratt", SandraB, "Miss Congeniality", KeanuR, "Thumbsucker"),
   290  			costarTag(SandraB, SandraB, "Miss Congeniality 2: Armed and Fabulous", KeanuR, nSpeed),
   291  			costarTag(SandraB, SandraB, "Miss Congeniality 2: Armed and Fabulous", KeanuR, nLakeH),
   292  			costarTag(SandraB, SandraB, "Murder by Numbers", KeanuR, nSpeed),
   293  			costarTag(SandraB, SandraB, "Murder by Numbers", KeanuR, nLakeH),
   294  			costarTag(SandraB, SandraB, "Practical Magic", KeanuR, nSpeed),
   295  			costarTag(SandraB, SandraB, "Practical Magic", KeanuR, nLakeH),
   296  			costarTag("Dianne Wiest", SandraB, "Practical Magic", KeanuR, "Parenthood"),
   297  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Flying"),
   298  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Animatrix"),
   299  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Tune in Tomorrow"),
   300  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Last Time I Committed Suicide"),
   301  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Constantine"),
   302  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Permanent Record"),
   303  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Dangerous Liaisons"),
   304  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Private Lives of Pippa Lee"),
   305  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "A Scanner Darkly"),
   306  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "A Walk in the Clouds"),
   307  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Hardball"),
   308  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Life Under Water"),
   309  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Much Ado About Nothing"),
   310  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "My Own Private Idaho"),
   311  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Parenthood"),
   312  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Point Break"),
   313  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Providence"),
   314  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "River's Edge"),
   315  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Something's Gotta Give"),
   316  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, nSpeed),
   317  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Sweet November"),
   318  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, nLakeH),
   319  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Matrix Reloaded"),
   320  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Matrix Revisited"),
   321  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Prince of Pennsylvania"),
   322  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Replacements"),
   323  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Even Cowgirls Get the Blues"),
   324  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Youngblood"),
   325  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Bill & Ted's Bogus Journey"),
   326  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Bill & Ted's Excellent Adventure"),
   327  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Johnny Mnemonic"),
   328  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Devil's Advocate"),
   329  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Thumbsucker"),
   330  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "I Love You to Death"),
   331  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Bram Stoker's Dracula"),
   332  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Gift"),
   333  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Little Buddha"),
   334  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Night Watchman"),
   335  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Chain Reaction"),
   336  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "Babes in Toyland"),
   337  			costarTag(KeanuR, SandraB, nSpeed, KeanuR, "The Day the Earth Stood Still"),
   338  			costarTag(SandraB, SandraB, nSpeed, KeanuR, nSpeed),
   339  			costarTag(SandraB, SandraB, nSpeed, KeanuR, nLakeH),
   340  			costarTag("Dennis Hopper", SandraB, nSpeed, KeanuR, "River's Edge"),
   341  			costarTag("Dennis Hopper", SandraB, nSpeed, KeanuR, nSpeed),
   342  			costarTag("Jeff Daniels", SandraB, nSpeed, KeanuR, nSpeed),
   343  			costarTag("Joe Morton", SandraB, nSpeed, KeanuR, nSpeed),
   344  			costarTag("Alan Ruck", SandraB, nSpeed, KeanuR, nSpeed),
   345  			costarTag("Glenn Plummer", SandraB, nSpeed, KeanuR, nSpeed),
   346  			costarTag("Carlos Carrasco", SandraB, nSpeed, KeanuR, nSpeed),
   347  			costarTag("Beth Grant", SandraB, nSpeed, KeanuR, nSpeed),
   348  			costarTag("Richard Lineback", SandraB, nSpeed, KeanuR, nSpeed),
   349  			costarTag("Hawthorne James", SandraB, nSpeed, KeanuR, nSpeed),
   350  			costarTag("Jordan Lund", SandraB, nSpeed, KeanuR, nSpeed),
   351  			costarTag("Thomas Rosales, Jr.", SandraB, nSpeed, KeanuR, nSpeed),
   352  			costarTag(SandraB, SandraB, "Speed 2: Cruise Control", KeanuR, nSpeed),
   353  			costarTag(SandraB, SandraB, "Speed 2: Cruise Control", KeanuR, nLakeH),
   354  			costarTag("Glenn Plummer", SandraB, "Speed 2: Cruise Control", KeanuR, nSpeed),
   355  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Flying"),
   356  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Animatrix"),
   357  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Tune in Tomorrow"),
   358  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Last Time I Committed Suicide"),
   359  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Constantine"),
   360  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Permanent Record"),
   361  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Dangerous Liaisons"),
   362  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Private Lives of Pippa Lee"),
   363  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "A Scanner Darkly"),
   364  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "A Walk in the Clouds"),
   365  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Hardball"),
   366  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Life Under Water"),
   367  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Much Ado About Nothing"),
   368  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "My Own Private Idaho"),
   369  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Parenthood"),
   370  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Point Break"),
   371  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Providence"),
   372  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "River's Edge"),
   373  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Something's Gotta Give"),
   374  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, nSpeed),
   375  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Sweet November"),
   376  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, nLakeH),
   377  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Matrix Reloaded"),
   378  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Matrix Revisited"),
   379  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Prince of Pennsylvania"),
   380  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Replacements"),
   381  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Even Cowgirls Get the Blues"),
   382  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Youngblood"),
   383  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Bill & Ted's Bogus Journey"),
   384  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Bill & Ted's Excellent Adventure"),
   385  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Johnny Mnemonic"),
   386  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Devil's Advocate"),
   387  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Thumbsucker"),
   388  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "I Love You to Death"),
   389  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Bram Stoker's Dracula"),
   390  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Gift"),
   391  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Little Buddha"),
   392  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Night Watchman"),
   393  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Chain Reaction"),
   394  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "Babes in Toyland"),
   395  			costarTag(KeanuR, SandraB, nLakeH, KeanuR, "The Day the Earth Stood Still"),
   396  			costarTag(SandraB, SandraB, nLakeH, KeanuR, nSpeed),
   397  			costarTag(SandraB, SandraB, nLakeH, KeanuR, nLakeH),
   398  			costarTag("Christopher Plummer", SandraB, nLakeH, KeanuR, nLakeH),
   399  			costarTag("Dylan Walsh", SandraB, nLakeH, KeanuR, nLakeH),
   400  			costarTag("Shohreh Aghdashloo", SandraB, nLakeH, KeanuR, nLakeH),
   401  			costarTag("Lynn Collins", SandraB, nLakeH, KeanuR, nLakeH),
   402  			costarTag(SandraB, SandraB, "The Net", KeanuR, nSpeed),
   403  			costarTag(SandraB, SandraB, "The Net", KeanuR, nLakeH),
   404  			costarTag("Michelle Pfeiffer", SandraB, "The Prince of Egypt", KeanuR, "Dangerous Liaisons"),
   405  			costarTag(SandraB, SandraB, "The Prince of Egypt", KeanuR, nSpeed),
   406  			costarTag(SandraB, SandraB, "The Prince of Egypt", KeanuR, nLakeH),
   407  			costarTag("Steve Martin", SandraB, "The Prince of Egypt", KeanuR, "Parenthood"),
   408  			costarTag(SandraB, SandraB, "Two Weeks Notice", KeanuR, nSpeed),
   409  			costarTag(SandraB, SandraB, "Two Weeks Notice", KeanuR, nLakeH),
   410  			costarTag(SandraB, SandraB, "While You Were Sleeping", KeanuR, nSpeed),
   411  			costarTag(SandraB, SandraB, "While You Were Sleeping", KeanuR, nLakeH),
   412  			costarTag("Jack Warden", SandraB, "While You Were Sleeping", KeanuR, "The Replacements"),
   413  			costarTag(SandraB, SandraB, "28 Days", KeanuR, nSpeed),
   414  			costarTag(SandraB, SandraB, "28 Days", KeanuR, nLakeH),
   415  			costarTag(SandraB, SandraB, "Premonition", KeanuR, nSpeed),
   416  			costarTag(SandraB, SandraB, "Premonition", KeanuR, nLakeH),
   417  			costarTag("Peter Stormare", SandraB, "Premonition", KeanuR, "Constantine"),
   418  			costarTag(SandraB, SandraB, "Wrestling Ernest Hemingway", KeanuR, nSpeed),
   419  			costarTag(SandraB, SandraB, "Wrestling Ernest Hemingway", KeanuR, nLakeH),
   420  			costarTag(SandraB, SandraB, "Fire on the Amazon", KeanuR, nSpeed),
   421  			costarTag(SandraB, SandraB, "Fire on the Amazon", KeanuR, nLakeH),
   422  			costarTag("River Phoenix", SandraB, "The Thing Called Love", KeanuR, "My Own Private Idaho"),
   423  			costarTag("River Phoenix", SandraB, "The Thing Called Love", KeanuR, "I Love You to Death"),
   424  			costarTag(SandraB, SandraB, "The Thing Called Love", KeanuR, nSpeed),
   425  			costarTag(SandraB, SandraB, "The Thing Called Love", KeanuR, nLakeH),
   426  			costarTag(SandraB, SandraB, "In Love and War", KeanuR, nSpeed),
   427  			costarTag(SandraB, SandraB, "In Love and War", KeanuR, nLakeH),
   428  		},
   429  	},
   430  	{
   431  		message: "Save a number of predicates around a set of nodes",
   432  		query: `
   433  		g.V("_:9037", "_:49278", "_:44112", "_:44709", "_:43382").save("</film/performance/character>", "char").save("</film/performance/actor>", "act").saveR("</film/film/starring>", "film").all()
   434  		`,
   435  		expect: []interface{}{
   436  			map[string]string{"act": "</en/humphrey_bogart>", "char": "Rick Blaine", "film": "</en/casablanca_1942>", "id": "_:9037"},
   437  			map[string]string{"act": "</en/humphrey_bogart>", "char": "Sam Spade", "film": "</en/the_maltese_falcon_1941>", "id": "_:49278"},
   438  			map[string]string{"act": "</en/humphrey_bogart>", "char": "Philip Marlowe", "film": "</en/the_big_sleep_1946>", "id": "_:44112"},
   439  			map[string]string{"act": "</en/humphrey_bogart>", "char": "Captain Queeg", "film": "</en/the_caine_mutiny_1954>", "id": "_:44709"},
   440  			map[string]string{"act": "</en/humphrey_bogart>", "char": "Charlie Allnut", "film": "</en/the_african_queen>", "id": "_:43382"},
   441  		},
   442  	},
   443  }
   444  
   445  const common = `
   446  var movie1 = g.V().has("<name>", "The Net")
   447  var movie2 = g.V().has("<name>", "Speed")
   448  var actor1 = g.V().has("<name>", "Sandra Bullock")
   449  var actor2 = g.V().has("<name>", "Keanu Reeves")
   450  
   451  // (film) -> starring -> (actor)
   452  var filmToActor = g.Morphism().out("</film/film/starring>").out("</film/performance/actor>")
   453  
   454  // (actor) -> starring -> [film -> starring -> (actor)]
   455  var coStars1 = g.Morphism().in("</film/performance/actor>").in("</film/film/starring>").save("<name>","costar1_movie").follow(filmToActor)
   456  var coStars2 = g.Morphism().in("</film/performance/actor>").in("</film/film/starring>").save("<name>","costar2_movie").follow(filmToActor)
   457  
   458  // Stars for the movies "The Net" and "Speed"
   459  var m1_actors = movie1.save("<name>","movie1").follow(filmToActor)
   460  var m2_actors = movie2.save("<name>","movie2").follow(filmToActor)
   461  `
   462  
   463  func prepare(t testing.TB, gen testutil.DatabaseFunc) (graph.QuadStore, func()) {
   464  	qs, _, closer := gen(t)
   465  
   466  	const needsLoad = true // TODO: support local setup
   467  	if needsLoad {
   468  		qw, err := qs.NewQuadWriter()
   469  		if err != nil {
   470  			closer()
   471  			require.NoError(t, err)
   472  		}
   473  
   474  		start := time.Now()
   475  		for _, p := range []string{"./", "../"} {
   476  			err = internal.Load(qw, 0, filepath.Join(p, "../../data/30kmoviedata.nq.gz"), format)
   477  			if err == nil || !os.IsNotExist(err) {
   478  				break
   479  			}
   480  		}
   481  		if err != nil {
   482  			qw.Close()
   483  			closer()
   484  			require.NoError(t, err)
   485  		}
   486  		err = qw.Close()
   487  		if err != nil {
   488  			closer()
   489  			require.NoError(t, err)
   490  		}
   491  		t.Logf("loaded data in %v", time.Since(start))
   492  	}
   493  	return qs, closer
   494  }
   495  
   496  func checkQueries(t *testing.T, qs graph.QuadStore, timeout time.Duration) {
   497  	if qs == nil {
   498  		t.Fatal("not initialized")
   499  	}
   500  	for _, test := range queries {
   501  		t.Run(test.message, func(t *testing.T) {
   502  			if testing.Short() && test.long {
   503  				t.SkipNow()
   504  			}
   505  			if test.skip {
   506  				t.SkipNow()
   507  			}
   508  			start := time.Now()
   509  			ses := gizmo.NewSession(qs)
   510  			ctx := context.Background()
   511  			if timeout > 0 {
   512  				var cancel func()
   513  				ctx, cancel = context.WithTimeout(ctx, timeout)
   514  				defer cancel()
   515  			}
   516  			it, err := ses.Execute(ctx, test.query, query.Options{
   517  				Collation: query.JSON,
   518  			})
   519  			if err != nil {
   520  				t.Fatal(err)
   521  			}
   522  			defer it.Close()
   523  			var got []interface{}
   524  			for it.Next(ctx) {
   525  				got = append(got, it.Result())
   526  			}
   527  			t.Logf("%12v %v", time.Since(start), test.message)
   528  
   529  			if len(got) != len(test.expect) {
   530  				t.Errorf("Unexpected number of results, got:%d expect:%d on %s.", len(got), len(test.expect), test.message)
   531  				return
   532  			}
   533  			if unsortedEqual(got, test.expect) {
   534  				return
   535  			}
   536  			t.Errorf("Unexpected results for %s:\n", test.message)
   537  			for i := range got {
   538  				t.Errorf("\n\tgot:%#v\n\texpect:%#v\n", got[i], test.expect[i])
   539  			}
   540  		})
   541  	}
   542  }
   543  
   544  func unsortedEqual(got, expect []interface{}) bool {
   545  	gotList := convertToStringList(got)
   546  	expectList := convertToStringList(expect)
   547  	return reflect.DeepEqual(gotList, expectList)
   548  }
   549  
   550  func convertToStringList(in []interface{}) []string {
   551  	var out []string
   552  	for _, x := range in {
   553  		if xc, ok := x.(map[string]string); ok {
   554  			for k, v := range xc {
   555  				out = append(out, fmt.Sprint(k, ":", v))
   556  			}
   557  		} else {
   558  			for k, v := range x.(map[string]interface{}) {
   559  				out = append(out, fmt.Sprint(k, ":", v))
   560  			}
   561  		}
   562  	}
   563  	sort.Strings(out)
   564  	return out
   565  }
   566  
   567  func benchmarkQueries(b *testing.B, gen testutil.DatabaseFunc) {
   568  	qs, closer := prepare(b, gen)
   569  	defer closer()
   570  
   571  	for _, bench := range queries {
   572  		b.Run(bench.message, func(b *testing.B) {
   573  			if testing.Short() && bench.long {
   574  				b.Skip()
   575  			}
   576  			b.StopTimer()
   577  			b.ResetTimer()
   578  			for i := 0; i < b.N; i++ {
   579  				func() {
   580  					ctx := context.Background()
   581  					if timeout > 0 {
   582  						var cancel func()
   583  						ctx, cancel = context.WithTimeout(ctx, timeout)
   584  						defer cancel()
   585  					}
   586  					ses := gizmo.NewSession(qs)
   587  					b.StartTimer()
   588  					it, err := ses.Execute(ctx, bench.query, query.Options{
   589  						Collation: query.Raw,
   590  					})
   591  					if err != nil {
   592  						b.Fatal(err)
   593  					}
   594  					defer it.Close()
   595  					n := 0
   596  					for it.Next(ctx) {
   597  						n++
   598  					}
   599  					if err = it.Err(); err != nil {
   600  						b.Fatal(err)
   601  					}
   602  					b.StopTimer()
   603  					if n != len(bench.expect) {
   604  						b.Fatalf("unexpected number of results: %d vs %d", n, len(bench.expect))
   605  					}
   606  				}()
   607  			}
   608  		})
   609  	}
   610  }