Skip to content

Commit

Permalink
tests: add datadriven test
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepymole committed Mar 26, 2023
1 parent a4dd651 commit 4428d11
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 110 deletions.
98 changes: 1 addition & 97 deletions db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package zgraph

import (
"context"
"sort"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -47,7 +46,7 @@ func (tk *TestKit) MustExec(ctx context.Context, query string) {
require.NoError(tk.t, rs.Next(ctx))
}

func TestDBDDL(t *testing.T) {
func TestDDL(t *testing.T) {
db, err := Open(t.TempDir(), nil)
require.NoError(t, err)
require.NotNil(t, db)
Expand Down Expand Up @@ -85,98 +84,3 @@ func TestDBDDL(t *testing.T) {
require.NoError(t, err)
require.Nil(t, catalog.Graph("graph101"))
}

func TestDBSelect(t *testing.T) {
db, err := Open(t.TempDir(), nil)
require.NoError(t, err)
require.NotNil(t, db)
defer db.Close()

sess := db.NewSession()
require.NotNil(t, sess)
tk := NewTestKit(t, sess)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

tk.MustExec(ctx, "CREATE GRAPH student_network")
tk.MustExec(ctx, "USE student_network")
tk.MustExec(ctx, "CREATE LABEL Person")
tk.MustExec(ctx, "CREATE LABEL University")
tk.MustExec(ctx, "CREATE LABEL knows")
tk.MustExec(ctx, "CREATE LABEL studentOf")

// A simple example in https://pgql-lang.org/spec/1.5/#edge-patterns.
tk.MustExec(ctx, `INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Kathrine', x.dob = DATE '1994-01-15')`)
tk.MustExec(ctx, `INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Riya', x.dob = DATE '1995-03-20')`)
tk.MustExec(ctx, `INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Lee', x.dob = DATE '1996-01-20')`)
tk.MustExec(ctx, `INSERT VERTEX x LABELS (University) PROPERTIES (x.name = 'UC Berkeley')`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'Lee'`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'Riya'`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Lee' AND y.name = 'Kathrine'`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'UC Berkeley'`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Lee' AND y.name = 'UC Berkeley'`)
tk.MustExec(ctx, `INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Riya' AND y.name = 'UC Berkeley'`)

rs, err := sess.Execute(ctx, `SELECT a.name AS a, b.name AS b FROM MATCH (a:Person) -[e:knows]-> (b:Person)`)
require.NoError(t, err)
require.Len(t, rs.Fields(), 2)

var knows [][2]string
for {
require.NoError(t, rs.Next(ctx))
if !rs.Valid() {
break
}
var a, b string
require.NoError(t, rs.Scan(&a, &b))
knows = append(knows, [2]string{a, b})
}
sort.Slice(knows, func(i, j int) bool {
return knows[i][0] < knows[j][0] || (knows[i][0] == knows[j][0] && knows[i][1] < knows[j][1])
})
require.Len(t, knows, 3)
require.Equal(t, [][2]string{{"Kathrine", "Lee"}, {"Kathrine", "Riya"}, {"Lee", "Kathrine"}}, knows)

// A more complex example in https://pgql-lang.org/spec/1.5/#more-complex-patterns.
// TODO: currently we assume every edge and vertex has explicit name, so here we assign a name to each edge.
rs, err = sess.Execute(ctx, `SELECT p2.name AS friend, u.name AS university
FROM MATCH (u:University) <-[anon1:studentOf]- (p1:Person) -[anon2:knows]-> (p2:Person) -[anon3:studentOf]-> (u)
WHERE p1.name = 'Lee'`)
require.NoError(t, err)
require.Len(t, rs.Fields(), 2)

var friend, university string
require.NoError(t, rs.Next(ctx))
require.True(t, rs.Valid())
require.NoError(t, rs.Scan(&friend, &university))
require.NoError(t, rs.Next(ctx))
require.False(t, rs.Valid(), "only one row should be returned")
require.Equal(t, "Kathrine", friend)
require.Equal(t, "UC Berkeley", university)

// An example in https://pgql-lang.org/spec/1.5/#binding-an-element-multiple-times.
// TODO: currently we assume every edge and vertex has explicit name, so here we assign a name to each edge.
rs, err = sess.Execute(ctx, `SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3
FROM MATCH (p1:Person) -[anon1:knows]-> (p2:Person) -[anon2:knows]-> (p3:Person)
WHERE p1.name = 'Lee'`)
require.NoError(t, err)
require.Len(t, rs.Fields(), 3)
var tuples [][3]string
for {
require.NoError(t, rs.Next(ctx))
if !rs.Valid() {
break
}
var p1, p2, p3 string
require.NoError(t, rs.Scan(&p1, &p2, &p3))
tuples = append(tuples, [3]string{p1, p2, p3})
}
sort.Slice(tuples, func(i, j int) bool {
return tuples[i][0] < tuples[j][0] ||
(tuples[i][0] == tuples[j][0] && tuples[i][1] < tuples[j][1]) ||
(tuples[i][0] == tuples[j][0] && tuples[i][1] == tuples[j][1] && tuples[i][2] < tuples[j][2])
})
require.Len(t, tuples, 2)
require.Equal(t, [][3]string{{"Lee", "Kathrine", "Lee"}, {"Lee", "Kathrine", "Riya"}}, tuples)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/charmbracelet/bubbles v0.14.1-0.20221007152719-9a48dca00354 // indirect
github.com/charmbracelet/lipgloss v0.6.0 // indirect
github.com/cockroachdb/datadriven v1.0.2 // indirect
github.com/cockroachdb/errors v1.9.0 // indirect
github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
Expand Down
13 changes: 0 additions & 13 deletions tests/run.sh

This file was deleted.

45 changes: 45 additions & 0 deletions tests/student_network_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2023 zGraph Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests_test

import (
"testing"

_ "github.com/vescale/zgraph"
)

// An example in https://pgql-lang.org/spec/1.5/#edge-patterns.
var initStudentNetwork = []string{
`CREATE GRAPH student_network`,
`USE student_network`,
`CREATE LABEL Person`,
`CREATE LABEL University`,
`CREATE LABEL knows`,
`CREATE LABEL studentOf`,
`INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Kathrine', x.dob = DATE '1994-01-15')`,
`INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Riya', x.dob = DATE '1995-03-20')`,
`INSERT VERTEX x LABELS (Person) PROPERTIES (x.name = 'Lee', x.dob = DATE '1996-01-20')`,
`INSERT VERTEX x LABELS (University) PROPERTIES (x.name = 'UC Berkeley')`,
`INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'Lee'`,
`INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'Riya'`,
`INSERT EDGE e BETWEEN x AND y LABELS ( knows ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Lee' AND y.name = 'Kathrine'`,
`INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Kathrine' AND y.name = 'UC Berkeley'`,
`INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Riya' AND y.name = 'UC Berkeley'`,
`INSERT EDGE e BETWEEN x AND y LABELS ( studentOf ) FROM MATCH (x), MATCH (y) WHERE x.name = 'Lee' AND y.name = 'UC Berkeley'`,
}

func TestStudentNetwork(t *testing.T) {
runDataDrivenTest(t, "testdata/student_network", initStudentNetwork)
}
24 changes: 24 additions & 0 deletions tests/testdata/student_network
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
simple
SELECT a.name AS a, b.name AS b FROM MATCH (a:Person) -[e:knows]-> (b:Person);
----
a,b
Kathrine,Lee
Kathrine,Riya
Lee,Kathrine

more-complex
SELECT p2.name AS friend, u.name AS university
FROM MATCH (u:University) <-[anon1:studentOf]- (p1:Person) -[anon2:knows]-> (p2:Person) -[anon3:studentOf]-> (u)
WHERE p1.name = 'Lee';
----
friend,university
Kathrine,UC Berkeley

binding-an-element-multiple-times
SELECT p1.name AS p1, p2.name AS p2, p3.name AS p3
FROM MATCH (p1:Person) -[anon1:knows]-> (p2:Person) -[anon2:knows]-> (p3:Person)
WHERE p1.name = 'Lee';
----
p1,p2,p3
Lee,Kathrine,Lee
Lee,Kathrine,Riya
99 changes: 99 additions & 0 deletions tests/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2023 zGraph Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tests_test

import (
"database/sql"
"sort"
"strings"
"testing"

"github.com/cockroachdb/datadriven"
"github.com/stretchr/testify/require"
)

func runDataDrivenTest(t *testing.T, path string, initSQLs []string) {
t.Helper()
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
db, err := initDB(t.TempDir(), initSQLs)
require.NoError(t, err)
defer db.Close()

rows, err := db.Query(d.Input)
require.NoError(t, err)
defer rows.Close()

result, err := combineRows(rows)
require.NoError(t, err)
// Sort the result to make it deterministic.
// TODO: It's not a good idea to sort the result, we should make query result deterministic instead.
lines := strings.Split(strings.TrimSpace(result), "\n")
sort.Strings(lines[1:])
result = strings.Join(lines, "\n")
return result
})
}

func initDB(path string, sqls []string) (*sql.DB, error) {
db, err := sql.Open("zgraph", path)
if err != nil {
return nil, err
}
for _, q := range sqls {
_, err = db.Exec(q)
if err != nil {
_ = db.Close()
return nil, err
}
}
return db, nil
}

func combineRows(rows *sql.Rows) (string, error) {
cols, err := rows.Columns()
if err != nil {
return "", err
}

var sb strings.Builder
for i, col := range cols {
sb.WriteString(col)
if i+1 == len(cols) {
sb.WriteByte('\n')
} else {
sb.WriteString(",")
}
}

dest := make([]any, len(cols))
for i := 0; i < len(cols); i++ {
var anyStr sql.NullString
dest[i] = &anyStr
}
for rows.Next() {
if err := rows.Scan(dest...); err != nil {
return "", err
}
for i := 0; i < len(cols); i++ {
sb.WriteString(dest[i].(*sql.NullString).String)
if i+1 == len(cols) {
sb.WriteByte('\n')
} else {
sb.WriteString(",")
}
}
}
return sb.String(), nil
}

0 comments on commit 4428d11

Please sign in to comment.