diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 6820eee952..22b8bb2d93 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -146,12 +146,15 @@ functions: script: | ${PREPARE_SHELL} rm -rf $DRIVERS_TOOLS - if [ "${project}" = "drivers-tools" ]; then - # If this was a patch build, doing a fresh clone would not actually test the patch - cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS - else - git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS - fi + git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS + + # Pin drivers-evergreen-tools to revision 98f6b0e (Aug 20, 2025). + # In leu of cutting a dedicated branch, please cherry-pick any required changes inline. + # For example: + # git -C $DRIVERS_TOOLS checkout 98f6b0e + # git -C $DRIVERS_TOOLS cherry-pick # needed for + git -C $DRIVERS_TOOLS checkout 98f6b0e + echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config - command: shell.exec params: @@ -384,7 +387,7 @@ functions: working_dir: src/go.mongodb.org/mongo-driver script: | ${PREPARE_SHELL} - ${BUILD_ENV|} BUILD_TAGS=${BUILD_TAGS|-tags=cse,gssapi} make ${targets} + ${BUILD_ENV|} BUILD_TAGS=${BUILD_TAGS|-tags=cse,gssapi,mongointernal} make ${targets} run-tests: - command: shell.exec diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index db12c76224..ec4bbb529d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,6 +49,9 @@ repos: hooks: - id: markdown-link-check exclude: ^(vendor) + # If the endpoint returns HTTP 429 (Too Many Requests), consider it a + # successful check. + args: ["-a 200,206,429"] - repo: local hooks: diff --git a/mongo/integration/mongointernal_test.go b/mongo/integration/mongointernal_test.go new file mode 100644 index 0000000000..1cdb099d72 --- /dev/null +++ b/mongo/integration/mongointernal_test.go @@ -0,0 +1,98 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// 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 + +//go:build mongointernal + +package integration + +import ( + "context" + "testing" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/internal/assert" + "go.mongodb.org/mongo-driver/internal/require" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/integration/mtest" + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" +) + +func TestNewSessionWithLSID(t *testing.T) { + mt := mtest.New(t) + + mt.Run("can be used to pass a specific session ID to CRUD commands", func(mt *mtest.T) { + mt.Parallel() + + // Create a session ID document, which is a BSON document with field + // "id" containing a 16-byte UUID (binary subtype 4). + sessionID := bson.Raw(bsoncore.NewDocumentBuilder(). + AppendBinary("id", 4, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}). + Build()) + + sess := mongo.NewSessionWithLSID(mt.Client, sessionID) + + ctx := mongo.NewSessionContext(context.Background(), sess) + _, err := mt.Coll.InsertOne(ctx, bson.D{{"foo", "bar"}}) + require.NoError(mt, err) + + evt := mt.GetStartedEvent() + val, err := evt.Command.LookupErr("lsid") + require.NoError(mt, err, "lsid should be present in the command document") + + doc, ok := val.DocumentOK() + require.True(mt, ok, "lsid should be a document") + + assert.Equal(mt, sessionID, doc) + }) + + mt.Run("EndSession panics", func(mt *mtest.T) { + mt.Parallel() + + sessionID := bson.Raw(bsoncore.NewDocumentBuilder(). + AppendBinary("id", 4, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}). + Build()) + sess := mongo.NewSessionWithLSID(mt.Client, sessionID) + + // Use a defer-recover block to catch the expected panic and assert that + // the recovered error is not nil. + defer func() { + err := recover() + assert.NotNil(mt, err, "expected EndSession to panic") + }() + + // Expect this call to panic. + sess.EndSession(context.Background()) + + // We expect that calling EndSession on a Session returned by + // NewSessionWithLSID panics. This code will only be reached if EndSession + // doesn't panic. + t.Errorf("expected EndSession to panic") + }) + + mt.Run("ClientSession.SetServer panics", func(mt *mtest.T) { + mt.Parallel() + + sessionID := bson.Raw(bsoncore.NewDocumentBuilder(). + AppendBinary("id", 4, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}). + Build()) + sess := mongo.NewSessionWithLSID(mt.Client, sessionID) + + // Use a defer-recover block to catch the expected panic and assert that + // the recovered error is not nil. + defer func() { + err := recover() + assert.NotNil(mt, err, "expected ClientSession.SetServer to panic") + }() + + // Expect this call to panic. + sess.(mongo.XSession).ClientSession().SetServer() + + // We expect that calling ClientSession.SetServer on a Session returned + // by NewSessionWithLSID panics. This code will only be reached if + // ClientSession.SetServer doesn't panic. + t.Errorf("expected ClientSession.SetServer to panic") + }) +} diff --git a/mongo/mongointernal.go b/mongo/mongointernal.go new file mode 100644 index 0000000000..0148756fc3 --- /dev/null +++ b/mongo/mongointernal.go @@ -0,0 +1,41 @@ +// Copyright (C) MongoDB, Inc. 2025-present. +// +// 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 + +//go:build mongointernal + +package mongo + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + "go.mongodb.org/mongo-driver/x/mongo/driver/session" +) + +// NewSessionWithLSID returns a Session with the given sessionID document. The +// sessionID is a BSON document with key "id" containing a 16-byte UUID (binary +// subtype 4). +// +// Sessions returned by NewSessionWithLSID are never added to the driver's +// session pool. Calling "EndSession" or "ClientSession.SetServer" on a Session +// returned by NewSessionWithLSID will panic. +// +// NewSessionWithLSID is intended only for internal use and may be changed or +// removed at any time. +func NewSessionWithLSID(client *Client, sessionID bson.Raw) Session { + return &sessionImpl{ + clientSession: &session.Client{ + Server: &session.Server{ + SessionID: bsoncore.Document(sessionID), + LastUsed: time.Now(), + }, + ClientID: client.id, + }, + client: client, + deployment: client.deployment, + } +}