From af23726d3967468e4320b0c20f7a3d4997cb504b Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Tue, 13 Dec 2022 15:19:19 -0500 Subject: [PATCH 1/4] Add event tests --- tests/integration/events/simple/utils.go | 27 +++ .../events/simple/with_create_test.go | 66 +++++++ .../events/simple/with_create_txn_test.go | 67 +++++++ .../events/simple/with_delete_test.go | 59 ++++++ .../events/simple/with_update_test.go | 77 ++++++++ tests/integration/events/utils.go | 184 ++++++++++++++++++ 6 files changed, 480 insertions(+) create mode 100644 tests/integration/events/simple/utils.go create mode 100644 tests/integration/events/simple/with_create_test.go create mode 100644 tests/integration/events/simple/with_create_txn_test.go create mode 100644 tests/integration/events/simple/with_delete_test.go create mode 100644 tests/integration/events/simple/with_update_test.go create mode 100644 tests/integration/events/utils.go diff --git a/tests/integration/events/simple/utils.go b/tests/integration/events/simple/utils.go new file mode 100644 index 0000000000..88a1ec543e --- /dev/null +++ b/tests/integration/events/simple/utils.go @@ -0,0 +1,27 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package simple + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration/events" +) + +var schema = ` + type users { + Name: String + } +` + +func executeTestCase(t *testing.T, test testUtils.TestCase) { + testUtils.ExecuteQueryTestCase(t, schema, test) +} diff --git a/tests/integration/events/simple/with_create_test.go b/tests/integration/events/simple/with_create_test.go new file mode 100644 index 0000000000..a1515e9de9 --- /dev/null +++ b/tests/integration/events/simple/with_create_test.go @@ -0,0 +1,66 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package simple + +import ( + "context" + "testing" + + "github.com/sourcenetwork/immutable" + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/client" + testUtils "github.com/sourcenetwork/defradb/tests/integration/events" +) + +func TestEventsSimpleWithCreate(t *testing.T) { + doc1, err := client.NewDocFromJSON( + []byte( + `{ + "Name": "John" + }`, + ), + ) + assert.Nil(t, err) + doc2, err := client.NewDocFromJSON( + []byte( + `{ + "Name": "Shahzad" + }`, + ), + ) + assert.Nil(t, err) + + test := testUtils.TestCase{ + CollectionCalls: map[string][]func(client.Collection){ + "users": []func(c client.Collection){ + func(c client.Collection) { + err = c.Save(context.Background(), doc1) + assert.Nil(t, err) + }, + func(c client.Collection) { + err = c.Save(context.Background(), doc2) + assert.Nil(t, err) + }, + }, + }, + ExpectedUpdates: []testUtils.ExpectedUpdate{ + { + DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + }, + { + DocKey: immutable.Some("bae-359dfe55-77a4-59de-a687-fc5049dc61fe"), + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/events/simple/with_create_txn_test.go b/tests/integration/events/simple/with_create_txn_test.go new file mode 100644 index 0000000000..ea0aa21caa --- /dev/null +++ b/tests/integration/events/simple/with_create_txn_test.go @@ -0,0 +1,67 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package simple + +import ( + "context" + "testing" + + "github.com/sourcenetwork/immutable" + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/client" + testUtils "github.com/sourcenetwork/defradb/tests/integration/events" +) + +func TestEventsSimpleWithCreateWithTxnDiscarded(t *testing.T) { + test := testUtils.TestCase{ + DatabaseCalls: []func(context.Context, client.DB){ + func(ctx context.Context, d client.DB) { + r := d.ExecQuery( + ctx, + `mutation { + create_users(data: "{\"Name\": \"John\"}") { + _key + } + }`, + ) + for _, err := range r.GQL.Errors { + assert.Nil(t, err) + } + }, + func(ctx context.Context, d client.DB) { + txn, err := d.NewTxn(ctx, false) + assert.Nil(t, err) + r := d.ExecTransactionalQuery( + ctx, + `mutation { + create_users(data: "{\"Name\": \"Shahzad\"}") { + _key + } + }`, + txn, + ) + for _, err := range r.GQL.Errors { + assert.Nil(t, err) + } + txn.Discard(ctx) + }, + }, + ExpectedUpdates: []testUtils.ExpectedUpdate{ + { + DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + }, + // No event should be recieved for Shahzad, as the transaction was discarded. + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/events/simple/with_delete_test.go b/tests/integration/events/simple/with_delete_test.go new file mode 100644 index 0000000000..7f2fecdb52 --- /dev/null +++ b/tests/integration/events/simple/with_delete_test.go @@ -0,0 +1,59 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package simple + +import ( + "context" + "testing" + + "github.com/sourcenetwork/immutable" + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/client" + testUtils "github.com/sourcenetwork/defradb/tests/integration/events" +) + +// This test documents undesirable behaviour which should be corrected in +// https://github.com/sourcenetwork/defradb/issues/867 +func TestEventsSimpleWithDelete(t *testing.T) { + doc1, err := client.NewDocFromJSON( + []byte( + `{ + "Name": "John" + }`, + ), + ) + assert.Nil(t, err) + + test := testUtils.TestCase{ + CollectionCalls: map[string][]func(client.Collection){ + "users": []func(c client.Collection){ + func(c client.Collection) { + err = c.Save(context.Background(), doc1) + assert.Nil(t, err) + }, + func(c client.Collection) { + wasDeleted, err := c.Delete(context.Background(), doc1.Key()) + assert.Nil(t, err) + assert.True(t, wasDeleted) + }, + }, + }, + ExpectedUpdates: []testUtils.ExpectedUpdate{ + { + DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + }, + // No update to reflect the delete + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/events/simple/with_update_test.go b/tests/integration/events/simple/with_update_test.go new file mode 100644 index 0000000000..9c749a30c6 --- /dev/null +++ b/tests/integration/events/simple/with_update_test.go @@ -0,0 +1,77 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package simple + +import ( + "context" + "testing" + + "github.com/sourcenetwork/immutable" + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/client" + testUtils "github.com/sourcenetwork/defradb/tests/integration/events" +) + +func TestEventsSimpleWithUpdate(t *testing.T) { + doc1, err := client.NewDocFromJSON( + []byte( + `{ + "Name": "John" + }`, + ), + ) + assert.Nil(t, err) + doc2, err := client.NewDocFromJSON( + []byte( + `{ + "Name": "Shahzad" + }`, + ), + ) + assert.Nil(t, err) + + test := testUtils.TestCase{ + CollectionCalls: map[string][]func(client.Collection){ + "users": []func(c client.Collection){ + func(c client.Collection) { + err = c.Save(context.Background(), doc1) + assert.Nil(t, err) + }, + func(c client.Collection) { + err = c.Save(context.Background(), doc2) + assert.Nil(t, err) + }, + func(c client.Collection) { + // Update John + doc1.Set("Name", "Johnnnnn") + err = c.Save(context.Background(), doc1) + assert.Nil(t, err) + }, + }, + }, + ExpectedUpdates: []testUtils.ExpectedUpdate{ + { + DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + Cid: immutable.Some("bafybeihm4gewwhzsyqs7tuazdsbusp6pm3oinhnj2ae6funcwshwpxuxri"), + }, + { + DocKey: immutable.Some("bae-359dfe55-77a4-59de-a687-fc5049dc61fe"), + }, + { + DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + Cid: immutable.Some("bafybeicxo5sqjzi2mjputtbzwnkliickvowwfigyzziibtwlj7xe3ca7ly"), + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/events/utils.go b/tests/integration/events/utils.go new file mode 100644 index 0000000000..2004be0e02 --- /dev/null +++ b/tests/integration/events/utils.go @@ -0,0 +1,184 @@ +// Copyright 2022 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package events + +import ( + "context" + "testing" + "time" + + "github.com/sourcenetwork/immutable" + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/db" + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +type TestCase struct { + Description string + + // docs is a map from Collection name, to a list + // of docs in stringified JSON format + Docs map[string][]string + + // The collection calls to make after subscribing to the events channel. + // + // Any errors generated within these calls are of no interest to this test + // framework and should be handled as desired by the caller. + CollectionCalls map[string][]func(client.Collection) + + // The daabase calls to make after subscribing to the events channel. + // + // Any errors generated within these calls are of no interest to this test + // framework and should be handled as desired by the caller. + DatabaseCalls []func(context.Context, client.DB) + + // The update events expected during the specified calls. The length will + // be asserted (within timeout). + ExpectedUpdates []ExpectedUpdate +} + +// A struct holding properties that can be asserted upon. +// +// Properties with a `None` value will not be asserted. If all properties +// are `None` the Update event will still be expected and will contribute +// to the asserted count. +type ExpectedUpdate struct { + DocKey immutable.Option[string] + // The expected Cid, as a string (results in much more readable errors) + Cid immutable.Option[string] + SchemaID immutable.Option[string] + Priority immutable.Option[uint64] +} + +type dbInfo interface { + DB() client.DB +} + +const eventTimeout = 100 * time.Millisecond + +func ExecuteQueryTestCase( + t *testing.T, + schema string, + testCase TestCase, +) { + var err error + ctx := context.Background() + + var dbi dbInfo + dbi, err = testUtils.NewBadgerMemoryDB(ctx, db.WithUpdateEvents()) + if err != nil { + t.Fatal(err) + } + + db := dbi.DB() + + err = db.AddSchema(ctx, schema) + if err != nil { + t.Fatal(err) + } + + setupDatabase(ctx, t, db, testCase) + + closeTestRoutineChan := make(chan struct{}) + testRoutineClosedChan := make(chan struct{}) + eventsChan, err := db.Events().Updates.Value().Subscribe() + if err != nil { + t.Fatal(err) + } + + indexOfNextExpectedUpdate := 0 + go func() { + for { + select { + case update := <-eventsChan: + if indexOfNextExpectedUpdate >= len(testCase.ExpectedUpdates) { + assert.Fail(t, "More events recieved than were expected", update) + testRoutineClosedChan <- struct{}{} + return + } + + expectedEvent := testCase.ExpectedUpdates[indexOfNextExpectedUpdate] + assertIfExpected(t, expectedEvent.Cid, update.Cid.String()) + assertIfExpected(t, expectedEvent.DocKey, update.DocKey) + assertIfExpected(t, expectedEvent.Priority, update.Priority) + assertIfExpected(t, expectedEvent.SchemaID, update.SchemaID) + + indexOfNextExpectedUpdate++ + case <-closeTestRoutineChan: + testRoutineClosedChan <- struct{}{} + return + } + } + }() + + for collectionName, collectionCallSet := range testCase.CollectionCalls { + col, err := db.GetCollectionByName(ctx, collectionName) + if err != nil { + t.Fatal(err) + } + + for _, collectionCall := range collectionCallSet { + collectionCall(col) + } + } + + for _, databaseCall := range testCase.DatabaseCalls { + databaseCall(ctx, db) + } + + select { + case <-time.After(eventTimeout): + closeTestRoutineChan <- struct{}{} + case <-testRoutineClosedChan: + // no-op - just allow the host func to continue + } + + // This is expressed verbosely, as `len(testCase.ExpectedUpdates) == indexOfNextExpectedUpdate` + // is less easy to understand than the below + indexOfLastExpectedUpdate := len(testCase.ExpectedUpdates) - 1 + indexOfLastAssertedUpdate := indexOfNextExpectedUpdate - 1 + assert.Equal(t, indexOfLastExpectedUpdate, indexOfLastAssertedUpdate) +} + +func setupDatabase( + ctx context.Context, + t *testing.T, + db client.DB, + testCase TestCase, +) { + for collectionName, docs := range testCase.Docs { + col, err := db.GetCollectionByName(ctx, collectionName) + if err != nil { + t.Fatal(err) + } + + for _, docStr := range docs { + doc, err := client.NewDocFromJSON([]byte(docStr)) + if err != nil { + t.Fatal(err) + } + err = col.Save(ctx, doc) + if err != nil { + t.Fatal(err) + } + } + } +} + +// assertIfExpected asserts that the given values are Equal, if the expected parameter +// has a value. Otherwise this function will do nothing. +func assertIfExpected[T any](t *testing.T, expected immutable.Option[T], actual T) { + if expected.HasValue() { + assert.Equal(t, expected.Value(), actual) + } +} From 9fb11e5e4eab523cd13a7540cd53b88eab260b3d Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Wed, 14 Dec 2022 17:34:10 -0500 Subject: [PATCH 2/4] PR FIXUP - Remove dockey hardcoding --- tests/integration/events/simple/with_create_test.go | 7 +++++-- tests/integration/events/simple/with_delete_test.go | 3 ++- tests/integration/events/simple/with_update_test.go | 9 ++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/integration/events/simple/with_create_test.go b/tests/integration/events/simple/with_create_test.go index a1515e9de9..67615a1392 100644 --- a/tests/integration/events/simple/with_create_test.go +++ b/tests/integration/events/simple/with_create_test.go @@ -30,6 +30,8 @@ func TestEventsSimpleWithCreate(t *testing.T) { ), ) assert.Nil(t, err) + docKey1 := doc1.Key().String() + doc2, err := client.NewDocFromJSON( []byte( `{ @@ -38,6 +40,7 @@ func TestEventsSimpleWithCreate(t *testing.T) { ), ) assert.Nil(t, err) + docKey2 := doc2.Key().String() test := testUtils.TestCase{ CollectionCalls: map[string][]func(client.Collection){ @@ -54,10 +57,10 @@ func TestEventsSimpleWithCreate(t *testing.T) { }, ExpectedUpdates: []testUtils.ExpectedUpdate{ { - DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + DocKey: immutable.Some(docKey1), }, { - DocKey: immutable.Some("bae-359dfe55-77a4-59de-a687-fc5049dc61fe"), + DocKey: immutable.Some(docKey2), }, }, } diff --git a/tests/integration/events/simple/with_delete_test.go b/tests/integration/events/simple/with_delete_test.go index 7f2fecdb52..4f2d5661db 100644 --- a/tests/integration/events/simple/with_delete_test.go +++ b/tests/integration/events/simple/with_delete_test.go @@ -32,6 +32,7 @@ func TestEventsSimpleWithDelete(t *testing.T) { ), ) assert.Nil(t, err) + docKey1 := doc1.Key().String() test := testUtils.TestCase{ CollectionCalls: map[string][]func(client.Collection){ @@ -49,7 +50,7 @@ func TestEventsSimpleWithDelete(t *testing.T) { }, ExpectedUpdates: []testUtils.ExpectedUpdate{ { - DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + DocKey: immutable.Some(docKey1), }, // No update to reflect the delete }, diff --git a/tests/integration/events/simple/with_update_test.go b/tests/integration/events/simple/with_update_test.go index 9c749a30c6..3426151748 100644 --- a/tests/integration/events/simple/with_update_test.go +++ b/tests/integration/events/simple/with_update_test.go @@ -30,6 +30,8 @@ func TestEventsSimpleWithUpdate(t *testing.T) { ), ) assert.Nil(t, err) + docKey1 := doc1.Key().String() + doc2, err := client.NewDocFromJSON( []byte( `{ @@ -38,6 +40,7 @@ func TestEventsSimpleWithUpdate(t *testing.T) { ), ) assert.Nil(t, err) + docKey2 := doc2.Key().String() test := testUtils.TestCase{ CollectionCalls: map[string][]func(client.Collection){ @@ -60,14 +63,14 @@ func TestEventsSimpleWithUpdate(t *testing.T) { }, ExpectedUpdates: []testUtils.ExpectedUpdate{ { - DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + DocKey: immutable.Some(docKey1), Cid: immutable.Some("bafybeihm4gewwhzsyqs7tuazdsbusp6pm3oinhnj2ae6funcwshwpxuxri"), }, { - DocKey: immutable.Some("bae-359dfe55-77a4-59de-a687-fc5049dc61fe"), + DocKey: immutable.Some(docKey2), }, { - DocKey: immutable.Some("bae-43deba43-f2bc-59f4-9056-fef661b22832"), + DocKey: immutable.Some(docKey1), Cid: immutable.Some("bafybeicxo5sqjzi2mjputtbzwnkliickvowwfigyzziibtwlj7xe3ca7ly"), }, }, From 7617e8d3e9811cc0981f2ed326ebc319e3e40634 Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Wed, 14 Dec 2022 18:11:25 -0500 Subject: [PATCH 3/4] PR FIXUP - Fix broken attempt at clean routine exit --- tests/integration/events/utils.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/integration/events/utils.go b/tests/integration/events/utils.go index 2004be0e02..3a26f970ca 100644 --- a/tests/integration/events/utils.go +++ b/tests/integration/events/utils.go @@ -89,8 +89,8 @@ func ExecuteQueryTestCase( setupDatabase(ctx, t, db, testCase) - closeTestRoutineChan := make(chan struct{}) testRoutineClosedChan := make(chan struct{}) + closeTestRoutineChan := make(chan struct{}) eventsChan, err := db.Events().Updates.Value().Subscribe() if err != nil { t.Fatal(err) @@ -115,7 +115,6 @@ func ExecuteQueryTestCase( indexOfNextExpectedUpdate++ case <-closeTestRoutineChan: - testRoutineClosedChan <- struct{}{} return } } @@ -138,6 +137,10 @@ func ExecuteQueryTestCase( select { case <-time.After(eventTimeout): + // Trigger an exit from the go routine monitoring the eventsChan. + // As well as being a bit cleaner it stops the `--race` flag from + // (rightly) seeing the assert of indexOfNextExpectedUpdate as a + // data race. closeTestRoutineChan <- struct{}{} case <-testRoutineClosedChan: // no-op - just allow the host func to continue From 43917504503a0e718afd6d13f66532965b1d10a7 Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Wed, 14 Dec 2022 18:14:27 -0500 Subject: [PATCH 4/4] PR FIXUP - Use require.NoError over t.Fatal --- tests/integration/events/utils.go | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/tests/integration/events/utils.go b/tests/integration/events/utils.go index 3a26f970ca..6e4282c74f 100644 --- a/tests/integration/events/utils.go +++ b/tests/integration/events/utils.go @@ -17,6 +17,7 @@ import ( "github.com/sourcenetwork/immutable" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/db" @@ -76,25 +77,19 @@ func ExecuteQueryTestCase( var dbi dbInfo dbi, err = testUtils.NewBadgerMemoryDB(ctx, db.WithUpdateEvents()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) db := dbi.DB() err = db.AddSchema(ctx, schema) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) setupDatabase(ctx, t, db, testCase) testRoutineClosedChan := make(chan struct{}) closeTestRoutineChan := make(chan struct{}) eventsChan, err := db.Events().Updates.Value().Subscribe() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) indexOfNextExpectedUpdate := 0 go func() { @@ -122,9 +117,7 @@ func ExecuteQueryTestCase( for collectionName, collectionCallSet := range testCase.CollectionCalls { col, err := db.GetCollectionByName(ctx, collectionName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, collectionCall := range collectionCallSet { collectionCall(col) @@ -161,19 +154,14 @@ func setupDatabase( ) { for collectionName, docs := range testCase.Docs { col, err := db.GetCollectionByName(ctx, collectionName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, docStr := range docs { doc, err := client.NewDocFromJSON([]byte(docStr)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + err = col.Save(ctx, doc) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } } }