Skip to content

Commit

Permalink
Implement killCursors command (#2939)
Browse files Browse the repository at this point in the history
Closes #1514.
  • Loading branch information
AlekSi authored Jul 6, 2023
1 parent 6b1a032 commit 708d90a
Show file tree
Hide file tree
Showing 25 changed files with 610 additions and 106 deletions.
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
github-actions:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -19,6 +22,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
build-docker:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -27,6 +33,9 @@ updates:
directory: "/build/deps"
labels: ["deps", "not ready"]
open-pull-requests-limit: 20
groups:
build-deps:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -37,6 +46,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
ferretdb:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -46,6 +58,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
integration:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -55,6 +70,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
tools:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand All @@ -64,6 +82,9 @@ updates:
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
golangci:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ jobs:
fuzz-short:
name: Fuzz short
runs-on: ubuntu-22.04
timeout-minutes: 10
timeout-minutes: 15

# Do not run this job in parallel for any PR change or branch push.
concurrency:
Expand Down Expand Up @@ -438,7 +438,7 @@ jobs:
git config user.name ferretdb-bot
git config user.email ferretdb-bot@ferretdb.io
git add --all .
git diff-index --patch
git diff-index -p
git diff-index --quiet HEAD || git commit --message 'Update corpus'
git fetch
git rebase origin/main
Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,13 @@ Writing separate tests might be much better than making a single test that check

Also, we should use driver methods as much as possible instead of testing commands directly via `RunCommand`.

We have a lot of existing helpers to convert the driver's `bson.D` values to our `*types.Document` values,
to compare them, etc.
In most cases, they should be used instead of (deprecated) `bson.D.Map()`,
`bson.Unmarshal`, decoding into a struct with `bson` tags, comparing fields one-by-one, etc.
The bar for adding new helpers is very high.
Please check all existing ones.

#### Integration tests naming guidelines

1. Test names should include the name of the command being tested.
Expand Down
2 changes: 2 additions & 0 deletions cmd/envtool/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"sort"
"strings"
Expand Down Expand Up @@ -57,6 +58,7 @@ func testsShard(w io.Writer, index, total uint) error {
func getAllTestNames(dir string) ([]string, error) {
cmd := exec.Command("go", "test", "-list=.", "./...")
cmd.Dir = dir
cmd.Stderr = os.Stderr

b, err := cmd.Output()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion integration/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestMostCommandsAreCaseSensitive(t *testing.T) {
res := db.RunCommand(ctx, bson.D{{"listcollections", 1}})
err := res.Err()
require.Error(t, err)
AssertEqualError(t, mongo.CommandError{Code: 59, Name: "CommandNotFound", Message: `no such command: 'listcollections'`}, err)
AssertEqualCommandError(t, mongo.CommandError{Code: 59, Name: "CommandNotFound", Message: `no such command: 'listcollections'`}, err)

res = db.RunCommand(ctx, bson.D{{"listCollections", 1}})
assert.NoError(t, res.Err())
Expand Down
129 changes: 125 additions & 4 deletions integration/commands_administration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestCommandsAdministrationCreateDropList(t *testing.T) {
Name: "NamespaceNotFound",
Message: `ns not found`,
}
AssertEqualError(t, expectedErr, err)
AssertEqualCommandError(t, expectedErr, err)

err = db.CreateCollection(ctx, name)
require.NoError(t, err)
Expand All @@ -76,7 +76,7 @@ func TestCommandsAdministrationCreateDropList(t *testing.T) {
Name: "NamespaceExists",
Message: `Collection TestCommandsAdministrationCreateDropList.TestCommandsAdministrationCreateDropList already exists.`,
}
AssertEqualError(t, expectedErr, err)
AssertEqualCommandError(t, expectedErr, err)

names, err = db.ListCollectionNames(ctx, bson.D{})
require.NoError(t, err)
Expand All @@ -93,7 +93,7 @@ func TestCommandsAdministrationCreateDropList(t *testing.T) {
Name: "NamespaceNotFound",
Message: `ns not found`,
}
AssertEqualError(t, expectedErr, err)
AssertEqualCommandError(t, expectedErr, err)
}

func TestCommandsAdministrationCreateDropListDatabases(t *testing.T) {
Expand Down Expand Up @@ -1148,7 +1148,8 @@ func TestCommandsAdministrationCurrentOp(t *testing.T) {
})

var res bson.D
err := s.Collection.Database().RunCommand(s.Ctx,
err := s.Collection.Database().RunCommand(
s.Ctx,
bson.D{{"currentOp", int32(1)}},
).Decode(&res)
require.NoError(t, err)
Expand All @@ -1158,3 +1159,123 @@ func TestCommandsAdministrationCurrentOp(t *testing.T) {
_, ok := must.NotFail(doc.Get("inprog")).(*types.Array)
assert.True(t, ok)
}

func TestCommandsAdministrationKillCursors(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t, shareddata.Strings)

// does not show up in cursorsAlive or anywhere else
cursor, err := collection.Find(ctx, bson.D{}, options.Find().SetBatchSize(1))
require.NoError(t, err)
require.True(t, cursor.Next(ctx))

defer cursor.Close(ctx)

t.Run("Empty", func(t *testing.T) {
t.Parallel()

var actual bson.D
err := collection.Database().RunCommand(ctx, bson.D{
{"killCursors", collection.Name()},
{"cursors", bson.A{}},
}).Decode(&actual)
require.NoError(t, err)
expected := bson.D{
{"cursorsKilled", bson.A{}},
{"cursorsNotFound", bson.A{}},
{"cursorsAlive", bson.A{}},
{"cursorsUnknown", bson.A{}},
{"ok", float64(1)},
}
AssertEqualDocuments(t, expected, actual)
})

t.Run("WrongType", func(tt *testing.T) {
tt.Parallel()

t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/1514")

c, err := collection.Find(ctx, bson.D{}, options.Find().SetBatchSize(1))
require.NoError(t, err)
require.True(t, c.Next(ctx))
defer c.Close(ctx)

var actual bson.D
err = collection.Database().RunCommand(ctx, bson.D{
{"killCursors", collection.Name()},
{"cursors", bson.A{c.ID(), "foo"}},
}).Decode(&actual)
expectedErr := mongo.CommandError{
Code: 14,
Name: "TypeMismatch",
Message: "BSON field 'killCursors.cursors.1' is the wrong type 'string', expected type 'long'",
}
AssertEqualCommandError(t, expectedErr, err)

assert.True(t, c.Next(ctx))
assert.NoError(t, c.Err())
})

t.Run("Found", func(t *testing.T) {
t.Parallel()

c, err := collection.Find(ctx, bson.D{}, options.Find().SetBatchSize(1))
require.NoError(t, err)
require.True(t, c.Next(ctx))
defer c.Close(ctx)

var actual bson.D
err = collection.Database().RunCommand(ctx, bson.D{
{"killCursors", collection.Name()},
{"cursors", bson.A{c.ID()}},
}).Decode(&actual)
require.NoError(t, err)
expected := bson.D{
{"cursorsKilled", bson.A{c.ID()}},
{"cursorsNotFound", bson.A{}},
{"cursorsAlive", bson.A{}},
{"cursorsUnknown", bson.A{}},
{"ok", float64(1)},
}
AssertEqualDocuments(t, expected, actual)

assert.False(t, c.Next(ctx))
expectedErr := mongo.CommandError{
Code: 43,
Name: "CursorNotFound",
}
AssertMatchesCommandError(t, expectedErr, c.Err())
})

t.Run("NotFound", func(t *testing.T) {
t.Parallel()

c, err := collection.Find(ctx, bson.D{}, options.Find().SetBatchSize(1))
require.NoError(t, err)
require.True(t, c.Next(ctx))
defer c.Close(ctx)

var actual bson.D
err = collection.Database().RunCommand(ctx, bson.D{
{"killCursors", collection.Name()},
{"cursors", bson.A{c.ID(), int64(100500)}},
}).Decode(&actual)
require.NoError(t, err)
expected := bson.D{
{"cursorsKilled", bson.A{c.ID()}},
{"cursorsNotFound", bson.A{int64(100500)}},
{"cursorsAlive", bson.A{}},
{"cursorsUnknown", bson.A{}},
{"ok", float64(1)},
}
AssertEqualDocuments(t, expected, actual)

assert.False(t, c.Next(ctx))
expectedErr := mongo.CommandError{
Code: 43,
Name: "CursorNotFound",
}
AssertMatchesCommandError(t, expectedErr, c.Err())
})
}
2 changes: 1 addition & 1 deletion integration/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func TestCreateStressSameCollection(t *testing.T) {
if err == nil {
created.Add(1)
} else {
AssertEqualError(
AssertEqualCommandError(
t,
mongo.CommandError{
Code: 48,
Expand Down
18 changes: 8 additions & 10 deletions integration/delete_command_compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,14 @@ func testDeleteCommandCompat(t *testing.T, testCases map[string]deleteCommandCom
t.Helper()

var targetRes, compatRes bson.D
targetErr := targetCollection.Database().RunCommand(ctx,
bson.D{
{"delete", targetCollection.Name()},
{"deletes", tc.deletes},
}).Decode(&targetRes)
compatErr := compatCollection.Database().RunCommand(ctx,
bson.D{
{"delete", compatCollection.Name()},
{"deletes", tc.deletes},
}).Decode(&compatRes)
targetErr := targetCollection.Database().RunCommand(ctx, bson.D{
{"delete", targetCollection.Name()},
{"deletes", tc.deletes},
}).Decode(&targetRes)
compatErr := compatCollection.Database().RunCommand(ctx, bson.D{
{"delete", compatCollection.Name()},
{"deletes", tc.deletes},
}).Decode(&compatRes)

if targetErr != nil {
t.Logf("Target error: %v", targetErr)
Expand Down
6 changes: 3 additions & 3 deletions integration/distinct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
func TestDistinctCommandErrors(t *testing.T) {
t.Parallel()

ctx, coll := setup.Setup(t, shareddata.Scalars, shareddata.Composites)
ctx, collection := setup.Setup(t, shareddata.Scalars, shareddata.Composites)

for name, tc := range map[string]struct {
command any // required, command to run
Expand Down Expand Up @@ -109,15 +109,15 @@ func TestDistinctCommandErrors(t *testing.T) {
require.NotNil(t, tc.command, "command must not be nil")
require.NotNil(t, tc.filter, "filter must not be nil")

var collName any = coll.Name()
var collName any = collection.Name()
if tc.collName != nil {
collName = tc.collName
}

command := bson.D{{"distinct", collName}, {"key", tc.command}, {"query", tc.filter}}

var res bson.D
err := coll.Database().RunCommand(ctx, command).Decode(res)
err := collection.Database().RunCommand(ctx, command).Decode(res)
if tc.err != nil {
assert.Nil(t, res)
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
Expand Down
4 changes: 2 additions & 2 deletions integration/getmore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,10 +641,10 @@ func TestGetMoreCommandConnection(t *testing.T) {
require.NoError(t, err)
})

t.Run("DifferentClient", func(t *testing.T) {
t.Run("DifferentClient", func(tt *testing.T) {
// The error returned from MongoDB is a session error, FerretDB does not
// return an error because db, collection and username are the same.
setup.SkipExceptMongoDB(t, "https://github.com/FerretDB/FerretDB/issues/153")
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/153")

// do not run subtest in parallel to avoid breaking another parallel subtest

Expand Down
16 changes: 1 addition & 15 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,6 @@ func AssertEqualDocumentsSlice(t testutil.TB, expected, actual []bson.D) bool {
return testutil.AssertEqualSlices(t, expectedDocs, actualDocs)
}

// AssertEqualError is a deprecated alias for AssertEqualCommandError.
//
// Deprecated: use AssertEqualCommandError instead.
func AssertEqualError(t testutil.TB, expected mongo.CommandError, actual error) bool {
return AssertEqualCommandError(t, expected, actual)
}

// AssertEqualCommandError asserts that the expected error is the same as the actual (ignoring the Raw part).
func AssertEqualCommandError(t testutil.TB, expected mongo.CommandError, actual error) bool {
t.Helper()
Expand Down Expand Up @@ -216,7 +209,7 @@ func AssertMatchesCommandError(t testutil.TB, expected, actual error) {
actualMessage := a.Message
a.Message = e.Message

if !AssertEqualError(t, e, a) {
if !AssertEqualCommandError(t, e, a) {
t.Logf("actual message: %s", actualMessage)
}
}
Expand Down Expand Up @@ -269,13 +262,6 @@ func AssertMatchesBulkException(t testutil.TB, expected, actual error) {
}
}

// AssertEqualAltError is a deprecated alias for AssertEqualAltCommandError.
//
// Deprecated: use AssertEqualAltCommandError instead.
func AssertEqualAltError(t testutil.TB, expected mongo.CommandError, altMessage string, actual error) bool {
return AssertEqualAltCommandError(t, expected, altMessage, actual)
}

// AssertEqualAltCommandError asserts that the expected error is the same as the actual (ignoring the Raw part);
// the alternative error message may be provided if FerretDB is unable to produce exactly the same text as MongoDB.
//
Expand Down
Loading

0 comments on commit 708d90a

Please sign in to comment.