getEncoderClass() {
+ return Vector.class;
+ }
+}
+
+
diff --git a/bson/src/main/org/bson/internal/UuidHelper.java b/bson/src/main/org/bson/internal/UuidHelper.java
index efe3d5b5812..9c46614b56e 100644
--- a/bson/src/main/org/bson/internal/UuidHelper.java
+++ b/bson/src/main/org/bson/internal/UuidHelper.java
@@ -124,6 +124,12 @@ public static UUID decodeBinaryToUuid(final byte[] data, final byte type, final
return new UUID(readLongFromArrayBigEndian(localData, 0), readLongFromArrayBigEndian(localData, 8));
}
+ public static boolean isLegacyUUID(final UuidRepresentation uuidRepresentation) {
+ return uuidRepresentation == UuidRepresentation.JAVA_LEGACY
+ || uuidRepresentation == UuidRepresentation.C_SHARP_LEGACY
+ || uuidRepresentation == UuidRepresentation.PYTHON_LEGACY;
+ }
+
private UuidHelper() {
}
}
diff --git a/bson/src/main/org/bson/internal/vector/VectorHelper.java b/bson/src/main/org/bson/internal/vector/VectorHelper.java
new file mode 100644
index 00000000000..9dbf583d2b0
--- /dev/null
+++ b/bson/src/main/org/bson/internal/vector/VectorHelper.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 org.bson.internal.vector;
+
+import org.bson.BsonBinary;
+import org.bson.BsonInvalidOperationException;
+import org.bson.Float32Vector;
+import org.bson.Int8Vector;
+import org.bson.PackedBitVector;
+import org.bson.Vector;
+import org.bson.assertions.Assertions;
+import org.bson.types.Binary;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * Helper class for encoding and decoding vectors to and from {@link BsonBinary}/{@link Binary}.
+ *
+ *
+ * This class is not part of the public API and may be removed or changed at any time.
+ *
+ * @see Vector
+ * @see BsonBinary#asVector()
+ * @see BsonBinary#BsonBinary(Vector)
+ */
+public final class VectorHelper {
+
+ private static final ByteOrder STORED_BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
+ private static final String ERROR_MESSAGE_UNKNOWN_VECTOR_DATA_TYPE = "Unknown vector data type: ";
+ private static final byte ZERO_PADDING = 0;
+
+ private VectorHelper() {
+ //NOP
+ }
+
+ private static final int METADATA_SIZE = 2;
+
+ public static byte[] encodeVectorToBinary(final Vector vector) {
+ Vector.DataType dataType = vector.getDataType();
+ switch (dataType) {
+ case INT8:
+ return encodeVector(dataType.getValue(), ZERO_PADDING, vector.asInt8Vector().getData());
+ case PACKED_BIT:
+ PackedBitVector packedBitVector = vector.asPackedBitVector();
+ return encodeVector(dataType.getValue(), packedBitVector.getPadding(), packedBitVector.getData());
+ case FLOAT32:
+ return encodeVector(dataType.getValue(), vector.asFloat32Vector().getData());
+ default:
+ throw Assertions.fail(ERROR_MESSAGE_UNKNOWN_VECTOR_DATA_TYPE + dataType);
+ }
+ }
+
+ /**
+ * Decodes a vector from a binary representation.
+ *
+ * encodedVector is not mutated nor stored in the returned {@link Vector}.
+ */
+ public static Vector decodeBinaryToVector(final byte[] encodedVector) {
+ isTrue("Vector encoded array length must be at least 2, but found: " + encodedVector.length, encodedVector.length >= METADATA_SIZE);
+ Vector.DataType dataType = determineVectorDType(encodedVector[0]);
+ byte padding = encodedVector[1];
+ switch (dataType) {
+ case INT8:
+ return decodeInt8Vector(encodedVector, padding);
+ case PACKED_BIT:
+ return decodePackedBitVector(encodedVector, padding);
+ case FLOAT32:
+ return decodeFloat32Vector(encodedVector, padding);
+ default:
+ throw Assertions.fail(ERROR_MESSAGE_UNKNOWN_VECTOR_DATA_TYPE + dataType);
+ }
+ }
+
+ private static Float32Vector decodeFloat32Vector(final byte[] encodedVector, final byte padding) {
+ isTrue("Padding must be 0 for FLOAT32 data type, but found: " + padding, padding == 0);
+ return Vector.floatVector(decodeLittleEndianFloats(encodedVector));
+ }
+
+ private static PackedBitVector decodePackedBitVector(final byte[] encodedVector, final byte padding) {
+ byte[] packedBitVector = extractVectorData(encodedVector);
+ isTrue("Padding must be 0 if vector is empty, but found: " + padding, padding == 0 || packedBitVector.length > 0);
+ isTrue("Padding must be between 0 and 7 bits, but found: " + padding, padding >= 0 && padding <= 7);
+ return Vector.packedBitVector(packedBitVector, padding);
+ }
+
+ private static Int8Vector decodeInt8Vector(final byte[] encodedVector, final byte padding) {
+ isTrue("Padding must be 0 for INT8 data type, but found: " + padding, padding == 0);
+ byte[] int8Vector = extractVectorData(encodedVector);
+ return Vector.int8Vector(int8Vector);
+ }
+
+ private static byte[] extractVectorData(final byte[] encodedVector) {
+ int vectorDataLength = encodedVector.length - METADATA_SIZE;
+ byte[] vectorData = new byte[vectorDataLength];
+ System.arraycopy(encodedVector, METADATA_SIZE, vectorData, 0, vectorDataLength);
+ return vectorData;
+ }
+
+ private static byte[] encodeVector(final byte dType, final byte padding, final byte[] vectorData) {
+ final byte[] bytes = new byte[vectorData.length + METADATA_SIZE];
+ bytes[0] = dType;
+ bytes[1] = padding;
+ System.arraycopy(vectorData, 0, bytes, METADATA_SIZE, vectorData.length);
+ return bytes;
+ }
+
+ private static byte[] encodeVector(final byte dType, final float[] vectorData) {
+ final byte[] bytes = new byte[vectorData.length * Float.BYTES + METADATA_SIZE];
+
+ bytes[0] = dType;
+ bytes[1] = ZERO_PADDING;
+
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ buffer.order(STORED_BYTE_ORDER);
+ buffer.position(METADATA_SIZE);
+
+ FloatBuffer floatBuffer = buffer.asFloatBuffer();
+
+ // The JVM may optimize this operation internally, potentially using intrinsics
+ // or platform-specific optimizations (such as SIMD). If the byte order matches the underlying system's
+ // native order, the operation may involve a direct memory copy.
+ floatBuffer.put(vectorData);
+
+ return bytes;
+ }
+
+ private static float[] decodeLittleEndianFloats(final byte[] encodedVector) {
+ isTrue("Byte array length must be a multiple of 4 for FLOAT32 data type, but found: " + encodedVector.length,
+ (encodedVector.length - METADATA_SIZE) % Float.BYTES == 0);
+
+ int vectorSize = encodedVector.length - METADATA_SIZE;
+
+ int numFloats = vectorSize / Float.BYTES;
+ float[] floatArray = new float[numFloats];
+
+ ByteBuffer buffer = ByteBuffer.wrap(encodedVector, METADATA_SIZE, vectorSize);
+ buffer.order(STORED_BYTE_ORDER);
+
+ // The JVM may optimize this operation internally, potentially using intrinsics
+ // or platform-specific optimizations (such as SIMD). If the byte order matches the underlying system's
+ // native order, the operation may involve a direct memory copy.
+ buffer.asFloatBuffer().get(floatArray);
+ return floatArray;
+ }
+
+ public static Vector.DataType determineVectorDType(final byte dType) {
+ Vector.DataType[] values = Vector.DataType.values();
+ for (Vector.DataType value : values) {
+ if (value.getValue() == dType) {
+ return value;
+ }
+ }
+ throw new BsonInvalidOperationException(ERROR_MESSAGE_UNKNOWN_VECTOR_DATA_TYPE + dType);
+ }
+
+ private static void isTrue(final String message, final boolean condition) {
+ if (!condition) {
+ throw new BsonInvalidOperationException(message);
+ }
+ }
+}
diff --git a/bson/src/test/resources/bson-binary-vector/float32.json b/bson/src/test/resources/bson-binary-vector/float32.json
new file mode 100644
index 00000000000..e1d142c184b
--- /dev/null
+++ b/bson/src/test/resources/bson-binary-vector/float32.json
@@ -0,0 +1,50 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype FLOAT32",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Simple Vector FLOAT32",
+ "valid": true,
+ "vector": [127.0, 7.0],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1C00000005766563746F72000A0000000927000000FE420000E04000"
+ },
+ {
+ "description": "Vector with decimals and negative value FLOAT32",
+ "valid": true,
+ "vector": [127.7, -7.7],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1C00000005766563746F72000A0000000927006666FF426666F6C000"
+ },
+ {
+ "description": "Empty Vector FLOAT32",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009270000"
+ },
+ {
+ "description": "Infinity Vector FLOAT32",
+ "valid": true,
+ "vector": ["-inf", 0.0, "inf"],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 0,
+ "canonical_bson": "2000000005766563746F72000E000000092700000080FF000000000000807F00"
+ },
+ {
+ "description": "FLOAT32 with padding",
+ "valid": false,
+ "vector": [127.0, 7.0],
+ "dtype_hex": "0x27",
+ "dtype_alias": "FLOAT32",
+ "padding": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bson/src/test/resources/bson-binary-vector/int8.json b/bson/src/test/resources/bson-binary-vector/int8.json
new file mode 100644
index 00000000000..c10c1b7d4e2
--- /dev/null
+++ b/bson/src/test/resources/bson-binary-vector/int8.json
@@ -0,0 +1,56 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype INT8",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Simple Vector INT8",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0,
+ "canonical_bson": "1600000005766563746F7200040000000903007F0700"
+ },
+ {
+ "description": "Empty Vector INT8",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009030000"
+ },
+ {
+ "description": "Overflow Vector INT8",
+ "valid": false,
+ "vector": [128],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ },
+ {
+ "description": "Underflow Vector INT8",
+ "valid": false,
+ "vector": [-129],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ },
+ {
+ "description": "INT8 with padding",
+ "valid": false,
+ "vector": [127, 7],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 3
+ },
+ {
+ "description": "INT8 with float inputs",
+ "valid": false,
+ "vector": [127.77, 7.77],
+ "dtype_hex": "0x03",
+ "dtype_alias": "INT8",
+ "padding": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bson/src/test/resources/bson-binary-vector/packed_bit.json b/bson/src/test/resources/bson-binary-vector/packed_bit.json
new file mode 100644
index 00000000000..69fb3948335
--- /dev/null
+++ b/bson/src/test/resources/bson-binary-vector/packed_bit.json
@@ -0,0 +1,97 @@
+{
+ "description": "Tests of Binary subtype 9, Vectors, with dtype PACKED_BIT",
+ "test_key": "vector",
+ "tests": [
+ {
+ "description": "Padding specified with no vector data PACKED_BIT",
+ "valid": false,
+ "vector": [],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 1
+ },
+ {
+ "description": "Simple Vector PACKED_BIT",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0,
+ "canonical_bson": "1600000005766563746F7200040000000910007F0700"
+ },
+ {
+ "description": "Empty Vector PACKED_BIT",
+ "valid": true,
+ "vector": [],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0,
+ "canonical_bson": "1400000005766563746F72000200000009100000"
+ },
+ {
+ "description": "PACKED_BIT with padding",
+ "valid": true,
+ "vector": [127, 7],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 3,
+ "canonical_bson": "1600000005766563746F7200040000000910037F0700"
+ },
+ {
+ "description": "Overflow Vector PACKED_BIT",
+ "valid": false,
+ "vector": [256],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Underflow Vector PACKED_BIT",
+ "valid": false,
+ "vector": [-1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Vector with float values PACKED_BIT",
+ "valid": false,
+ "vector": [127.5],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ },
+ {
+ "description": "Padding specified with no vector data PACKED_BIT",
+ "valid": false,
+ "vector": [],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 1
+ },
+ {
+ "description": "Exceeding maximum padding PACKED_BIT",
+ "valid": false,
+ "vector": [1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 8
+ },
+ {
+ "description": "Negative padding PACKED_BIT",
+ "valid": false,
+ "vector": [1],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": -1
+ },
+ {
+ "description": "Vector with float values PACKED_BIT",
+ "valid": false,
+ "vector": [127.5],
+ "dtype_hex": "0x10",
+ "dtype_alias": "PACKED_BIT",
+ "padding": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bson/src/test/resources/bson/binary.json b/bson/src/test/resources/bson/binary.json
index d3c57ec1081..29d88471afe 100644
--- a/bson/src/test/resources/bson/binary.json
+++ b/bson/src/test/resources/bson/binary.json
@@ -55,6 +55,11 @@
"canonical_bson": "1D000000057800100000000773FFD26444B34C6990E8E7D1DFC035D400",
"canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"07\"}}}"
},
+ {
+ "description": "subtype 0x08",
+ "canonical_bson": "1D000000057800100000000873FFD26444B34C6990E8E7D1DFC035D400",
+ "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"08\"}}}"
+ },
{
"description": "subtype 0x80",
"canonical_bson": "0F0000000578000200000080FFFF00",
@@ -69,6 +74,36 @@
"description": "$type query operator (conflicts with legacy $binary form with $type field)",
"canonical_bson": "180000000378001000000010247479706500020000000000",
"canonical_extjson": "{\"x\" : { \"$type\" : {\"$numberInt\": \"2\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector FLOAT32",
+ "canonical_bson": "170000000578000A0000000927000000FE420000E04000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwAAAP5CAADgQA==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector INT8",
+ "canonical_bson": "11000000057800040000000903007F0700",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwB/Bw==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector PACKED_BIT",
+ "canonical_bson": "11000000057800040000000910007F0700",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAB/Bw==\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) FLOAT32",
+ "canonical_bson": "0F0000000578000200000009270000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"JwA=\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) INT8",
+ "canonical_bson": "0F0000000578000200000009030000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"AwA=\", \"subType\": \"09\"}}}"
+ },
+ {
+ "description": "subtype 0x09 Vector (Zero-length) PACKED_BIT",
+ "canonical_bson": "0F0000000578000200000009100000",
+ "canonical_extjson": "{\"x\": {\"$binary\": {\"base64\": \"EAA=\", \"subType\": \"09\"}}}"
}
],
"decodeErrors": [
@@ -115,4 +150,4 @@
"string": "{\"x\" : { \"$uuid\" : \"----d264-44b3-4--9-90e8-e7d1dfc0----\"}}"
}
]
-}
+}
\ No newline at end of file
diff --git a/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy b/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy
index e51094e964f..503440daa04 100644
--- a/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy
+++ b/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy
@@ -48,9 +48,14 @@ class BsonBinarySpecification extends Specification {
data == bsonBinary.getData()
where:
- subType << [BsonBinarySubType.BINARY, BsonBinarySubType.FUNCTION, BsonBinarySubType.MD5,
- BsonBinarySubType.OLD_BINARY, BsonBinarySubType.USER_DEFINED, BsonBinarySubType.UUID_LEGACY,
- BsonBinarySubType.UUID_STANDARD]
+ subType << [BsonBinarySubType.BINARY,
+ BsonBinarySubType.FUNCTION,
+ BsonBinarySubType.MD5,
+ BsonBinarySubType.OLD_BINARY,
+ BsonBinarySubType.USER_DEFINED,
+ BsonBinarySubType.UUID_LEGACY,
+ BsonBinarySubType.UUID_STANDARD,
+ BsonBinarySubType.VECTOR]
}
@Unroll
diff --git a/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy b/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy
index f26d1ad00d9..448d63f23fd 100644
--- a/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy
+++ b/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy
@@ -33,5 +33,7 @@ class BsonBinarySubTypeSpecification extends Specification {
5 | false
6 | false
7 | false
+ 8 | false
+ 9 | false
}
}
diff --git a/bson/src/test/unit/org/bson/BsonBinaryTest.java b/bson/src/test/unit/org/bson/BsonBinaryTest.java
new file mode 100644
index 00000000000..029c611c594
--- /dev/null
+++ b/bson/src/test/unit/org/bson/BsonBinaryTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 org.bson;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+class BsonBinaryTest {
+
+ private static final byte FLOAT32_DTYPE = Vector.DataType.FLOAT32.getValue();
+ private static final byte INT8_DTYPE = Vector.DataType.INT8.getValue();
+ private static final byte PACKED_BIT_DTYPE = Vector.DataType.PACKED_BIT.getValue();
+ public static final int ZERO_PADDING = 0;
+
+ @Test
+ void shouldThrowExceptionWhenCreatingBsonBinaryWithNullVector() {
+ // given
+ Vector vector = null;
+
+ // when & then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new BsonBinary(vector));
+ assertEquals("Vector must not be null", exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = BsonBinarySubType.class, mode = EnumSource.Mode.EXCLUDE, names = {"VECTOR"})
+ void shouldThrowExceptionWhenBsonBinarySubTypeIsNotVector(final BsonBinarySubType bsonBinarySubType) {
+ // given
+ byte[] data = new byte[]{1, 2, 3, 4};
+ BsonBinary bsonBinary = new BsonBinary(bsonBinarySubType.getValue(), data);
+
+ // when & then
+ BsonInvalidOperationException exception = assertThrows(BsonInvalidOperationException.class, bsonBinary::asVector);
+ assertEquals("type must be a Vector subtype.", exception.getMessage());
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("provideFloatVectors")
+ void shouldEncodeFloatVector(final Vector actualFloat32Vector, final byte[] expectedBsonEncodedVector) {
+ // when
+ BsonBinary actualBsonBinary = new BsonBinary(actualFloat32Vector);
+ byte[] actualBsonEncodedVector = actualBsonBinary.getData();
+
+ // then
+ assertEquals(BsonBinarySubType.VECTOR.getValue(), actualBsonBinary.getType(), "The subtype must be VECTOR");
+ assertArrayEquals(expectedBsonEncodedVector, actualBsonEncodedVector);
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("provideFloatVectors")
+ void shouldDecodeFloatVector(final Float32Vector expectedFloatVector, final byte[] bsonEncodedVector) {
+ // when
+ Float32Vector decodedVector = (Float32Vector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector();
+
+ // then
+ assertEquals(expectedFloatVector, decodedVector);
+ }
+
+ private static Stream provideFloatVectors() {
+ return Stream.of(
+ arguments(
+ Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f, -1.0f, Float.MAX_VALUE, Float.MIN_VALUE, Float.POSITIVE_INFINITY,
+ Float.NEGATIVE_INFINITY}),
+ new byte[]{FLOAT32_DTYPE, ZERO_PADDING,
+ (byte) 205, (byte) 204, (byte) 140, (byte) 63, // 1.1f in little-endian
+ (byte) 205, (byte) 204, (byte) 12, (byte) 64, // 2.2f in little-endian
+ (byte) 51, (byte) 51, (byte) 83, (byte) 64, // 3.3f in little-endian
+ (byte) 0, (byte) 0, (byte) 128, (byte) 191, // -1.0f in little-endian
+ (byte) 255, (byte) 255, (byte) 127, (byte) 127, // Float.MAX_VALUE in little-endian
+ (byte) 1, (byte) 0, (byte) 0, (byte) 0, // Float.MIN_VALUE in little-endian
+ (byte) 0, (byte) 0, (byte) 128, (byte) 127, // Float.POSITIVE_INFINITY in little-endian
+ (byte) 0, (byte) 0, (byte) 128, (byte) 255 // Float.NEGATIVE_INFINITY in little-endian
+ }
+ ),
+ arguments(
+ Vector.floatVector(new float[]{0.0f}),
+ new byte[]{FLOAT32_DTYPE, ZERO_PADDING,
+ (byte) 0, (byte) 0, (byte) 0, (byte) 0 // 0.0f in little-endian
+ }
+ ),
+ arguments(
+ Vector.floatVector(new float[]{}),
+ new byte[]{FLOAT32_DTYPE, ZERO_PADDING}
+ )
+ );
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("provideInt8Vectors")
+ void shouldEncodeInt8Vector(final Vector actualInt8Vector, final byte[] expectedBsonEncodedVector) {
+ // when
+ BsonBinary actualBsonBinary = new BsonBinary(actualInt8Vector);
+ byte[] actualBsonEncodedVector = actualBsonBinary.getData();
+
+ // then
+ assertEquals(BsonBinarySubType.VECTOR.getValue(), actualBsonBinary.getType(), "The subtype must be VECTOR");
+ assertArrayEquals(expectedBsonEncodedVector, actualBsonEncodedVector);
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("provideInt8Vectors")
+ void shouldDecodeInt8Vector(final Int8Vector expectedInt8Vector, final byte[] bsonEncodedVector) {
+ // when
+ Int8Vector decodedVector = (Int8Vector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector();
+
+ // then
+ assertEquals(expectedInt8Vector, decodedVector);
+ }
+
+ private static Stream provideInt8Vectors() {
+ return Stream.of(
+ arguments(
+ Vector.int8Vector(new byte[]{Byte.MAX_VALUE, 1, 2, 3, 4, Byte.MIN_VALUE}),
+ new byte[]{INT8_DTYPE, ZERO_PADDING, Byte.MAX_VALUE, 1, 2, 3, 4, Byte.MIN_VALUE
+ }),
+ arguments(Vector.int8Vector(new byte[]{}),
+ new byte[]{INT8_DTYPE, ZERO_PADDING}
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("providePackedBitVectors")
+ void shouldEncodePackedBitVector(final Vector actualPackedBitVector, final byte[] expectedBsonEncodedVector) {
+ // when
+ BsonBinary actualBsonBinary = new BsonBinary(actualPackedBitVector);
+ byte[] actualBsonEncodedVector = actualBsonBinary.getData();
+
+ // then
+ assertEquals(BsonBinarySubType.VECTOR.getValue(), actualBsonBinary.getType(), "The subtype must be VECTOR");
+ assertArrayEquals(expectedBsonEncodedVector, actualBsonEncodedVector);
+ }
+
+ @ParameterizedTest
+ @MethodSource("providePackedBitVectors")
+ void shouldDecodePackedBitVector(final PackedBitVector expectedPackedBitVector, final byte[] bsonEncodedVector) {
+ // when
+ PackedBitVector decodedVector = (PackedBitVector) new BsonBinary(BsonBinarySubType.VECTOR, bsonEncodedVector).asVector();
+
+ // then
+ assertEquals(expectedPackedBitVector, decodedVector);
+ }
+
+ private static Stream providePackedBitVectors() {
+ return Stream.of(
+ arguments(
+ Vector.packedBitVector(new byte[]{(byte) 0, (byte) 255, (byte) 10}, (byte) 2),
+ new byte[]{PACKED_BIT_DTYPE, 2, (byte) 0, (byte) 255, (byte) 10}
+ ),
+ arguments(
+ Vector.packedBitVector(new byte[0], (byte) 0),
+ new byte[]{PACKED_BIT_DTYPE, 0}
+ ));
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidFloatArrayLengthWhenDecode() {
+ // given
+ byte[] invalidData = {FLOAT32_DTYPE, 0, 10, 20, 30};
+
+ // when & Then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Byte array length must be a multiple of 4 for FLOAT32 data type, but found: " + invalidData.length,
+ thrown.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1})
+ void shouldThrowExceptionWhenEncodedVectorLengthIsLessThenMetadataLength(final int encodedVectorLength) {
+ // given
+ byte[] invalidData = new byte[encodedVectorLength];
+
+ // when & Then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Vector encoded array length must be at least 2, but found: " + encodedVectorLength,
+ thrown.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1})
+ void shouldThrowExceptionForInvalidFloatArrayPaddingWhenDecode(final byte invalidPadding) {
+ // given
+ byte[] invalidData = {FLOAT32_DTYPE, invalidPadding, 10, 20, 30, 20};
+
+ // when & Then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Padding must be 0 for FLOAT32 data type, but found: " + invalidPadding, thrown.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1})
+ void shouldThrowExceptionForInvalidInt8ArrayPaddingWhenDecode(final byte invalidPadding) {
+ // given
+ byte[] invalidData = {INT8_DTYPE, invalidPadding, 10, 20, 30, 20};
+
+ // when & Then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Padding must be 0 for INT8 data type, but found: " + invalidPadding, thrown.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 8})
+ void shouldThrowExceptionForInvalidPackedBitArrayPaddingWhenDecode(final byte invalidPadding) {
+ // given
+ byte[] invalidData = {PACKED_BIT_DTYPE, invalidPadding, 10, 20, 30, 20};
+
+ // when & then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Padding must be between 0 and 7 bits, but found: " + invalidPadding, thrown.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1, 2, 3, 4, 5, 6, 7, 8})
+ void shouldThrowExceptionForInvalidPackedBitArrayPaddingWhenDecodeEmptyVector(final byte invalidPadding) {
+ // given
+ byte[] invalidData = {PACKED_BIT_DTYPE, invalidPadding};
+
+ // when & Then
+ BsonInvalidOperationException thrown = assertThrows(BsonInvalidOperationException.class, () -> {
+ new BsonBinary(BsonBinarySubType.VECTOR, invalidData).asVector();
+ });
+ assertEquals("Padding must be 0 if vector is empty, but found: " + invalidPadding, thrown.getMessage());
+ }
+
+ @Test
+ void shouldThrowWhenUnknownVectorDType() {
+ // when
+ BsonBinary bsonBinary = new BsonBinary(BsonBinarySubType.VECTOR, new byte[]{(byte) 0});
+ assertThrows(BsonInvalidOperationException.class, bsonBinary::asVector);
+ }
+}
diff --git a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java
index 15e27065ba2..c9e22fcce7a 100644
--- a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java
+++ b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java
@@ -40,6 +40,9 @@
public class BsonBinaryWriterTest {
+ private static final byte FLOAT32_DTYPE = Vector.DataType.FLOAT32.getValue();
+ private static final int ZERO_PADDING = 0;
+
private BsonBinaryWriter writer;
private BasicOutputBuffer buffer;
@@ -299,12 +302,38 @@ public void testWriteBinary() {
writer.writeBinaryData("b1", new BsonBinary(new byte[]{0, 0, 0, 0, 0, 0, 0, 0}));
writer.writeBinaryData("b2", new BsonBinary(BsonBinarySubType.OLD_BINARY, new byte[]{1, 1, 1, 1, 1}));
writer.writeBinaryData("b3", new BsonBinary(BsonBinarySubType.FUNCTION, new byte[]{}));
+ writer.writeBinaryData("b4", new BsonBinary(BsonBinarySubType.VECTOR, new byte[]{FLOAT32_DTYPE, ZERO_PADDING,
+ (byte) 205, (byte) 204, (byte) 140, (byte) 63}));
writer.writeEndDocument();
+ byte[] expectedValues = new byte[]{
+ 64, // total document length
+ 0, 0, 0,
+
+ //Binary
+ (byte) BsonType.BINARY.getValue(),
+ 98, 49, 0, // name "b1"
+ 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ // Old binary
+ (byte) BsonType.BINARY.getValue(),
+ 98, 50, 0, // name "b2"
+ 9, 0, 0, 0, 2, 5, 0, 0, 0, 1, 1, 1, 1, 1,
+
+ // Function binary
+ (byte) BsonType.BINARY.getValue(),
+ 98, 51, 0, // name "b3"
+ 0, 0, 0, 0, 1,
+
+ //Vector binary
+ (byte) BsonType.BINARY.getValue(),
+ 98, 52, 0, // name "b4"
+ 6, 0, 0, 0, // total length, int32 (little endian)
+ BsonBinarySubType.VECTOR.getValue(), FLOAT32_DTYPE, ZERO_PADDING, (byte) 205, (byte) 204, (byte) 140, 63,
+
+ 0 //end of document
+ };
- byte[] expectedValues = {49, 0, 0, 0, 5, 98, 49, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 98, 50, 0,
- 9, 0,
- 0, 0, 2, 5, 0, 0, 0, 1, 1, 1, 1, 1, 5, 98, 51, 0, 0, 0, 0, 0, 1, 0};
assertArrayEquals(expectedValues, buffer.toByteArray());
}
diff --git a/bson/src/test/unit/org/bson/BsonHelper.java b/bson/src/test/unit/org/bson/BsonHelper.java
index 985e398b1ca..59fdba474a2 100644
--- a/bson/src/test/unit/org/bson/BsonHelper.java
+++ b/bson/src/test/unit/org/bson/BsonHelper.java
@@ -17,10 +17,12 @@
package org.bson;
import org.bson.codecs.BsonDocumentCodec;
+import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.io.BasicOutputBuffer;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
+import util.Hex;
import java.nio.ByteBuffer;
import java.util.Date;
@@ -109,4 +111,23 @@ public static ByteBuffer toBson(final BsonDocument document) {
private BsonHelper() {
}
+
+ public static BsonDocument decodeToDocument(final String subjectHex, final String description) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(Hex.decode(subjectHex));
+ BsonDocument actualDecodedDocument = new BsonDocumentCodec().decode(new BsonBinaryReader(byteBuffer),
+ DecoderContext.builder().build());
+
+ if (byteBuffer.hasRemaining()) {
+ throw new BsonSerializationException(format("Should have consumed all bytes, but " + byteBuffer.remaining()
+ + " still remain in the buffer for document with description ",
+ description));
+ }
+ return actualDecodedDocument;
+ }
+
+ public static String encodeToHex(final BsonDocument decodedDocument) {
+ BasicOutputBuffer outputBuffer = new BasicOutputBuffer();
+ new BsonDocumentCodec().encode(new BsonBinaryWriter(outputBuffer), decodedDocument, EncoderContext.builder().build());
+ return Hex.encode(outputBuffer.toByteArray());
+ }
}
diff --git a/bson/src/test/unit/org/bson/GenericBsonTest.java b/bson/src/test/unit/org/bson/GenericBsonTest.java
index 2f50bcd7f61..6ba2c6ae382 100644
--- a/bson/src/test/unit/org/bson/GenericBsonTest.java
+++ b/bson/src/test/unit/org/bson/GenericBsonTest.java
@@ -16,10 +16,6 @@
package org.bson;
-import org.bson.codecs.BsonDocumentCodec;
-import org.bson.codecs.DecoderContext;
-import org.bson.codecs.EncoderContext;
-import org.bson.io.BasicOutputBuffer;
import org.bson.json.JsonMode;
import org.bson.json.JsonParseException;
import org.bson.json.JsonWriterSettings;
@@ -27,7 +23,6 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import util.Hex;
import util.JsonPoweredTestHelper;
import java.io.File;
@@ -35,7 +30,6 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URISyntaxException;
-import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,6 +38,8 @@
import static java.lang.String.format;
import static org.bson.BsonDocument.parse;
+import static org.bson.BsonHelper.decodeToDocument;
+import static org.bson.BsonHelper.encodeToHex;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -207,25 +203,6 @@ private boolean shouldEscapeCharacter(final char escapedChar) {
}
}
- private BsonDocument decodeToDocument(final String subjectHex, final String description) {
- ByteBuffer byteBuffer = ByteBuffer.wrap(Hex.decode(subjectHex));
- BsonDocument actualDecodedDocument = new BsonDocumentCodec().decode(new BsonBinaryReader(byteBuffer),
- DecoderContext.builder().build());
-
- if (byteBuffer.hasRemaining()) {
- throw new BsonSerializationException(format("Should have consumed all bytes, but " + byteBuffer.remaining()
- + " still remain in the buffer for document with description ",
- description));
- }
- return actualDecodedDocument;
- }
-
- private String encodeToHex(final BsonDocument decodedDocument) {
- BasicOutputBuffer outputBuffer = new BasicOutputBuffer();
- new BsonDocumentCodec().encode(new BsonBinaryWriter(outputBuffer), decodedDocument, EncoderContext.builder().build());
- return Hex.encode(outputBuffer.toByteArray());
- }
-
private void runDecodeError(final BsonDocument testCase) {
try {
String description = testCase.getString("description").getValue();
diff --git a/bson/src/test/unit/org/bson/VectorTest.java b/bson/src/test/unit/org/bson/VectorTest.java
new file mode 100644
index 00000000000..36cc7156db6
--- /dev/null
+++ b/bson/src/test/unit/org/bson/VectorTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 org.bson;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class VectorTest {
+
+ @Test
+ void shouldCreateInt8Vector() {
+ // given
+ byte[] data = {1, 2, 3, 4, 5};
+
+ // when
+ Int8Vector vector = Vector.int8Vector(data);
+
+ // then
+ assertNotNull(vector);
+ assertEquals(Vector.DataType.INT8, vector.getDataType());
+ assertArrayEquals(data, vector.getData());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenCreatingInt8VectorWithNullData() {
+ // given
+ byte[] data = null;
+
+ // when & Then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Vector.int8Vector(data));
+ assertEquals("data can not be null", exception.getMessage());
+ }
+
+ @Test
+ void shouldCreateFloat32Vector() {
+ // given
+ float[] data = {1.0f, 2.0f, 3.0f};
+
+ // when
+ Float32Vector vector = Vector.floatVector(data);
+
+ // then
+ assertNotNull(vector);
+ assertEquals(Vector.DataType.FLOAT32, vector.getDataType());
+ assertArrayEquals(data, vector.getData());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenCreatingFloat32VectorWithNullData() {
+ // given
+ float[] data = null;
+
+ // when & Then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Vector.floatVector(data));
+ assertEquals("data can not be null", exception.getMessage());
+ }
+
+
+ @ParameterizedTest(name = "{index}: validPadding={0}")
+ @ValueSource(bytes = {0, 1, 2, 3, 4, 5, 6, 7})
+ void shouldCreatePackedBitVector(final byte validPadding) {
+ // given
+ byte[] data = {(byte) 0b10101010, (byte) 0b01010101};
+
+ // when
+ PackedBitVector vector = Vector.packedBitVector(data, validPadding);
+
+ // then
+ assertNotNull(vector);
+ assertEquals(Vector.DataType.PACKED_BIT, vector.getDataType());
+ assertArrayEquals(data, vector.getData());
+ assertEquals(validPadding, vector.getPadding());
+ }
+
+ @ParameterizedTest(name = "{index}: invalidPadding={0}")
+ @ValueSource(bytes = {-1, 8})
+ void shouldThrowExceptionWhenPackedBitVectorHasInvalidPadding(final byte invalidPadding) {
+ // given
+ byte[] data = {(byte) 0b10101010};
+
+ // when & Then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
+ Vector.packedBitVector(data, invalidPadding));
+ assertEquals("state should be: Padding must be between 0 and 7 bits. Provided padding: " + invalidPadding, exception.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenPackedBitVectorIsCreatedWithNullData() {
+ // given
+ byte[] data = null;
+ byte padding = 0;
+
+ // when & Then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
+ Vector.packedBitVector(data, padding));
+ assertEquals("data can not be null", exception.getMessage());
+ }
+
+ @Test
+ void shouldCreatePackedBitVectorWithZeroPaddingAndEmptyData() {
+ // given
+ byte[] data = new byte[0];
+ byte padding = 0;
+
+ // when
+ PackedBitVector vector = Vector.packedBitVector(data, padding);
+
+ // then
+ assertNotNull(vector);
+ assertEquals(Vector.DataType.PACKED_BIT, vector.getDataType());
+ assertArrayEquals(data, vector.getData());
+ assertEquals(padding, vector.getPadding());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenPackedBitVectorWithNonZeroPaddingAndEmptyData() {
+ // given
+ byte[] data = new byte[0];
+ byte padding = 1;
+
+ // when & Then
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
+ Vector.packedBitVector(data, padding));
+ assertEquals("state should be: Padding must be 0 if vector is empty. Provided padding: " + padding, exception.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenRetrievingInt8DataFromNonInt8Vector() {
+ // given
+ float[] data = {1.0f, 2.0f};
+ Vector vector = Vector.floatVector(data);
+
+ // when & Then
+ IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asInt8Vector);
+ assertEquals("Expected vector data type INT8, but found FLOAT32", exception.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenRetrievingFloat32DataFromNonFloat32Vector() {
+ // given
+ byte[] data = {1, 2, 3};
+ Vector vector = Vector.int8Vector(data);
+
+ // when & Then
+ IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asFloat32Vector);
+ assertEquals("Expected vector data type FLOAT32, but found INT8", exception.getMessage());
+ }
+
+ @Test
+ void shouldThrowExceptionWhenRetrievingPackedBitDataFromNonPackedBitVector() {
+ // given
+ float[] data = {1.0f, 2.0f};
+ Vector vector = Vector.floatVector(data);
+
+ // when & Then
+ IllegalStateException exception = assertThrows(IllegalStateException.class, vector::asPackedBitVector);
+ assertEquals("Expected vector data type PACKED_BIT, but found FLOAT32", exception.getMessage());
+ }
+}
diff --git a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java
index 79c65573556..67c6b561aa5 100644
--- a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java
+++ b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java
@@ -23,6 +23,7 @@
import org.bson.BsonObjectId;
import org.bson.ByteBufNIO;
import org.bson.Document;
+import org.bson.Vector;
import org.bson.io.BasicOutputBuffer;
import org.bson.io.BsonInput;
import org.bson.io.ByteBufferBsonInput;
@@ -80,6 +81,9 @@ public void testPrimitiveBSONTypeCodecs() throws IOException {
doc.put("code", new Code("var i = 0"));
doc.put("minkey", new MinKey());
doc.put("maxkey", new MaxKey());
+ doc.put("vectorFloat", Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}));
+ doc.put("vectorInt8", Vector.int8Vector(new byte[]{10, 20, 30, 40}));
+ doc.put("vectorPackedBit", Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3));
// doc.put("pattern", Pattern.compile("^hello")); // TODO: Pattern doesn't override equals method!
doc.put("null", null);
diff --git a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy
index c20299715e0..23c46fb7b0b 100644
--- a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy
+++ b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy
@@ -17,6 +17,10 @@
package org.bson.codecs
import org.bson.Document
+import org.bson.Float32Vector
+import org.bson.Int8Vector
+import org.bson.PackedBitVector
+import org.bson.Vector
import org.bson.codecs.configuration.CodecRegistries
import org.bson.types.Binary
import org.bson.types.Code
@@ -32,6 +36,8 @@ import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import java.util.regex.Pattern
+//Codenarc
+@SuppressWarnings("VectorIsObsolete")
class ValueCodecProviderSpecification extends Specification {
private final provider = new ValueCodecProvider()
private final registry = CodecRegistries.fromProviders(provider)
@@ -56,6 +62,10 @@ class ValueCodecProviderSpecification extends Specification {
provider.get(Short, registry) instanceof ShortCodec
provider.get(byte[], registry) instanceof ByteArrayCodec
provider.get(Float, registry) instanceof FloatCodec
+ provider.get(Vector, registry) instanceof VectorCodec
+ provider.get(Float32Vector, registry) instanceof Float32VectorCodec
+ provider.get(Int8Vector, registry) instanceof Int8VectorCodec
+ provider.get(PackedBitVector, registry) instanceof PackedBitVectorCodec
provider.get(Binary, registry) instanceof BinaryCodec
provider.get(MinKey, registry) instanceof MinKeyCodec
diff --git a/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java b/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java
new file mode 100644
index 00000000000..bf33af90cae
--- /dev/null
+++ b/bson/src/test/unit/org/bson/codecs/VectorCodecTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 org.bson.codecs;
+
+import org.bson.BsonBinary;
+import org.bson.BsonBinaryReader;
+import org.bson.BsonBinarySubType;
+import org.bson.BsonBinaryWriter;
+import org.bson.BsonDocument;
+import org.bson.BsonInvalidOperationException;
+import org.bson.BsonType;
+import org.bson.BsonWriter;
+import org.bson.ByteBufNIO;
+import org.bson.Float32Vector;
+import org.bson.Int8Vector;
+import org.bson.PackedBitVector;
+import org.bson.Vector;
+import org.bson.io.BasicOutputBuffer;
+import org.bson.io.ByteBufferBsonInput;
+import org.bson.io.OutputBuffer;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.stream.Stream;
+
+import static org.bson.BsonHelper.toBson;
+import static org.bson.assertions.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+class VectorCodecTest extends CodecTestCase {
+
+ private static Stream provideVectorsAndCodecs() {
+ return Stream.of(
+ arguments(Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}), new Float32VectorCodec(), Float32Vector.class),
+ arguments(Vector.int8Vector(new byte[]{10, 20, 30, 40}), new Int8VectorCodec(), Int8Vector.class),
+ arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new PackedBitVectorCodec(), PackedBitVector.class),
+ arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), Vector.class),
+ arguments(Vector.int8Vector(new byte[]{10, 20, 30, 40}), new VectorCodec(), Vector.class),
+ arguments(Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3), new VectorCodec(), Vector.class)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideVectorsAndCodecs")
+ void shouldEncodeVector(final Vector vectorToEncode, final Codec vectorCodec) throws IOException {
+ // given
+ BsonBinary bsonBinary = new BsonBinary(vectorToEncode);
+ byte[] encodedVector = bsonBinary.getData();
+ ByteArrayOutputStream expectedStream = new ByteArrayOutputStream();
+ // Total length of a Document (int 32). It is 0, because we do not expect
+ // codec to write the end of the document (that is when we back-patch the length of the document).
+ expectedStream.write(new byte[]{0, 0, 0, 0});
+ // Bson type
+ expectedStream.write((byte) BsonType.BINARY.getValue());
+ // Field name "b4"
+ expectedStream.write(new byte[]{98, 52, 0});
+ // Total length of binary data (little-endian format)
+ expectedStream.write(new byte[]{(byte) encodedVector.length, 0, 0, 0});
+ // Vector binary subtype
+ expectedStream.write(BsonBinarySubType.VECTOR.getValue());
+ // Actual BSON binary data
+ expectedStream.write(encodedVector);
+
+ OutputBuffer buffer = new BasicOutputBuffer();
+ BsonWriter writer = new BsonBinaryWriter(buffer);
+ writer.writeStartDocument();
+ writer.writeName("b4");
+
+ // when
+ vectorCodec.encode(writer, vectorToEncode, EncoderContext.builder().build());
+
+ // then
+ assertArrayEquals(expectedStream.toByteArray(), buffer.toByteArray());
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideVectorsAndCodecs")
+ void shouldDecodeVector(final Vector vectorToDecode, final Codec vectorCodec) {
+ // given
+ OutputBuffer buffer = new BasicOutputBuffer();
+ BsonWriter writer = new BsonBinaryWriter(buffer);
+ writer.writeStartDocument();
+ writer.writeName("vector");
+ writer.writeBinaryData(new BsonBinary(vectorToDecode));
+ writer.writeEndDocument();
+
+ BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray()))));
+ reader.readStartDocument();
+
+ // when
+ Vector decodedVector = vectorCodec.decode(reader, DecoderContext.builder().build());
+
+ // then
+ assertDoesNotThrow(reader::readEndDocument);
+ assertNotNull(decodedVector);
+ assertEquals(vectorToDecode, decodedVector);
+ }
+
+
+ @ParameterizedTest
+ @EnumSource(value = BsonBinarySubType.class, mode = EnumSource.Mode.EXCLUDE, names = {"VECTOR"})
+ void shouldThrowExceptionForInvalidSubType(final BsonBinarySubType subType) {
+ // given
+ BsonDocument document = new BsonDocument("name", new BsonBinary(subType.getValue(), new byte[]{}));
+ BsonBinaryReader reader = new BsonBinaryReader(toBson(document));
+ reader.readStartDocument();
+
+ // when & then
+ Stream.of(new Float32VectorCodec(), new Int8VectorCodec(), new PackedBitVectorCodec())
+ .forEach(codec -> {
+ BsonInvalidOperationException exception = assertThrows(BsonInvalidOperationException.class, () ->
+ codec.decode(reader, DecoderContext.builder().build()));
+ assertEquals("Expected vector binary subtype 9 but found: " + subType.getValue(), exception.getMessage());
+ });
+ }
+
+
+ @ParameterizedTest
+ @MethodSource("provideVectorsAndCodecs")
+ void shouldReturnCorrectEncoderClass(final Vector vector,
+ final Codec extends Vector> codec,
+ final Class extends Vector> expectedEncoderClass) {
+ // when
+ Class extends Vector> encoderClass = codec.getEncoderClass();
+
+ // then
+ assertEquals(expectedEncoderClass, encoderClass);
+ }
+}
diff --git a/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java b/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java
new file mode 100644
index 00000000000..64e84f6afc8
--- /dev/null
+++ b/bson/src/test/unit/org/bson/vector/VectorGenericBsonTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 org.bson.vector;
+
+import org.bson.BsonArray;
+import org.bson.BsonBinary;
+import org.bson.BsonDocument;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+import org.bson.Float32Vector;
+import org.bson.PackedBitVector;
+import org.bson.Vector;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import util.JsonPoweredTestHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static java.lang.String.format;
+import static org.bson.BsonHelper.decodeToDocument;
+import static org.bson.BsonHelper.encodeToHex;
+import static org.bson.internal.vector.VectorHelper.determineVectorDType;
+import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+/**
+ * See
+ * JSON-based tests that included in test resources.
+ */
+class VectorGenericBsonTest {
+
+ private static final List TEST_NAMES_TO_IGNORE = Arrays.asList(
+ //NO API to set padding for floats available.
+ "FLOAT32 with padding",
+ //NO API to set padding for floats available.
+ "INT8 with padding",
+ //It is impossible to provide float inputs for INT8 in the API.
+ "INT8 with float inputs",
+ //It is impossible to provide float inputs for INT8.
+ "Underflow Vector PACKED_BIT",
+ //It is impossible to provide float inputs for PACKED_BIT in the API.
+ "Vector with float values PACKED_BIT",
+ //It is impossible to provide float inputs for INT8.
+ "Overflow Vector PACKED_BIT",
+ //It is impossible to overflow byte with values higher than 127 in the API.
+ "Overflow Vector INT8",
+ //It is impossible to underflow byte with values lower than -128 in the API.
+ "Underflow Vector INT8");
+
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideTestCases")
+ void shouldPassAllOutcomes(@SuppressWarnings("unused") final String description,
+ final BsonDocument testDefinition, final BsonDocument testCase) {
+ assumeFalse(TEST_NAMES_TO_IGNORE.contains(testCase.get("description").asString().getValue()));
+
+ String testKey = testDefinition.getString("test_key").getValue();
+ boolean isValidVector = testCase.getBoolean("valid").getValue();
+ if (isValidVector) {
+ runValidTestCase(testKey, testCase);
+ } else {
+ runInvalidTestCase(testCase);
+ }
+ }
+
+ private static void runInvalidTestCase(final BsonDocument testCase) {
+ BsonArray arrayVector = testCase.getArray("vector");
+ byte expectedPadding = (byte) testCase.getInt32("padding").getValue();
+ byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue());
+ Vector.DataType expectedDType = determineVectorDType(dtypeByte);
+
+ switch (expectedDType) {
+ case INT8:
+ byte[] expectedVectorData = toByteArray(arrayVector);
+ assertValidationException(assertThrows(RuntimeException.class,
+ () -> Vector.int8Vector(expectedVectorData)));
+ break;
+ case PACKED_BIT:
+ byte[] expectedVectorPackedBitData = toByteArray(arrayVector);
+ assertValidationException(assertThrows(RuntimeException.class,
+ () -> Vector.packedBitVector(expectedVectorPackedBitData, expectedPadding)));
+ break;
+ case FLOAT32:
+ float[] expectedFloatVector = toFloatArray(arrayVector);
+ assertValidationException(assertThrows(RuntimeException.class, () -> Vector.floatVector(expectedFloatVector)));
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType);
+ }
+ }
+
+ private static void runValidTestCase(final String testKey, final BsonDocument testCase) {
+ String description = testCase.getString("description").getValue();
+ byte dtypeByte = Byte.decode(testCase.getString("dtype_hex").getValue());
+
+ byte expectedPadding = (byte) testCase.getInt32("padding").getValue();
+ Vector.DataType expectedDType = determineVectorDType(dtypeByte);
+ String expectedCanonicalBsonHex = testCase.getString("canonical_bson").getValue().toUpperCase();
+
+ BsonArray arrayVector = testCase.getArray("vector");
+ BsonDocument actualDecodedDocument = decodeToDocument(expectedCanonicalBsonHex, description);
+ Vector actualVector = actualDecodedDocument.getBinary("vector").asVector();
+
+ switch (expectedDType) {
+ case INT8:
+ byte[] expectedVectorData = toByteArray(arrayVector);
+ byte[] actualVectorData = actualVector.asInt8Vector().getData();
+ assertVectorDecoding(
+ expectedVectorData,
+ expectedDType,
+ actualVectorData,
+ actualVector);
+
+ assertThatVectorCreationResultsInCorrectBinary(Vector.int8Vector(expectedVectorData),
+ testKey,
+ actualDecodedDocument,
+ expectedCanonicalBsonHex,
+ description);
+ break;
+ case PACKED_BIT:
+ PackedBitVector actualPackedBitVector = actualVector.asPackedBitVector();
+ byte[] expectedVectorPackedBitData = toByteArray(arrayVector);
+ assertVectorDecoding(
+ expectedVectorPackedBitData,
+ expectedDType, expectedPadding,
+ actualPackedBitVector);
+
+ assertThatVectorCreationResultsInCorrectBinary(
+ Vector.packedBitVector(expectedVectorPackedBitData, expectedPadding),
+ testKey,
+ actualDecodedDocument,
+ expectedCanonicalBsonHex,
+ description);
+ break;
+ case FLOAT32:
+ Float32Vector actualFloat32Vector = actualVector.asFloat32Vector();
+ float[] expectedFloatVector = toFloatArray(arrayVector);
+ assertVectorDecoding(
+ expectedFloatVector,
+ expectedDType,
+ actualFloat32Vector);
+ assertThatVectorCreationResultsInCorrectBinary(
+ Vector.floatVector(expectedFloatVector),
+ testKey,
+ actualDecodedDocument,
+ expectedCanonicalBsonHex,
+ description);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported vector data type: " + expectedDType);
+ }
+ }
+
+ private static void assertValidationException(final RuntimeException runtimeException) {
+ assertTrue(runtimeException instanceof IllegalArgumentException || runtimeException instanceof IllegalStateException);
+ }
+
+ private static void assertThatVectorCreationResultsInCorrectBinary(final Vector expectedVectorData,
+ final String testKey,
+ final BsonDocument actualDecodedDocument,
+ final String expectedCanonicalBsonHex,
+ final String description) {
+ BsonDocument documentToEncode = new BsonDocument(testKey, new BsonBinary(expectedVectorData));
+ assertEquals(documentToEncode, actualDecodedDocument);
+ assertEquals(expectedCanonicalBsonHex, encodeToHex(documentToEncode),
+ format("Failed to create expected BSON for document with description '%s'", description));
+ }
+
+ private static void assertVectorDecoding(final byte[] expectedVectorData,
+ final Vector.DataType expectedDType,
+ final byte[] actualVectorData,
+ final Vector actualVector) {
+ Assertions.assertArrayEquals(actualVectorData, expectedVectorData,
+ () -> "Actual: " + Arrays.toString(actualVectorData) + " != Expected:" + Arrays.toString(expectedVectorData));
+ assertEquals(expectedDType, actualVector.getDataType());
+ }
+
+ private static void assertVectorDecoding(final byte[] expectedVectorData,
+ final Vector.DataType expectedDType,
+ final byte expectedPadding,
+ final PackedBitVector actualVector) {
+ byte[] actualVectorData = actualVector.getData();
+ assertVectorDecoding(
+ expectedVectorData,
+ expectedDType,
+ actualVectorData,
+ actualVector);
+ assertEquals(expectedPadding, actualVector.getPadding());
+ }
+
+ private static void assertVectorDecoding(final float[] expectedVectorData,
+ final Vector.DataType expectedDType,
+ final Float32Vector actualVector) {
+ float[] actualVectorArray = actualVector.getData();
+ Assertions.assertArrayEquals(actualVectorArray, expectedVectorData,
+ () -> "Actual: " + Arrays.toString(actualVectorArray) + " != Expected:" + Arrays.toString(expectedVectorData));
+ assertEquals(expectedDType, actualVector.getDataType());
+ }
+
+ private static byte[] toByteArray(final BsonArray arrayVector) {
+ byte[] bytes = new byte[arrayVector.size()];
+ for (int i = 0; i < arrayVector.size(); i++) {
+ bytes[i] = (byte) arrayVector.get(i).asInt32().getValue();
+ }
+ return bytes;
+ }
+
+ private static float[] toFloatArray(final BsonArray arrayVector) {
+ float[] floats = new float[arrayVector.size()];
+ for (int i = 0; i < arrayVector.size(); i++) {
+ BsonValue bsonValue = arrayVector.get(i);
+ if (bsonValue.isString()) {
+ floats[i] = parseFloat(bsonValue.asString());
+ } else {
+ floats[i] = (float) arrayVector.get(i).asDouble().getValue();
+ }
+ }
+ return floats;
+ }
+
+ private static float parseFloat(final BsonString bsonValue) {
+ String floatValue = bsonValue.getValue();
+ switch (floatValue) {
+ case "-inf":
+ return Float.NEGATIVE_INFINITY;
+ case "inf":
+ return Float.POSITIVE_INFINITY;
+ default:
+ return Float.parseFloat(floatValue);
+ }
+ }
+
+ private static Stream provideTestCases() throws URISyntaxException, IOException {
+ List data = new ArrayList<>();
+ for (File file : JsonPoweredTestHelper.getTestFiles("/bson-binary-vector")) {
+ BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file);
+ for (BsonValue curValue : testDocument.getArray("tests", new BsonArray())) {
+ BsonDocument testCaseDocument = curValue.asDocument();
+ data.add(Arguments.of(createTestCaseDescription(testDocument, testCaseDocument), testDocument, testCaseDocument));
+ }
+ }
+ return data.stream();
+ }
+
+ private static String createTestCaseDescription(final BsonDocument testDocument,
+ final BsonDocument testCaseDocument) {
+ boolean isValidTestCase = testCaseDocument.getBoolean("valid").getValue();
+ String fileDescription = testDocument.getString("description").getValue();
+ String testDescription = testCaseDocument.getString("description").getValue();
+ return "[Valid input: " + isValidTestCase + "] " + fileDescription + ": " + testDescription;
+ }
+}
diff --git a/build.gradle b/build.gradle
index d9ebd912fb8..54679fa5d88 100644
--- a/build.gradle
+++ b/build.gradle
@@ -75,7 +75,7 @@ configure(coreProjects) {
apply plugin: 'idea'
group = 'org.mongodb'
- version = '5.2.0-SNAPSHOT'
+ version = '5.3.0-beta0'
repositories {
mavenLocal()
@@ -109,6 +109,7 @@ configure(javaProjects) {
configure(scalaProjects) {
apply plugin: 'scala'
+ apply plugin: 'java-library'
apply plugin: 'idea'
apply plugin: "com.adtran.scala-multiversion-plugin"
apply plugin: "com.diffplug.spotless"
@@ -116,8 +117,8 @@ configure(scalaProjects) {
group = 'org.mongodb.scala'
dependencies {
- implementation ('org.scala-lang:scala-library:%scala-version%')
- implementation ('org.scala-lang:scala-reflect:%scala-version%')
+ api ('org.scala-lang:scala-library:%scala-version%')
+ api ('org.scala-lang:scala-reflect:%scala-version%')
testImplementation(platform("org.junit:junit-bom:$junitBomVersion"))
testImplementation("org.junit.vintage:junit-vintage-engine")
@@ -157,7 +158,7 @@ configure(scalaProjects) {
"-feature",
"-unchecked",
"-language:reflectiveCalls",
- "-Wconf:cat=deprecation:ws,any:e",
+ "-Wconf:cat=deprecation:ws",
"-Wconf:msg=While parsing annotations in:silent",
"-Xlint:strict-unsealed-patmat"
]
diff --git a/driver-core/build.gradle b/driver-core/build.gradle
index c23a24a9fb8..70061ca2b1e 100644
--- a/driver-core/build.gradle
+++ b/driver-core/build.gradle
@@ -82,6 +82,7 @@ afterEvaluate {
'!sun.misc.*', // Used by DirectBufferDeallocator only for java 8
'!sun.nio.ch.*', // Used by DirectBufferDeallocator only for java 8
'!javax.annotation.*', // Brought in by com.google.code.findbugs:annotations
+ '!com.oracle.svm.core.annotate.*', // this dependency is provided by the GraalVM runtime
'io.netty.*;resolution:=optional',
'com.amazonaws.*;resolution:=optional',
'software.amazon.awssdk.*;resolution:=optional',
diff --git a/driver-core/src/main/com/mongodb/client/model/Aggregates.java b/driver-core/src/main/com/mongodb/client/model/Aggregates.java
index 4bb3a03771c..7d6306cdd23 100644
--- a/driver-core/src/main/com/mongodb/client/model/Aggregates.java
+++ b/driver-core/src/main/com/mongodb/client/model/Aggregates.java
@@ -37,6 +37,7 @@
import org.bson.BsonType;
import org.bson.BsonValue;
import org.bson.Document;
+import org.bson.Vector;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
@@ -963,28 +964,37 @@ public static Bson vectorSearch(
notNull("queryVector", queryVector);
notNull("index", index);
notNull("options", options);
- return new Bson() {
- @Override
- public BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry) {
- Document specificationDoc = new Document("path", path.toValue())
- .append("queryVector", queryVector)
- .append("index", index)
- .append("limit", limit);
- specificationDoc.putAll(options.toBsonDocument(documentClass, codecRegistry));
- return new Document("$vectorSearch", specificationDoc).toBsonDocument(documentClass, codecRegistry);
- }
+ return new VectorSearchBson(path, queryVector, index, limit, options);
+ }
- @Override
- public String toString() {
- return "Stage{name=$vectorSearch"
- + ", path=" + path
- + ", queryVector=" + queryVector
- + ", index=" + index
- + ", limit=" + limit
- + ", options=" + options
- + '}';
- }
- };
+ /**
+ * Creates a {@code $vectorSearch} pipeline stage supported by MongoDB Atlas.
+ * You may use the {@code $meta: "vectorSearchScore"} expression, e.g., via {@link Projections#metaVectorSearchScore(String)},
+ * to extract the relevance score assigned to each found document.
+ *
+ * @param queryVector The {@linkplain Vector query vector}. The number of dimensions must match that of the {@code index}.
+ * @param path The field to be searched.
+ * @param index The name of the index to use.
+ * @param limit The limit on the number of documents produced by the pipeline stage.
+ * @param options Optional {@code $vectorSearch} pipeline stage fields.
+ * @return The {@code $vectorSearch} pipeline stage.
+ * @mongodb.atlas.manual atlas-vector-search/vector-search-stage/ $vectorSearch
+ * @mongodb.atlas.manual atlas-search/scoring/ Scoring
+ * @mongodb.server.release 6.0
+ * @see Vector
+ * @since 5.3
+ */
+ public static Bson vectorSearch(
+ final FieldSearchPath path,
+ final Vector queryVector,
+ final String index,
+ final long limit,
+ final VectorSearchOptions options) {
+ notNull("path", path);
+ notNull("queryVector", queryVector);
+ notNull("index", index);
+ notNull("options", options);
+ return new VectorSearchBson(path, queryVector, index, limit, options);
}
/**
@@ -2145,6 +2155,45 @@ public String toString() {
}
}
+ private static class VectorSearchBson implements Bson {
+ private final FieldSearchPath path;
+ private final Object queryVector;
+ private final String index;
+ private final long limit;
+ private final VectorSearchOptions options;
+
+ VectorSearchBson(final FieldSearchPath path, final Object queryVector,
+ final String index, final long limit,
+ final VectorSearchOptions options) {
+ this.path = path;
+ this.queryVector = queryVector;
+ this.index = index;
+ this.limit = limit;
+ this.options = options;
+ }
+
+ @Override
+ public BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry) {
+ Document specificationDoc = new Document("path", path.toValue())
+ .append("queryVector", queryVector)
+ .append("index", index)
+ .append("limit", limit);
+ specificationDoc.putAll(options.toBsonDocument(documentClass, codecRegistry));
+ return new Document("$vectorSearch", specificationDoc).toBsonDocument(documentClass, codecRegistry);
+ }
+
+ @Override
+ public String toString() {
+ return "Stage{name=$vectorSearch"
+ + ", path=" + path
+ + ", queryVector=" + queryVector
+ + ", index=" + index
+ + ", limit=" + limit
+ + ", options=" + options
+ + '}';
+ }
+ }
+
private Aggregates() {
}
}
diff --git a/driver-core/src/main/com/mongodb/connection/ClusterSettings.java b/driver-core/src/main/com/mongodb/connection/ClusterSettings.java
index 0af168725cd..01e5c140441 100644
--- a/driver-core/src/main/com/mongodb/connection/ClusterSettings.java
+++ b/driver-core/src/main/com/mongodb/connection/ClusterSettings.java
@@ -36,7 +36,6 @@
import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.connection.ServerAddressHelper.createServerAddress;
-import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -607,11 +606,6 @@ private ClusterSettings(final Builder builder) {
if (builder.srvHost.contains(":")) {
throw new IllegalArgumentException("The srvHost can not contain a host name that specifies a port");
}
-
- if (builder.srvHost.split("\\.").length < 3) {
- throw new IllegalArgumentException(format("An SRV host name '%s' was provided that does not contain at least three parts. "
- + "It must contain a hostname, domain name and a top level domain.", builder.srvHost));
- }
}
if (builder.hosts.size() > 1 && builder.requiredClusterType == ClusterType.STANDALONE) {
diff --git a/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java b/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java
index d483e220253..f7b433b85bd 100644
--- a/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java
+++ b/driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java
@@ -64,13 +64,19 @@ public DefaultDnsResolver(@Nullable final DnsClient dnsClient) {
The priority and weight are ignored, and we just concatenate the host (after removing the ending '.') and port with a
':' in between, as expected by ServerAddress.
-
- It's required that the srvHost has at least three parts (e.g. foo.bar.baz) and that all of the resolved hosts have a parent
- domain equal to the domain of the srvHost.
*/
@Override
public List resolveHostFromSrvRecords(final String srvHost, final String srvServiceName) {
- String srvHostDomain = srvHost.substring(srvHost.indexOf('.') + 1);
+ List srvHostParts = asList(srvHost.split("\\."));
+
+ String srvHostDomain;
+ boolean srvHasLessThanThreeParts = srvHostParts.size() < 3;
+ if (srvHasLessThanThreeParts) {
+ srvHostDomain = srvHost;
+ } else {
+ srvHostDomain = srvHost.substring(srvHost.indexOf('.') + 1);
+ }
+
List srvHostDomainParts = asList(srvHostDomain.split("\\."));
List hosts = new ArrayList<>();
String resourceName = "_" + srvServiceName + "._tcp." + srvHost;
@@ -84,9 +90,15 @@ public List resolveHostFromSrvRecords(final String srvHost, final String
String[] split = srvRecord.split(" ");
String resolvedHost = split[3].endsWith(".") ? split[3].substring(0, split[3].length() - 1) : split[3];
String resolvedHostDomain = resolvedHost.substring(resolvedHost.indexOf('.') + 1);
- if (!sameParentDomain(srvHostDomainParts, resolvedHostDomain)) {
+ List resolvedHostDomainParts = asList(resolvedHostDomain.split("\\."));
+ if (!sameDomain(srvHostDomainParts, resolvedHostDomainParts)) {
+ throw new MongoConfigurationException(
+ format("The SRV host name '%s' resolved to a host '%s' that does not share domain name",
+ srvHost, resolvedHost));
+ }
+ if (srvHasLessThanThreeParts && resolvedHostDomainParts.size() <= srvHostDomainParts.size()) {
throw new MongoConfigurationException(
- format("The SRV host name '%s' resolved to a host '%s 'that is not in a sub-domain of the SRV host.",
+ format("The SRV host name '%s' resolved to a host '%s' that does not have at least one more domain level",
srvHost, resolvedHost));
}
hosts.add(resolvedHost + ":" + split[2]);
@@ -98,8 +110,7 @@ public List resolveHostFromSrvRecords(final String srvHost, final String
return hosts;
}
- private static boolean sameParentDomain(final List srvHostDomainParts, final String resolvedHostDomain) {
- List resolvedHostDomainParts = asList(resolvedHostDomain.split("\\."));
+ private static boolean sameDomain(final List srvHostDomainParts, final List resolvedHostDomainParts) {
if (srvHostDomainParts.size() > resolvedHostDomainParts.size()) {
return false;
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java
index 2e52e3fa0ae..a57087e9217 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/CreateSearchIndexesOperation.java
@@ -32,11 +32,11 @@
*
* This class is not part of the public API and may be removed or changed at any time
*/
-final class CreateSearchIndexesOperation extends AbstractWriteSearchIndexOperation {
+public final class CreateSearchIndexesOperation extends AbstractWriteSearchIndexOperation {
private static final String COMMAND_NAME = "createSearchIndexes";
private final List indexRequests;
- CreateSearchIndexesOperation(final MongoNamespace namespace, final List indexRequests) {
+ public CreateSearchIndexesOperation(final MongoNamespace namespace, final List indexRequests) {
super(namespace);
this.indexRequests = assertNotNull(indexRequests);
}
diff --git a/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java b/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
index 0f9a81dbf19..3dfde30511d 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/ListSearchIndexesOperation.java
@@ -42,7 +42,7 @@
*
* This class is not part of the public API and may be removed or changed at any time
*/
-final class ListSearchIndexesOperation
+public final class ListSearchIndexesOperation
implements AsyncExplainableReadOperation>, ExplainableReadOperation> {
private static final String STAGE_LIST_SEARCH_INDEXES = "$listSearchIndexes";
private final MongoNamespace namespace;
@@ -59,9 +59,10 @@ final class ListSearchIndexesOperation
private final String indexName;
private final boolean retryReads;
- ListSearchIndexesOperation(final MongoNamespace namespace, final Decoder decoder, @Nullable final String indexName,
- @Nullable final Integer batchSize, @Nullable final Collation collation, @Nullable final BsonValue comment,
- @Nullable final Boolean allowDiskUse, final boolean retryReads) {
+ public ListSearchIndexesOperation(final MongoNamespace namespace, final Decoder decoder, @Nullable final String indexName,
+ @Nullable final Integer batchSize, @Nullable final Collation collation,
+ @Nullable final BsonValue comment,
+ @Nullable final Boolean allowDiskUse, final boolean retryReads) {
this.namespace = namespace;
this.decoder = decoder;
this.allowDiskUse = allowDiskUse;
diff --git a/driver-core/src/main/com/mongodb/internal/operation/SearchIndexRequest.java b/driver-core/src/main/com/mongodb/internal/operation/SearchIndexRequest.java
index 0d37d2c2178..29b9b1ef34d 100644
--- a/driver-core/src/main/com/mongodb/internal/operation/SearchIndexRequest.java
+++ b/driver-core/src/main/com/mongodb/internal/operation/SearchIndexRequest.java
@@ -31,14 +31,15 @@
*
* This class is not part of the public API and may be removed or changed at any time
*/
-final class SearchIndexRequest {
+public final class SearchIndexRequest {
private final BsonDocument definition;
@Nullable
private final String indexName;
@Nullable
private final SearchIndexType searchIndexType;
- SearchIndexRequest(final BsonDocument definition, @Nullable final String indexName, @Nullable final SearchIndexType searchIndexType) {
+ public SearchIndexRequest(final BsonDocument definition, @Nullable final String indexName,
+ @Nullable final SearchIndexType searchIndexType) {
assertNotNull(definition);
this.definition = definition;
this.indexName = indexName;
diff --git a/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java
new file mode 100644
index 00000000000..15def0f5d71
--- /dev/null
+++ b/driver-core/src/test/functional/com/mongodb/client/model/search/AggregatesVectorSearchIntegrationTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 com.mongodb.client.model.search;
+
+import com.mongodb.MongoInterruptedException;
+import com.mongodb.MongoNamespace;
+import com.mongodb.client.model.Aggregates;
+import com.mongodb.client.model.SearchIndexType;
+import com.mongodb.client.test.CollectionHelper;
+import com.mongodb.internal.operation.SearchIndexRequest;
+import org.bson.BsonDocument;
+import org.bson.Document;
+import org.bson.Vector;
+import org.bson.codecs.DocumentCodec;
+import org.bson.conversions.Bson;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import static com.mongodb.ClusterFixture.isAtlasSearchTest;
+import static com.mongodb.ClusterFixture.serverVersionAtLeast;
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Filters.gt;
+import static com.mongodb.client.model.Filters.gte;
+import static com.mongodb.client.model.Filters.in;
+import static com.mongodb.client.model.Filters.lt;
+import static com.mongodb.client.model.Filters.lte;
+import static com.mongodb.client.model.Filters.ne;
+import static com.mongodb.client.model.Filters.nin;
+import static com.mongodb.client.model.Filters.or;
+import static com.mongodb.client.model.Projections.fields;
+import static com.mongodb.client.model.Projections.metaVectorSearchScore;
+import static com.mongodb.client.model.search.SearchPath.fieldPath;
+import static com.mongodb.client.model.search.VectorSearchOptions.approximateVectorSearchOptions;
+import static com.mongodb.client.model.search.VectorSearchOptions.exactVectorSearchOptions;
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+class AggregatesVectorSearchIntegrationTest {
+ private static final String EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE =
+ "Exceeded maximum attempts waiting for Search Index creation in Atlas cluster. Index document: %s";
+
+ private static final String VECTOR_INDEX = "vector_search_index";
+ private static final String VECTOR_FIELD_INT_8 = "int8Vector";
+ private static final String VECTOR_FIELD_FLOAT_32 = "float32Vector";
+ private static final String VECTOR_FIELD_LEGACY_DOUBLE_LIST = "legacyDoubleVector";
+ private static final int LIMIT = 5;
+ private static final String FIELD_YEAR = "year";
+ private static CollectionHelper collectionHelper;
+ private static final BsonDocument VECTOR_SEARCH_INDEX_DEFINITION = BsonDocument.parse(
+ "{"
+ + " fields: ["
+ + " {"
+ + " path: '" + VECTOR_FIELD_INT_8 + "',"
+ + " numDimensions: 5,"
+ + " similarity: 'cosine',"
+ + " type: 'vector',"
+ + " },"
+ + " {"
+ + " path: '" + VECTOR_FIELD_FLOAT_32 + "',"
+ + " numDimensions: 5,"
+ + " similarity: 'cosine',"
+ + " type: 'vector',"
+ + " },"
+ + " {"
+ + " path: '" + VECTOR_FIELD_LEGACY_DOUBLE_LIST + "',"
+ + " numDimensions: 5,"
+ + " similarity: 'cosine',"
+ + " type: 'vector',"
+ + " },"
+ + " {"
+ + " path: '" + FIELD_YEAR + "',"
+ + " type: 'filter',"
+ + " },"
+ + " ]"
+ + "}");
+
+ @BeforeAll
+ static void beforeAll() {
+ assumeTrue(isAtlasSearchTest());
+ assumeTrue(serverVersionAtLeast(6, 0));
+
+ collectionHelper =
+ new CollectionHelper<>(new DocumentCodec(), new MongoNamespace("javaVectorSearchTest", AggregatesVectorSearchIntegrationTest.class.getSimpleName()));
+ collectionHelper.drop();
+ collectionHelper.insertDocuments(
+ new Document()
+ .append("_id", 0)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{0, 1, 2, 3, 4}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{0.0001, 1.12345, 2.23456, 3.34567, 4.45678})
+ .append(FIELD_YEAR, 2016),
+ new Document()
+ .append("_id", 1)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{1, 2, 3, 4, 5}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{1.0001f, 2.12345f, 3.23456f, 4.34567f, 5.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{1.0001, 2.12345, 3.23456, 4.34567, 5.45678})
+ .append(FIELD_YEAR, 2017),
+ new Document()
+ .append("_id", 2)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{2, 3, 4, 5, 6}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{2.0002f, 3.12345f, 4.23456f, 5.34567f, 6.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{2.0002, 3.12345, 4.23456, 5.34567, 6.45678})
+ .append(FIELD_YEAR, 2018),
+ new Document()
+ .append("_id", 3)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{3, 4, 5, 6, 7}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{3.0003f, 4.12345f, 5.23456f, 6.34567f, 7.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{3.0003, 4.12345, 5.23456, 6.34567, 7.45678})
+ .append(FIELD_YEAR, 2019),
+ new Document()
+ .append("_id", 4)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{4, 5, 6, 7, 8}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{4.0004f, 5.12345f, 6.23456f, 7.34567f, 8.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{4.0004, 5.12345, 6.23456, 7.34567, 8.45678})
+ .append(FIELD_YEAR, 2020),
+ new Document()
+ .append("_id", 5)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{5, 6, 7, 8, 9}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{5.0005f, 6.12345f, 7.23456f, 8.34567f, 9.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{5.0005, 6.12345, 7.23456, 8.34567, 9.45678})
+ .append(FIELD_YEAR, 2021),
+ new Document()
+ .append("_id", 6)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{6, 7, 8, 9, 10}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{6.0006f, 7.12345f, 8.23456f, 9.34567f, 10.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{6.0006, 7.12345, 8.23456, 9.34567, 10.45678})
+ .append(FIELD_YEAR, 2022),
+ new Document()
+ .append("_id", 7)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{7, 8, 9, 10, 11}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{7.0007f, 8.12345f, 9.23456f, 10.34567f, 11.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{7.0007, 8.12345, 9.23456, 10.34567, 11.45678})
+ .append(FIELD_YEAR, 2023),
+ new Document()
+ .append("_id", 8)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{8, 9, 10, 11, 12}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{8.0008f, 9.12345f, 10.23456f, 11.34567f, 12.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{8.0008, 9.12345, 10.23456, 11.34567, 12.45678})
+ .append(FIELD_YEAR, 2024),
+ new Document()
+ .append("_id", 9)
+ .append(VECTOR_FIELD_INT_8, Vector.int8Vector(new byte[]{9, 10, 11, 12, 13}))
+ .append(VECTOR_FIELD_FLOAT_32, Vector.floatVector(new float[]{9.0009f, 10.12345f, 11.23456f, 12.34567f, 13.45678f}))
+ .append(VECTOR_FIELD_LEGACY_DOUBLE_LIST, new double[]{9.0009, 10.12345, 11.23456, 12.34567, 13.45678})
+ .append(FIELD_YEAR, 2025)
+ );
+
+ collectionHelper.createSearchIndex(
+ new SearchIndexRequest(VECTOR_SEARCH_INDEX_DEFINITION, VECTOR_INDEX,
+ SearchIndexType.vectorSearch()));
+ awaitIndexCreation();
+ }
+
+ @AfterAll
+ static void afterAll() {
+ if (collectionHelper != null) {
+ collectionHelper.drop();
+ }
+ }
+
+ private static Stream provideSupportedVectors() {
+ return Stream.of(
+ arguments(Vector.int8Vector(new byte[]{0, 1, 2, 3, 4}),
+ // `multi` is used here only to verify that it is tolerated
+ fieldPath(VECTOR_FIELD_INT_8).multi("ignored"),
+ approximateVectorSearchOptions(LIMIT * 2)),
+ arguments(Vector.int8Vector(new byte[]{0, 1, 2, 3, 4}),
+ fieldPath(VECTOR_FIELD_INT_8),
+ approximateVectorSearchOptions(LIMIT * 2)),
+
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ // `multi` is used here only to verify that it is tolerated
+ fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"),
+ approximateVectorSearchOptions(LIMIT * 2)),
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ fieldPath(VECTOR_FIELD_FLOAT_32),
+ approximateVectorSearchOptions(LIMIT * 2)),
+
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ // `multi` is used here only to verify that it is tolerated
+ fieldPath(VECTOR_FIELD_FLOAT_32).multi("ignored"),
+ exactVectorSearchOptions()),
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ fieldPath(VECTOR_FIELD_FLOAT_32),
+ exactVectorSearchOptions()),
+
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ // `multi` is used here only to verify that it is tolerated
+ fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"),
+ exactVectorSearchOptions()),
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST),
+ exactVectorSearchOptions()),
+
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ // `multi` is used here only to verify that it is tolerated
+ fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST).multi("ignored"),
+ approximateVectorSearchOptions(LIMIT * 2)),
+ arguments(Vector.floatVector(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f}),
+ fieldPath(VECTOR_FIELD_LEGACY_DOUBLE_LIST),
+ approximateVectorSearchOptions(LIMIT * 2))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideSupportedVectors")
+ void shouldSearchByVectorWithSearchScore(final Vector vector,
+ final FieldSearchPath fieldSearchPath,
+ final VectorSearchOptions vectorSearchOptions) {
+ //given
+ List pipeline = asList(
+ Aggregates.vectorSearch(
+ fieldSearchPath,
+ vector,
+ VECTOR_INDEX, LIMIT,
+ vectorSearchOptions),
+ Aggregates.project(
+ fields(
+ metaVectorSearchScore("vectorSearchScore")
+ ))
+ );
+
+ //when
+ List aggregate = collectionHelper.aggregate(pipeline);
+
+ //then
+ Assertions.assertEquals(LIMIT, aggregate.size());
+ assertScoreIsDecreasing(aggregate);
+ Document highestScoreDocument = aggregate.get(0);
+ assertEquals(1, highestScoreDocument.getDouble("vectorSearchScore"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideSupportedVectors")
+ void shouldSearchByVector(final Vector vector,
+ final FieldSearchPath fieldSearchPath,
+ final VectorSearchOptions vectorSearchOptions) {
+ //given
+ List pipeline = asList(
+ Aggregates.vectorSearch(
+ fieldSearchPath,
+ vector,
+ VECTOR_INDEX, LIMIT,
+ vectorSearchOptions)
+ );
+
+ //when
+ List aggregate = collectionHelper.aggregate(pipeline);
+
+ //then
+ Assertions.assertEquals(LIMIT, aggregate.size());
+ assertFalse(
+ aggregate.stream()
+ .anyMatch(document -> document.containsKey("vectorSearchScore"))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideSupportedVectors")
+ void shouldSearchByVectorWithFilter(final Vector vector,
+ final FieldSearchPath fieldSearchPath,
+ final VectorSearchOptions vectorSearchOptions) {
+ Consumer asserter = filter -> {
+ List pipeline = singletonList(
+ Aggregates.vectorSearch(
+ fieldSearchPath, vector, VECTOR_INDEX, 1,
+ vectorSearchOptions.filter(filter))
+ );
+
+ List aggregate = collectionHelper.aggregate(pipeline);
+ Assertions.assertFalse(aggregate.isEmpty());
+ };
+
+ assertAll(
+ () -> asserter.accept(lt("year", 2020)),
+ () -> asserter.accept(lte("year", 2020)),
+ () -> asserter.accept(eq("year", 2020)),
+ () -> asserter.accept(gte("year", 2016)),
+ () -> asserter.accept(gt("year", 2015)),
+ () -> asserter.accept(ne("year", 2016)),
+ () -> asserter.accept(in("year", 2000, 2024)),
+ () -> asserter.accept(nin("year", 2000, 2024)),
+ () -> asserter.accept(and(gte("year", 2015), lte("year", 2017))),
+ () -> asserter.accept(or(eq("year", 2015), eq("year", 2017)))
+ );
+ }
+
+ private static void assertScoreIsDecreasing(final List aggregate) {
+ double previousScore = Integer.MAX_VALUE;
+ for (Document document : aggregate) {
+ Double vectorSearchScore = document.getDouble("vectorSearchScore");
+ assertTrue(vectorSearchScore > 0, "Expected positive score");
+ assertTrue(vectorSearchScore < previousScore, "Expected decreasing score");
+ previousScore = vectorSearchScore;
+ }
+ }
+
+ private static void awaitIndexCreation() {
+ int attempts = 10;
+ Optional searchIndex = Optional.empty();
+
+ while (attempts-- > 0) {
+ searchIndex = collectionHelper.listSearchIndex(VECTOR_INDEX);
+ if (searchIndex.filter(document -> document.getBoolean("queryable"))
+ .isPresent()) {
+ return;
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(5);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new MongoInterruptedException(null, e);
+ }
+ }
+
+ searchIndex.ifPresent(document ->
+ Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, document.toJson())));
+ Assertions.fail(format(EXCEED_WAIT_ATTEMPTS_ERROR_MESSAGE, "null"));
+ }
+}
diff --git a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java
index e297726d325..adce165ee51 100644
--- a/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java
+++ b/driver-core/src/test/functional/com/mongodb/client/test/CollectionHelper.java
@@ -43,11 +43,14 @@
import com.mongodb.internal.operation.CountDocumentsOperation;
import com.mongodb.internal.operation.CreateCollectionOperation;
import com.mongodb.internal.operation.CreateIndexesOperation;
+import com.mongodb.internal.operation.CreateSearchIndexesOperation;
import com.mongodb.internal.operation.DropCollectionOperation;
import com.mongodb.internal.operation.DropDatabaseOperation;
import com.mongodb.internal.operation.FindOperation;
import com.mongodb.internal.operation.ListIndexesOperation;
+import com.mongodb.internal.operation.ListSearchIndexesOperation;
import com.mongodb.internal.operation.MixedBulkWriteOperation;
+import com.mongodb.internal.operation.SearchIndexRequest;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWrapper;
@@ -56,6 +59,7 @@
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.Document;
+import org.bson.assertions.Assertions;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.Codec;
import org.bson.codecs.Decoder;
@@ -65,6 +69,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import static com.mongodb.ClusterFixture.executeAsync;
@@ -297,6 +302,25 @@ public List find() {
return find(codec);
}
+ public Optional listSearchIndex(final String indexName) {
+ ListSearchIndexesOperation listSearchIndexesOperation =
+ new ListSearchIndexesOperation<>(namespace, codec, indexName, null, null, null, null, true);
+ BatchCursor cursor = listSearchIndexesOperation.execute(getBinding());
+
+ List results = new ArrayList<>();
+ while (cursor.hasNext()) {
+ results.addAll(cursor.next());
+ }
+ Assertions.assertTrue("Expected at most one result, but found " + results.size(), results.size() <= 1);
+ return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
+ }
+
+ public void createSearchIndex(final SearchIndexRequest searchIndexModel) {
+ CreateSearchIndexesOperation searchIndexesOperation =
+ new CreateSearchIndexesOperation(namespace, singletonList(searchIndexModel));
+ searchIndexesOperation.execute(getBinding());
+ }
+
public List find(final Codec codec) {
BatchCursor cursor = new FindOperation<>(namespace, codec)
.sort(new BsonDocument("_id", new BsonInt32(1)))
diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/explain.json b/driver-core/src/test/resources/client-side-encryption/legacy/explain.json
index 0e451e4818a..8ca3b48d37d 100644
--- a/driver-core/src/test/resources/client-side-encryption/legacy/explain.json
+++ b/driver-core/src/test/resources/client-side-encryption/legacy/explain.json
@@ -1,7 +1,7 @@
{
"runOn": [
{
- "minServerVersion": "4.1.10"
+ "minServerVersion": "7.0.0"
}
],
"database_name": "default",
diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Compact.json b/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Compact.json
index 85fb8bf607a..868095e1e64 100644
--- a/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Compact.json
+++ b/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Compact.json
@@ -230,4 +230,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json b/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json
index 59241927ca1..bba9f25535a 100644
--- a/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json
+++ b/driver-core/src/test/resources/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json
@@ -6,7 +6,8 @@
"replicaset",
"sharded",
"load-balanced"
- ]
+ ],
+ "serverless": "forbid"
}
],
"database_name": "default",
diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/getMore.json b/driver-core/src/test/resources/client-side-encryption/legacy/getMore.json
index ee99bf7537e..94e788ef61d 100644
--- a/driver-core/src/test/resources/client-side-encryption/legacy/getMore.json
+++ b/driver-core/src/test/resources/client-side-encryption/legacy/getMore.json
@@ -216,7 +216,10 @@
"command_started_event": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "default",
"batchSize": 2
diff --git a/driver-core/src/test/resources/client-side-encryption/legacy/namedKMS.json b/driver-core/src/test/resources/client-side-encryption/legacy/namedKMS.json
index 394a6ac5484..c859443585b 100644
--- a/driver-core/src/test/resources/client-side-encryption/legacy/namedKMS.json
+++ b/driver-core/src/test/resources/client-side-encryption/legacy/namedKMS.json
@@ -194,4 +194,4 @@
}
}
]
-}
\ No newline at end of file
+}
diff --git a/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.md b/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.md
new file mode 100644
index 00000000000..99a1e8e3f06
--- /dev/null
+++ b/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.md
@@ -0,0 +1,182 @@
+# Initial DNS Seedlist Discovery tests
+
+This directory contains platform-independent tests that drivers can use to prove their conformance to the Initial DNS
+Seedlist Discovery spec.
+
+## Prose Tests
+
+For the following prose tests, it is assumed drivers are be able to stub DNS results to easily test invalid DNS
+resolution results.
+
+### 1. Allow SRVs with fewer than 3 `.` separated parts
+
+When running validation on an SRV string before DNS resolution, do not throw a error due to number of SRV parts.
+
+- `mongodb+srv://localhost`
+- `mongodb+srv://mongo.local`
+
+### 2. Throw when return address does not end with SRV domain
+
+When given a returned address that does NOT end with the original SRV's domain name, throw a runtime error.
+
+For this test, run each of the following cases:
+
+- the SRV `mongodb+srv://localhost` resolving to `localhost.mongodb`
+- the SRV `mongodb+srv://mongo.local` resolving to `test_1.evil.local`
+- the SRV `mongodb+srv://blogs.mongodb.com` resolving to `blogs.evil.com`
+
+Remember, the domain of an SRV with one or two `.` separated parts is the SRVs entire hostname.
+
+### 3. Throw when return address is identical to SRV hostname
+
+When given a returned address that is identical to the SRV hostname and the SRV hostname has fewer than three `.`
+separated parts, throw a runtime error.
+
+For this test, run each of the following cases:
+
+- the SRV `mongodb+srv://localhost` resolving to `localhost`
+- the SRV `mongodb+srv://mongo.local` resolving to `mongo.local`
+
+### 4. Throw when return address does not contain `.` separating shared part of domain
+
+When given a returned address that does NOT share the domain name of the SRV record because it's missing a `.`, throw a
+runtime error.
+
+For this test, run each of the following cases:
+
+- the SRV `mongodb+srv://localhost` resolving to `test_1.cluster_1localhost`
+- the SRV `mongodb+srv://mongo.local` resolving to `test_1.my_hostmongo.local`
+- the SRV `mongodb+srv://blogs.mongodb.com` resolving to `cluster.testmongodb.com`
+
+## Test Setup
+
+The tests in the `replica-set` directory MUST be executed against a three-node replica set on localhost ports 27017,
+27018, and 27019 with replica set name `repl0`.
+
+The tests in the `load-balanced` directory MUST be executed against a load-balanced sharded cluster with the mongos
+servers running on localhost ports 27017 and 27018 and `--loadBalancerPort` 27050 and 27051, respectively (corresponding
+to the script in
+[drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-load-balancer.sh)).
+The load balancers, shard servers, and config servers may run on any open ports.
+
+The tests in the `sharded` directory MUST be executed against a sharded cluster with the mongos servers running on
+localhost ports 27017 and 27018. Shard servers and config servers may run on any open ports.
+
+In all cases, the clusters MUST be started with SSL enabled.
+
+To run the tests that accompany this spec, you need to configure the SRV and TXT records with a real name server. The
+following records are required for these tests:
+
+```
+Record TTL Class Address
+localhost.test.build.10gen.cc. 86400 IN A 127.0.0.1
+localhost.sub.test.build.10gen.cc. 86400 IN A 127.0.0.1
+
+Record TTL Class Port Target
+_mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc.
+_mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc.
+_mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27019 localhost.test.build.10gen.cc.
+_mongodb._tcp.test3.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test5.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test6.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test7.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test8.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test10.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test11.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test12.test.build.10gen.cc. 86400 IN SRV 27017 localhost.build.10gen.cc.
+_mongodb._tcp.test13.test.build.10gen.cc. 86400 IN SRV 27017 test.build.10gen.cc.
+_mongodb._tcp.test14.test.build.10gen.cc. 86400 IN SRV 27017 localhost.not-test.build.10gen.cc.
+_mongodb._tcp.test15.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.not-build.10gen.cc.
+_mongodb._tcp.test16.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.not-10gen.cc.
+_mongodb._tcp.test17.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.not-cc.
+_mongodb._tcp.test18.test.build.10gen.cc. 86400 IN SRV 27017 localhost.sub.test.build.10gen.cc.
+_mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.evil.build.10gen.cc.
+_mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test20.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test21.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_customname._tcp.test22.test.build.10gen.cc 86400 IN SRV 27017 localhost.test.build.10gen.cc.
+_mongodb._tcp.test23.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc.
+_mongodb._tcp.test24.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc.
+
+Record TTL Class Text
+test5.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0&authSource=thisDB"
+test6.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0"
+test6.test.build.10gen.cc. 86400 IN TXT "authSource=otherDB"
+test7.test.build.10gen.cc. 86400 IN TXT "ssl=false"
+test8.test.build.10gen.cc. 86400 IN TXT "authSource"
+test10.test.build.10gen.cc. 86400 IN TXT "socketTimeoutMS=500"
+test11.test.build.10gen.cc. 86400 IN TXT "replicaS" "et=rep" "l0"
+test20.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true"
+test21.test.build.10gen.cc. 86400 IN TXT "loadBalanced=false"
+test24.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true"
+```
+
+Notes:
+
+- `test4` is omitted deliberately to test what happens with no SRV record.
+- `test9` is missing because it was deleted during the development of the tests.
+- The missing `test.` sub-domain in the SRV record target for `test12` is deliberate.
+- `test22` is used to test a custom service name (`customname`).
+- `test23` and `test24` point to port 8000 (HAProxy) and are used for load-balanced tests.
+
+In our tests we have used `localhost.test.build.10gen.cc` as the domain, and then configured
+`localhost.test.build.10gen.cc` to resolve to 127.0.0.1.
+
+You need to adapt the records shown above to replace `test.build.10gen.cc` with your own domain name, and update the
+"uri" field in the YAML or JSON files in this directory with the actual domain.
+
+## Test Format and Use
+
+These YAML and JSON files contain the following fields:
+
+- `uri`: a `mongodb+srv` connection string
+- `seeds`: the expected set of initial seeds discovered from the SRV record
+- `numSeeds`: the expected number of initial seeds discovered from the SRV record. This is mainly used to test
+ `srvMaxHosts`, since randomly selected hosts cannot be deterministically asserted.
+- `hosts`: the discovered topology's list of hosts once SDAM completes a scan
+- `numHosts`: the expected number of hosts discovered once SDAM completes a scan. This is mainly used to test
+ `srvMaxHosts`, since randomly selected hosts cannot be deterministically asserted.
+- `options`: the parsed [URI options](../../uri-options/uri-options.md) as discovered from the
+ [Connection String](../../connection-string/connection-string-spec.md)'s "Connection Options" component and SRV
+ resolution (e.g. TXT records, implicit `tls` default).
+- `parsed_options`: additional, parsed options from other
+ [Connection String](../../connection-string/connection-string-spec.md) components. This is mainly used for asserting
+ `UserInfo` (as `user` and `password`) and `Auth database` (as `auth_database`).
+- `error`: indicates that the parsing of the URI, or the resolving or contents of the SRV or TXT records included
+ errors.
+- `comment`: a comment to indicate why a test would fail.
+- `ping`: if false, the test runner should not run a "ping" operation.
+
+For each YAML file:
+
+- Create a MongoClient initialized with the `mongodb+srv` connection string.
+- Run a "ping" operation unless `ping` is false or `error` is true.
+
+Assertions:
+
+- If `seeds` is specified, drivers SHOULD verify that the set of hosts in the client's initial seedlist matches the list
+ in `seeds`. If `numSeeds` is specified, drivers SHOULD verify that the size of that set matches `numSeeds`.
+
+- If `hosts` is specified, drivers MUST verify that the set of ServerDescriptions in the client's TopologyDescription
+ eventually matches the list in `hosts`. If `numHosts` is specified, drivers MUST verify that the size of that set
+ matches `numHosts`.
+
+- If `options` is specified, drivers MUST verify each of the values under `options` match the MongoClient's parsed value
+ for that option. There may be other options parsed by the MongoClient as well, which a test does not verify.
+
+- If `parsed_options` is specified, drivers MUST verify that each of the values under `parsed_options` match the
+ MongoClient's parsed value for that option. Supported values include, but are not limited to, `user` and `password`
+ (parsed from `UserInfo`) and `auth_database` (parsed from `Auth database`).
+
+- If `error` is specified and `true`, drivers MUST verify that initializing the MongoClient throws an error. If `error`
+ is not specified or is `false`, both initializing the MongoClient and running a ping operation must succeed without
+ throwing any errors.
+
+- If `ping` is not specified or `true`, drivers MUST verify that running a "ping" operation using the initialized
+ MongoClient succeeds. If `ping` is `false`, drivers MUST NOT run a "ping" operation.
+
+ > **Note:** These tests are expected to be run against MongoDB databases with and without authentication enabled. The
+ > "ping" operation does not require authentication so should succeed with URIs that contain no userinfo (i.e. no
+ > username and password). Tests with URIs that contain userinfo always set `ping` to `false` because some drivers will
+ > fail handshake on a connection if userinfo is provided but incorrect.
diff --git a/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.rst b/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.rst
deleted file mode 100644
index e8b33936845..00000000000
--- a/driver-core/src/test/resources/initial-dns-seedlist-discovery/README.rst
+++ /dev/null
@@ -1,164 +0,0 @@
-====================================
-Initial DNS Seedlist Discovery tests
-====================================
-
-This directory contains platform-independent tests that drivers can use
-to prove their conformance to the Initial DNS Seedlist Discovery spec.
-
-Test Setup
-----------
-
-The tests in the ``replica-set`` directory MUST be executed against a
-three-node replica set on localhost ports 27017, 27018, and 27019 with
-replica set name ``repl0``.
-
-The tests in the ``load-balanced`` directory MUST be executed against a
-load-balanced sharded cluster with the mongos servers running on localhost ports
-27017 and 27018 and ``--loadBalancerPort`` 27050 and 27051, respectively
-(corresponding to the script in `drivers-evergreen-tools`_). The load balancers,
-shard servers, and config servers may run on any open ports.
-
-.. _`drivers-evergreen-tools`: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-load-balancer.sh
-
-The tests in the ``sharded`` directory MUST be executed against a sharded
-cluster with the mongos servers running on localhost ports 27017 and 27018.
-Shard servers and config servers may run on any open ports.
-
-In all cases, the clusters MUST be started with SSL enabled.
-
-To run the tests that accompany this spec, you need to configure the SRV and
-TXT records with a real name server. The following records are required for
-these tests::
-
- Record TTL Class Address
- localhost.test.build.10gen.cc. 86400 IN A 127.0.0.1
- localhost.sub.test.build.10gen.cc. 86400 IN A 127.0.0.1
-
- Record TTL Class Port Target
- _mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc.
- _mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc.
- _mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27019 localhost.test.build.10gen.cc.
- _mongodb._tcp.test3.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test5.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test6.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test7.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test8.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test10.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test11.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test12.test.build.10gen.cc. 86400 IN SRV 27017 localhost.build.10gen.cc.
- _mongodb._tcp.test13.test.build.10gen.cc. 86400 IN SRV 27017 test.build.10gen.cc.
- _mongodb._tcp.test14.test.build.10gen.cc. 86400 IN SRV 27017 localhost.not-test.build.10gen.cc.
- _mongodb._tcp.test15.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.not-build.10gen.cc.
- _mongodb._tcp.test16.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.not-10gen.cc.
- _mongodb._tcp.test17.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.not-cc.
- _mongodb._tcp.test18.test.build.10gen.cc. 86400 IN SRV 27017 localhost.sub.test.build.10gen.cc.
- _mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.evil.build.10gen.cc.
- _mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test20.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test21.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _customname._tcp.test22.test.build.10gen.cc 86400 IN SRV 27017 localhost.test.build.10gen.cc.
- _mongodb._tcp.test23.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc.
- _mongodb._tcp.test24.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc.
-
- Record TTL Class Text
- test5.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0&authSource=thisDB"
- test6.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0"
- test6.test.build.10gen.cc. 86400 IN TXT "authSource=otherDB"
- test7.test.build.10gen.cc. 86400 IN TXT "ssl=false"
- test8.test.build.10gen.cc. 86400 IN TXT "authSource"
- test10.test.build.10gen.cc. 86400 IN TXT "socketTimeoutMS=500"
- test11.test.build.10gen.cc. 86400 IN TXT "replicaS" "et=rep" "l0"
- test20.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true"
- test21.test.build.10gen.cc. 86400 IN TXT "loadBalanced=false"
- test24.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true"
-
-Notes:
-
-- ``test4`` is omitted deliberately to test what happens with no SRV record.
-- ``test9`` is missing because it was deleted during the development of the
- tests.
-- The missing ``test.`` sub-domain in the SRV record target for ``test12`` is
- deliberate.
-- ``test22`` is used to test a custom service name (``customname``).
-- ``test23`` and ``test24`` point to port 8000 (HAProxy) and are used for
- load-balanced tests.
-
-In our tests we have used ``localhost.test.build.10gen.cc`` as the domain, and
-then configured ``localhost.test.build.10gen.cc`` to resolve to 127.0.0.1.
-
-You need to adapt the records shown above to replace ``test.build.10gen.cc``
-with your own domain name, and update the "uri" field in the YAML or JSON files
-in this directory with the actual domain.
-
-Test Format and Use
--------------------
-
-These YAML and JSON files contain the following fields:
-
-- ``uri``: a ``mongodb+srv`` connection string
-- ``seeds``: the expected set of initial seeds discovered from the SRV record
-- ``numSeeds``: the expected number of initial seeds discovered from the SRV
- record. This is mainly used to test ``srvMaxHosts``, since randomly selected
- hosts cannot be deterministically asserted.
-- ``hosts``: the discovered topology's list of hosts once SDAM completes a scan
-- ``numHosts``: the expected number of hosts discovered once SDAM completes a
- scan. This is mainly used to test ``srvMaxHosts``, since randomly selected
- hosts cannot be deterministically asserted.
-- ``options``: the parsed `URI options`_ as discovered from the
- `Connection String`_'s "Connection Options" component and SRV resolution
- (e.g. TXT records, implicit ``tls`` default).
-- ``parsed_options``: additional, parsed options from other `Connection String`_
- components. This is mainly used for asserting ``UserInfo`` (as ``user`` and
- ``password``) and ``Auth database`` (as ``auth_database``).
-- ``error``: indicates that the parsing of the URI, or the resolving or
- contents of the SRV or TXT records included errors.
-- ``comment``: a comment to indicate why a test would fail.
-- ``ping``: if false, the test runner should not run a "ping" operation.
-
-.. _`Connection String`: ../../connection-string/connection-string-spec.rst
-.. _`URI options`: ../../uri-options/uri-options.rst
-
-For each YAML file:
-
-- Create a MongoClient initialized with the ``mongodb+srv``
- connection string.
-- Run a "ping" operation unless ``ping`` is false or ``error`` is true.
-
-Assertions:
-
-- If ``seeds`` is specified, drivers SHOULD verify that the set of hosts in the
- client's initial seedlist matches the list in ``seeds``. If ``numSeeds`` is
- specified, drivers SHOULD verify that the size of that set matches
- ``numSeeds``.
-
-- If ``hosts`` is specified, drivers MUST verify that the set of
- ServerDescriptions in the client's TopologyDescription eventually matches the
- list in ``hosts``. If ``numHosts`` is specified, drivers MUST verify that the
- size of that set matches ``numHosts``.
-
-- If ``options`` is specified, drivers MUST verify each of the values under
- ``options`` match the MongoClient's parsed value for that option. There may be
- other options parsed by the MongoClient as well, which a test does not verify.
-
-- If ``parsed_options`` is specified, drivers MUST verify that each of the
- values under ``parsed_options`` match the MongoClient's parsed value for that
- option. Supported values include, but are not limited to, ``user`` and
- ``password`` (parsed from ``UserInfo``) and ``auth_database`` (parsed from
- ``Auth database``).
-
-- If ``error`` is specified and ``true``, drivers MUST verify that initializing
- the MongoClient throws an error. If ``error`` is not specified or is
- ``false``, both initializing the MongoClient and running a ping operation must
- succeed without throwing any errors.
-
-- If ``ping`` is not specified or ``true``, drivers MUST verify that running a
- "ping" operation using the initialized MongoClient succeeds. If ``ping`` is
- ``false``, drivers MUST NOT run a "ping" operation.
-
- **Note:** These tests are expected to be run against MongoDB databases with
- and without authentication enabled. The "ping" operation does not require
- authentication so should succeed with URIs that contain no userinfo (i.e.
- no username and password). Tests with URIs that contain userinfo always set
- ``ping`` to ``false`` because some drivers will fail handshake on a
- connection if userinfo is provided but incorrect.
diff --git a/driver-core/src/test/resources/initial-dns-seedlist-discovery/replica-set/not-enough-parts.json b/driver-core/src/test/resources/initial-dns-seedlist-discovery/replica-set/not-enough-parts.json
deleted file mode 100644
index 7cfce2ec57e..00000000000
--- a/driver-core/src/test/resources/initial-dns-seedlist-discovery/replica-set/not-enough-parts.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "uri": "mongodb+srv://10gen.cc/",
- "seeds": [],
- "hosts": [],
- "error": true,
- "comment": "Should fail because host in URI does not have {hostname}, {domainname} and {tld}."
-}
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-clusterTime.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-clusterTime.json
index 55b4ae3fbcb..2b09e548f1d 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-clusterTime.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-clusterTime.json
@@ -28,7 +28,6 @@
"minServerVersion": "4.0.0",
"topologies": [
"replicaset",
- "sharded-replicaset",
"load-balanced",
"sharded"
],
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-disambiguatedPaths.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-disambiguatedPaths.json
index 91d8e66da20..e6cc5ef66ed 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-disambiguatedPaths.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-disambiguatedPaths.json
@@ -28,7 +28,6 @@
"minServerVersion": "6.1.0",
"topologies": [
"replicaset",
- "sharded-replicaset",
"load-balanced",
"sharded"
],
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-errors.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-errors.json
index 04fe8f04f33..65e99e541ed 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-errors.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-errors.json
@@ -145,7 +145,7 @@
"minServerVersion": "4.1.11",
"topologies": [
"replicaset",
- "sharded-replicaset",
+ "sharded",
"load-balanced"
]
}
@@ -190,7 +190,7 @@
"minServerVersion": "4.2",
"topologies": [
"replicaset",
- "sharded-replicaset",
+ "sharded",
"load-balanced"
]
}
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-pre_and_post_images.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-pre_and_post_images.json
index 8beefb2bc82..e62fc034596 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-pre_and_post_images.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-pre_and_post_images.json
@@ -6,7 +6,7 @@
"minServerVersion": "6.0.0",
"topologies": [
"replicaset",
- "sharded-replicaset",
+ "sharded",
"load-balanced"
],
"serverless": "forbid"
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-allowlist.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-allowlist.json
index b4953ec736a..1ec72b432be 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-allowlist.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-allowlist.json
@@ -6,7 +6,7 @@
"minServerVersion": "3.6",
"topologies": [
"replicaset",
- "sharded-replicaset",
+ "sharded",
"load-balanced"
],
"serverless": "forbid"
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-errorLabels.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-errorLabels.json
index f5f4505a9f9..7fd70108f07 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-errorLabels.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-resume-errorLabels.json
@@ -6,7 +6,7 @@
"minServerVersion": "4.3.1",
"topologies": [
"replicaset",
- "sharded-replicaset",
+ "sharded",
"load-balanced"
],
"serverless": "forbid"
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-showExpandedEvents.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-showExpandedEvents.json
index a59a818493c..b9594e0c1e1 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-showExpandedEvents.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams-showExpandedEvents.json
@@ -6,7 +6,6 @@
"minServerVersion": "6.0.0",
"topologies": [
"replicaset",
- "sharded-replicaset",
"sharded"
],
"serverless": "forbid"
@@ -463,7 +462,6 @@
"runOnRequirements": [
{
"topologies": [
- "sharded-replicaset",
"sharded"
]
}
diff --git a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams.json b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams.json
index 476154f4e15..c8b60ed4e25 100644
--- a/driver-core/src/test/resources/unified-test-format/change-streams/change-streams.json
+++ b/driver-core/src/test/resources/unified-test-format/change-streams/change-streams.json
@@ -210,7 +210,6 @@
"expectEvents": [
{
"client": "client0",
- "ignoreExtraEvents": true,
"events": [
{
"commandStartedEvent": {
@@ -256,7 +255,6 @@
"expectEvents": [
{
"client": "client0",
- "ignoreExtraEvents": true,
"events": [
{
"commandStartedEvent": {
@@ -293,7 +291,6 @@
"expectEvents": [
{
"client": "client0",
- "ignoreExtraEvents": true,
"events": [
{
"commandStartedEvent": {
@@ -349,7 +346,6 @@
"expectEvents": [
{
"client": "client0",
- "ignoreExtraEvents": true,
"events": [
{
"commandStartedEvent": {
@@ -436,7 +432,6 @@
"expectEvents": [
{
"client": "client0",
- "ignoreExtraEvents": true,
"events": [
{
"commandStartedEvent": {
diff --git a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/command-execution.json b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/command-execution.json
index b9b306c7fb6..aa9c3eb23f3 100644
--- a/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/command-execution.json
+++ b/driver-core/src/test/resources/unified-test-format/client-side-operation-timeout/command-execution.json
@@ -3,7 +3,7 @@
"schemaVersion": "1.9",
"runOnRequirements": [
{
- "minServerVersion": "4.9",
+ "minServerVersion": "4.4.7",
"topologies": [
"single",
"replicaset",
diff --git a/driver-core/src/test/resources/unified-test-format/collection-management/modifyCollection-pre_and_post_images.json b/driver-core/src/test/resources/unified-test-format/collection-management/modifyCollection-pre_and_post_images.json
index 62c00b5a986..8026faeb17a 100644
--- a/driver-core/src/test/resources/unified-test-format/collection-management/modifyCollection-pre_and_post_images.json
+++ b/driver-core/src/test/resources/unified-test-format/collection-management/modifyCollection-pre_and_post_images.json
@@ -1,6 +1,6 @@
{
"description": "modifyCollection-pre_and_post_images",
- "schemaVersion": "1.0",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "6.0",
diff --git a/driver-core/src/test/resources/unified-test-format/command-logging/command.json b/driver-core/src/test/resources/unified-test-format/command-logging/command.json
index 3d5c2570be2..d2970df692f 100644
--- a/driver-core/src/test/resources/unified-test-format/command-logging/command.json
+++ b/driver-core/src/test/resources/unified-test-format/command-logging/command.json
@@ -93,6 +93,7 @@
"component": "command",
"data": {
"message": "Command succeeded",
+ "databaseName": "logging-tests",
"commandName": "ping",
"reply": {
"$$type": "string"
@@ -177,6 +178,7 @@
"component": "command",
"data": {
"message": "Command failed",
+ "databaseName": "logging-tests",
"commandName": "find",
"failure": {
"$$exists": true
diff --git a/driver-core/src/test/resources/unified-test-format/command-monitoring/command.json b/driver-core/src/test/resources/unified-test-format/command-monitoring/command.json
index d2970df692f..c28af95fed2 100644
--- a/driver-core/src/test/resources/unified-test-format/command-monitoring/command.json
+++ b/driver-core/src/test/resources/unified-test-format/command-monitoring/command.json
@@ -1,34 +1,36 @@
{
- "description": "command-logging",
- "schemaVersion": "1.13",
+ "description": "command",
+ "schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client",
- "observeLogMessages": {
- "command": "debug"
- }
+ "observeEvents": [
+ "commandStartedEvent",
+ "commandSucceededEvent",
+ "commandFailedEvent"
+ ]
}
},
{
"database": {
"id": "database",
"client": "client",
- "databaseName": "logging-tests"
+ "databaseName": "command-monitoring-tests"
}
},
{
"collection": {
"id": "collection",
"database": "database",
- "collectionName": "logging-tests-collection"
+ "collectionName": "test"
}
}
],
"initialData": [
{
- "collectionName": "logging-tests-collection",
- "databaseName": "logging-tests",
+ "collectionName": "test",
+ "databaseName": "command-monitoring-tests",
"documents": [
{
"_id": 1,
@@ -52,159 +54,25 @@
}
}
],
- "expectLogMessages": [
+ "expectEvents": [
{
"client": "client",
- "messages": [
+ "events": [
{
- "level": "debug",
- "component": "command",
- "data": {
- "message": "Command started",
- "databaseName": "logging-tests",
- "commandName": "ping",
+ "commandStartedEvent": {
"command": {
- "$$matchAsDocument": {
- "$$matchAsRoot": {
- "ping": 1,
- "$db": "logging-tests"
- }
- }
- },
- "requestId": {
- "$$type": [
- "int",
- "long"
- ]
+ "ping": 1
},
- "serverHost": {
- "$$type": "string"
- },
- "serverPort": {
- "$$type": [
- "int",
- "long"
- ]
- }
- }
- },
- {
- "level": "debug",
- "component": "command",
- "data": {
- "message": "Command succeeded",
- "databaseName": "logging-tests",
"commandName": "ping",
- "reply": {
- "$$type": "string"
- },
- "requestId": {
- "$$type": [
- "int",
- "long"
- ]
- },
- "serverHost": {
- "$$type": "string"
- },
- "serverPort": {
- "$$type": [
- "int",
- "long"
- ]
- },
- "durationMS": {
- "$$type": [
- "double",
- "int",
- "long"
- ]
- }
- }
- }
- ]
- }
- ]
- },
- {
- "description": "A failed command",
- "operations": [
- {
- "name": "find",
- "object": "collection",
- "arguments": {
- "filter": {
- "$or": true
- }
- },
- "expectError": {
- "isClientError": false
- }
- }
- ],
- "expectLogMessages": [
- {
- "client": "client",
- "messages": [
- {
- "level": "debug",
- "component": "command",
- "data": {
- "message": "Command started",
- "databaseName": "logging-tests",
- "commandName": "find",
- "command": {
- "$$type": "string"
- },
- "requestId": {
- "$$type": [
- "int",
- "long"
- ]
- },
- "serverHost": {
- "$$type": "string"
- },
- "serverPort": {
- "$$type": [
- "int",
- "long"
- ]
- }
+ "databaseName": "command-monitoring-tests"
}
},
{
- "level": "debug",
- "component": "command",
- "data": {
- "message": "Command failed",
- "databaseName": "logging-tests",
- "commandName": "find",
- "failure": {
- "$$exists": true
- },
- "requestId": {
- "$$type": [
- "int",
- "long"
- ]
- },
- "serverHost": {
- "$$type": "string"
- },
- "serverPort": {
- "$$type": [
- "int",
- "long"
- ]
+ "commandSucceededEvent": {
+ "reply": {
+ "ok": 1
},
- "durationMS": {
- "$$type": [
- "double",
- "int",
- "long"
- ]
- }
+ "commandName": "ping"
}
}
]
diff --git a/driver-core/src/test/resources/unified-test-format/command-monitoring/unacknowledgedBulkWrite.json b/driver-core/src/test/resources/unified-test-format/command-monitoring/unacknowledgedBulkWrite.json
index ed6ceafa5fd..78ddde767ff 100644
--- a/driver-core/src/test/resources/unified-test-format/command-monitoring/unacknowledgedBulkWrite.json
+++ b/driver-core/src/test/resources/unified-test-format/command-monitoring/unacknowledgedBulkWrite.json
@@ -71,17 +71,7 @@
"object": "collection",
"arguments": {
"filter": {}
- },
- "expectResult": [
- {
- "_id": 1,
- "x": 11
- },
- {
- "_id": "unorderedBulkWriteInsertW0",
- "x": 44
- }
- ]
+ }
}
],
"expectEvents": [
diff --git a/driver-core/src/test/resources/unified-test-format/command-monitoring/writeConcernError.json b/driver-core/src/test/resources/unified-test-format/command-monitoring/writeConcernError.json
index cc97450687d..455e5422b72 100644
--- a/driver-core/src/test/resources/unified-test-format/command-monitoring/writeConcernError.json
+++ b/driver-core/src/test/resources/unified-test-format/command-monitoring/writeConcernError.json
@@ -1,9 +1,9 @@
{
"description": "writeConcernError",
- "schemaVersion": "1.13",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
- "minServerVersion": "4.1.0",
+ "minServerVersion": "4.3.1",
"topologies": [
"replicaset"
],
@@ -66,11 +66,11 @@
"failCommands": [
"insert"
],
+ "errorLabels": [
+ "RetryableWriteError"
+ ],
"writeConcernError": {
- "code": 91,
- "errorLabels": [
- "RetryableWriteError"
- ]
+ "code": 91
}
}
}
@@ -112,11 +112,11 @@
"reply": {
"ok": 1,
"n": 1,
+ "errorLabels": [
+ "RetryableWriteError"
+ ],
"writeConcernError": {
- "code": 91,
- "errorLabels": [
- "RetryableWriteError"
- ]
+ "code": 91
}
},
"commandName": "insert"
diff --git a/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json b/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json
index bc887e83cbc..c1fa3b4574a 100644
--- a/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json
+++ b/driver-core/src/test/resources/unified-test-format/crud/aggregate-write-readPreference.json
@@ -78,11 +78,6 @@
"x": 33
}
]
- },
- {
- "collectionName": "coll1",
- "databaseName": "db0",
- "documents": []
}
],
"tests": [
@@ -159,22 +154,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll1",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 2,
- "x": 22
- },
- {
- "_id": 3,
- "x": 33
- }
- ]
- }
]
},
{
@@ -250,22 +229,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll1",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 2,
- "x": 22
- },
- {
- "_id": 3,
- "x": 33
- }
- ]
- }
]
},
{
@@ -344,22 +307,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll1",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 2,
- "x": 22
- },
- {
- "_id": 3,
- "x": 33
- }
- ]
- }
]
},
{
@@ -438,22 +385,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll1",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 2,
- "x": 22
- },
- {
- "_id": 3,
- "x": 33
- }
- ]
- }
]
}
]
diff --git a/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-replaceOne-sort.json b/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-replaceOne-sort.json
new file mode 100644
index 00000000000..c0bd3835142
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-replaceOne-sort.json
@@ -0,0 +1,239 @@
+{
+ "description": "BulkWrite replaceOne-sort",
+ "schemaVersion": "1.0",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent",
+ "commandSucceededEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "crud-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "BulkWrite replaceOne with sort option",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0"
+ }
+ ],
+ "operations": [
+ {
+ "object": "collection0",
+ "name": "bulkWrite",
+ "arguments": {
+ "requests": [
+ {
+ "replaceOne": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "replacement": {
+ "x": 1
+ }
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "x": 1
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "reply": {
+ "ok": 1,
+ "n": 1
+ },
+ "commandName": "update"
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "BulkWrite replaceOne with sort option unsupported (server-side error)",
+ "runOnRequirements": [
+ {
+ "maxServerVersion": "7.99"
+ }
+ ],
+ "operations": [
+ {
+ "object": "collection0",
+ "name": "bulkWrite",
+ "arguments": {
+ "requests": [
+ {
+ "replaceOne": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "replacement": {
+ "x": 1
+ }
+ }
+ }
+ ]
+ },
+ "expectError": {
+ "isClientError": false
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "x": 1
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-updateOne-sort.json b/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-updateOne-sort.json
new file mode 100644
index 00000000000..f78bd3bf3e3
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/crud/bulkWrite-updateOne-sort.json
@@ -0,0 +1,255 @@
+{
+ "description": "BulkWrite updateOne-sort",
+ "schemaVersion": "1.0",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent",
+ "commandSucceededEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "crud-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "BulkWrite updateOne with sort option",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0"
+ }
+ ],
+ "operations": [
+ {
+ "object": "collection0",
+ "name": "bulkWrite",
+ "arguments": {
+ "requests": [
+ {
+ "updateOne": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "update": [
+ {
+ "$set": {
+ "x": 1
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": [
+ {
+ "$set": {
+ "x": 1
+ }
+ }
+ ],
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "reply": {
+ "ok": 1,
+ "n": 1
+ },
+ "commandName": "update"
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "BulkWrite updateOne with sort option unsupported (server-side error)",
+ "runOnRequirements": [
+ {
+ "maxServerVersion": "7.99"
+ }
+ ],
+ "operations": [
+ {
+ "object": "collection0",
+ "name": "bulkWrite",
+ "arguments": {
+ "requests": [
+ {
+ "updateOne": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "update": [
+ {
+ "$set": {
+ "x": 1
+ }
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "expectError": {
+ "isClientError": false
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": [
+ {
+ "$set": {
+ "x": 1
+ }
+ }
+ ],
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json b/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json
index 2a81282de81..b6460f001f2 100644
--- a/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json
+++ b/driver-core/src/test/resources/unified-test-format/crud/db-aggregate-write-readPreference.json
@@ -52,13 +52,6 @@
}
}
],
- "initialData": [
- {
- "collectionName": "coll0",
- "databaseName": "db0",
- "documents": []
- }
- ],
"tests": [
{
"description": "Database-level aggregate with $out includes read preference for 5.0+ server",
@@ -141,17 +134,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll0",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
]
},
{
@@ -235,17 +217,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll0",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
]
},
{
@@ -332,17 +303,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll0",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
]
},
{
@@ -429,17 +389,6 @@
}
]
}
- ],
- "outcome": [
- {
- "collectionName": "coll0",
- "databaseName": "db0",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
]
}
]
diff --git a/driver-core/src/test/resources/unified-test-format/crud/replaceOne-sort.json b/driver-core/src/test/resources/unified-test-format/crud/replaceOne-sort.json
new file mode 100644
index 00000000000..cf2271dda57
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/crud/replaceOne-sort.json
@@ -0,0 +1,232 @@
+{
+ "description": "replaceOne-sort",
+ "schemaVersion": "1.0",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent",
+ "commandSucceededEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "crud-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "ReplaceOne with sort option",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0"
+ }
+ ],
+ "operations": [
+ {
+ "name": "replaceOne",
+ "object": "collection0",
+ "arguments": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "replacement": {
+ "x": 1
+ }
+ },
+ "expectResult": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedCount": 0
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "x": 1
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "reply": {
+ "ok": 1,
+ "n": 1
+ },
+ "commandName": "update"
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "replaceOne with sort option unsupported (server-side error)",
+ "runOnRequirements": [
+ {
+ "maxServerVersion": "7.99"
+ }
+ ],
+ "operations": [
+ {
+ "name": "replaceOne",
+ "object": "collection0",
+ "arguments": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "replacement": {
+ "x": 1
+ }
+ },
+ "expectError": {
+ "isClientError": false
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "x": 1
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/crud/updateOne-sort.json b/driver-core/src/test/resources/unified-test-format/crud/updateOne-sort.json
new file mode 100644
index 00000000000..8fe4f50b94f
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/crud/updateOne-sort.json
@@ -0,0 +1,240 @@
+{
+ "description": "updateOne-sort",
+ "schemaVersion": "1.0",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent",
+ "commandSucceededEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "crud-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "UpdateOne with sort option",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0"
+ }
+ ],
+ "operations": [
+ {
+ "name": "updateOne",
+ "object": "collection0",
+ "arguments": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ },
+ "expectResult": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedCount": 0
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "reply": {
+ "ok": 1,
+ "n": 1
+ },
+ "commandName": "update"
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 34
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "updateOne with sort option unsupported (server-side error)",
+ "runOnRequirements": [
+ {
+ "maxServerVersion": "7.99"
+ }
+ ],
+ "operations": [
+ {
+ "name": "updateOne",
+ "object": "collection0",
+ "arguments": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ },
+ "expectError": {
+ "isClientError": false
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "update": "coll0",
+ "updates": [
+ {
+ "q": {
+ "_id": {
+ "$gt": 1
+ }
+ },
+ "u": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "sort": {
+ "_id": -1
+ },
+ "multi": {
+ "$$unsetOrMatches": false
+ },
+ "upsert": {
+ "$$unsetOrMatches": false
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "crud-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndex.json b/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndex.json
index 327cb612593..f4f2a6c6612 100644
--- a/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndex.json
+++ b/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndex.json
@@ -28,7 +28,17 @@
],
"runOnRequirements": [
{
- "minServerVersion": "7.0.0",
+ "minServerVersion": "7.0.5",
+ "maxServerVersion": "7.0.99",
+ "topologies": [
+ "replicaset",
+ "load-balanced",
+ "sharded"
+ ],
+ "serverless": "forbid"
+ },
+ {
+ "minServerVersion": "7.2.0",
"topologies": [
"replicaset",
"load-balanced",
diff --git a/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndexes.json b/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndexes.json
index d91d7d9cf3c..01300b1b7f4 100644
--- a/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndexes.json
+++ b/driver-core/src/test/resources/unified-test-format/index-management/createSearchIndexes.json
@@ -28,7 +28,17 @@
],
"runOnRequirements": [
{
- "minServerVersion": "7.0.0",
+ "minServerVersion": "7.0.5",
+ "maxServerVersion": "7.0.99",
+ "topologies": [
+ "replicaset",
+ "load-balanced",
+ "sharded"
+ ],
+ "serverless": "forbid"
+ },
+ {
+ "minServerVersion": "7.2.0",
"topologies": [
"replicaset",
"load-balanced",
diff --git a/driver-core/src/test/resources/unified-test-format/load-balancers/README.md b/driver-core/src/test/resources/unified-test-format/load-balancers/README.md
index e69de29bb2d..45f185caa61 100644
--- a/driver-core/src/test/resources/unified-test-format/load-balancers/README.md
+++ b/driver-core/src/test/resources/unified-test-format/load-balancers/README.md
@@ -0,0 +1,49 @@
+# Load Balancer Support Tests
+
+______________________________________________________________________
+
+## Introduction
+
+This document describes how drivers should create load balanced clusters for testing and how tests should be executed
+for such clusters.
+
+## Testing Requirements
+
+For each server version that supports load balanced clusters, drivers MUST add two Evergreen tasks: one with a sharded
+cluster with both authentication and TLS enabled and one with a sharded cluster with authentication and TLS disabled. In
+each task, the sharded cluster MUST be configured with two mongos nodes running on localhost ports 27017 and 27018. The
+shard and config servers may run on any free ports. Each task MUST also start up two TCP load balancers operating in
+round-robin mode: one fronting both mongos servers and one fronting a single mongos.
+
+### Load Balancer Configuration
+
+Drivers MUST use the `run-load-balancer.sh` script in `drivers-evergreen-tools` to start the TCP load balancers for
+Evergreen tasks. This script MUST be run after the backing sharded cluster has already been started. The script writes
+the URIs of the load balancers to a YAML expansions file, which can be read by drivers via the `expansions.update`
+Evergreen command. This will store the URIs into the `SINGLE_MONGOS_LB_URI` and `MULTI_MONGOS_LB_URI` environment
+variables.
+
+### Test Runner Configuration
+
+If the backing sharded cluster is configured with TLS enabled, drivers MUST add the relevant TLS options to both
+`SINGLE_MONGOS_LB_URI` and `MULTI_MONGOS_LB_URI` to ensure that test clients can connect to the cluster. Drivers MUST
+use the final URI stored in `SINGLE_MONGOS_LB_URI` (with additional TLS options if required) to configure internal
+clients for test runners (e.g. the internal MongoClient described by the
+[Unified Test Format spec](../../unified-test-format/unified-test-format.md)).
+
+In addition to modifying load balancer URIs, drivers MUST also mock server support for returning a `serviceId` field in
+`hello` or legacy `hello` command responses when running tests against a load-balanced cluster. This can be done by
+using the value of `topologyVersion.processId` to set `serviceId`. This MUST be done for all connections established by
+the test runner, including those made by any internal clients.
+
+## Tests
+
+The YAML and JSON files in this directory contain platform-independent tests written in the
+[Unified Test Format](../../unified-test-format/unified-test-format.md). Drivers MUST run the following test suites
+against a load balanced cluster:
+
+1. All test suites written in the Unified Test Format
+2. Retryable Reads
+3. Retryable Writes
+4. Change Streams
+5. Initial DNS Seedlist Discovery
diff --git a/driver-core/src/test/resources/unified-test-format/load-balancers/cursors.json b/driver-core/src/test/resources/unified-test-format/load-balancers/cursors.json
index 7da08c94d6c..b11bf2c6fae 100644
--- a/driver-core/src/test/resources/unified-test-format/load-balancers/cursors.json
+++ b/driver-core/src/test/resources/unified-test-format/load-balancers/cursors.json
@@ -1,6 +1,6 @@
{
"description": "cursors are correctly pinned to connections for load-balanced clusters",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"topologies": [
@@ -222,7 +222,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"firstBatch": {
"$$type": "array"
@@ -239,7 +242,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
@@ -333,7 +339,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"firstBatch": {
"$$type": "array"
@@ -475,7 +484,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"firstBatch": {
"$$type": "array"
@@ -492,7 +504,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
@@ -605,7 +620,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"firstBatch": {
"$$type": "array"
@@ -750,7 +768,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"firstBatch": {
"$$type": "array"
@@ -767,7 +788,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
@@ -858,7 +882,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
@@ -950,7 +977,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": {
"$$type": "string"
@@ -996,11 +1026,6 @@
},
{
"description": "listIndexes pins the cursor to a connection",
- "runOnRequirements": [
- {
- "serverless": "forbid"
- }
- ],
"operations": [
{
"name": "createIndex",
@@ -1105,7 +1130,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
diff --git a/driver-core/src/test/resources/unified-test-format/load-balancers/non-lb-connection-establishment.json b/driver-core/src/test/resources/unified-test-format/load-balancers/non-lb-connection-establishment.json
index d2d26856d97..6aaa7bdf98b 100644
--- a/driver-core/src/test/resources/unified-test-format/load-balancers/non-lb-connection-establishment.json
+++ b/driver-core/src/test/resources/unified-test-format/load-balancers/non-lb-connection-establishment.json
@@ -3,7 +3,6 @@
"schemaVersion": "1.3",
"runOnRequirements": [
{
- "minServerVersion": "3.6",
"topologies": [
"single",
"sharded"
diff --git a/driver-core/src/test/resources/unified-test-format/load-balancers/sdam-error-handling.json b/driver-core/src/test/resources/unified-test-format/load-balancers/sdam-error-handling.json
index 4ab34b1fed4..47323fae4f3 100644
--- a/driver-core/src/test/resources/unified-test-format/load-balancers/sdam-error-handling.json
+++ b/driver-core/src/test/resources/unified-test-format/load-balancers/sdam-error-handling.json
@@ -1,6 +1,6 @@
{
"description": "state change errors are correctly handled",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"topologies": [
@@ -263,7 +263,7 @@
"description": "errors during the initial connection hello are ignored",
"runOnRequirements": [
{
- "minServerVersion": "4.9"
+ "minServerVersion": "4.4.7"
}
],
"operations": [
diff --git a/driver-core/src/test/resources/unified-test-format/load-balancers/transactions.json b/driver-core/src/test/resources/unified-test-format/load-balancers/transactions.json
index 8cf24f4ca4f..0dd04ee8540 100644
--- a/driver-core/src/test/resources/unified-test-format/load-balancers/transactions.json
+++ b/driver-core/src/test/resources/unified-test-format/load-balancers/transactions.json
@@ -1,6 +1,6 @@
{
"description": "transactions are correctly pinned to connections for load-balanced clusters",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"topologies": [
diff --git a/driver-core/src/test/resources/unified-test-format/retryable-reads/readConcernMajorityNotAvailableYet.json b/driver-core/src/test/resources/unified-test-format/retryable-reads/readConcernMajorityNotAvailableYet.json
new file mode 100644
index 00000000000..8aa6a6b5e5e
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/retryable-reads/readConcernMajorityNotAvailableYet.json
@@ -0,0 +1,147 @@
+{
+ "description": "ReadConcernMajorityNotAvailableYet is a retryable read",
+ "schemaVersion": "1.3",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "4.0",
+ "topologies": [
+ "single",
+ "replicaset"
+ ]
+ },
+ {
+ "minServerVersion": "4.1.7",
+ "topologies": [
+ "sharded",
+ "load-balanced"
+ ]
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "commandStartedEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "retryable-reads-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "readconcernmajoritynotavailableyet_test"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "readconcernmajoritynotavailableyet_test",
+ "databaseName": "retryable-reads-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "Find succeeds on second attempt after ReadConcernMajorityNotAvailableYet",
+ "operations": [
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "find"
+ ],
+ "errorCode": 134
+ }
+ }
+ }
+ },
+ {
+ "name": "find",
+ "arguments": {
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ }
+ },
+ "object": "collection0",
+ "expectResult": [
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "find": "readconcernmajoritynotavailableyet_test",
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ }
+ },
+ "commandName": "find",
+ "databaseName": "retryable-reads-tests"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "command": {
+ "find": "readconcernmajoritynotavailableyet_test",
+ "filter": {
+ "_id": {
+ "$gt": 1
+ }
+ }
+ },
+ "commandName": "find",
+ "databaseName": "retryable-reads-tests"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-clientErrors.json b/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-clientErrors.json
new file mode 100644
index 00000000000..d16e0c9c8d6
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-clientErrors.json
@@ -0,0 +1,351 @@
+{
+ "description": "client bulkWrite retryable writes with client errors",
+ "schemaVersion": "1.21",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0",
+ "topologies": [
+ "replicaset",
+ "sharded",
+ "load-balanced"
+ ],
+ "serverless": "forbid"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent"
+ ],
+ "useMultipleMongoses": false
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "retryable-writes-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "retryable-writes-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "_yamlAnchors": {
+ "namespace": "retryable-writes-tests.coll0"
+ },
+ "tests": [
+ {
+ "description": "client bulkWrite with one network error succeeds after retry",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "closeConnection": true
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ }
+ ],
+ "verboseResults": true
+ },
+ "expectResult": {
+ "insertedCount": 1,
+ "upsertedCount": 0,
+ "matchedCount": 0,
+ "modifiedCount": 0,
+ "deletedCount": 0,
+ "insertResults": {
+ "0": {
+ "insertedId": 4
+ }
+ },
+ "updateResults": {},
+ "deleteResults": {}
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "retryable-writes-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ },
+ {
+ "_id": 4,
+ "x": 44
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client bulkWrite with two network errors fails after retry",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 2
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "closeConnection": true
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ }
+ ],
+ "verboseResults": true
+ },
+ "expectError": {
+ "isClientError": true,
+ "errorLabelsContain": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "retryable-writes-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-serverErrors.json b/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-serverErrors.json
new file mode 100644
index 00000000000..f58c82bcc73
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/retryable-writes/client-bulkWrite-serverErrors.json
@@ -0,0 +1,873 @@
+{
+ "description": "client bulkWrite retryable writes",
+ "schemaVersion": "1.21",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0",
+ "topologies": [
+ "replicaset",
+ "sharded",
+ "load-balanced"
+ ],
+ "serverless": "forbid"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent"
+ ],
+ "useMultipleMongoses": false
+ }
+ },
+ {
+ "client": {
+ "id": "clientRetryWritesFalse",
+ "uriOptions": {
+ "retryWrites": false
+ },
+ "observeEvents": [
+ "commandStartedEvent"
+ ],
+ "useMultipleMongoses": false
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "retryable-writes-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "retryable-writes-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ }
+ ]
+ }
+ ],
+ "_yamlAnchors": {
+ "namespace": "retryable-writes-tests.coll0"
+ },
+ "tests": [
+ {
+ "description": "client bulkWrite with no multi: true operations succeeds after retryable top-level error",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "errorCode": 189,
+ "errorLabels": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ },
+ {
+ "updateOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ }
+ },
+ {
+ "replaceOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 2
+ },
+ "replacement": {
+ "x": 222
+ }
+ }
+ },
+ {
+ "deleteOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 3
+ }
+ }
+ }
+ ],
+ "verboseResults": true
+ },
+ "expectResult": {
+ "insertedCount": 1,
+ "upsertedCount": 0,
+ "matchedCount": 2,
+ "modifiedCount": 2,
+ "deletedCount": 1,
+ "insertResults": {
+ "0": {
+ "insertedId": 4
+ }
+ },
+ "updateResults": {
+ "1": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedId": {
+ "$$exists": false
+ }
+ },
+ "2": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedId": {
+ "$$exists": false
+ }
+ }
+ },
+ "deleteResults": {
+ "3": {
+ "deletedCount": 1
+ }
+ }
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": false
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 2
+ },
+ "updateMods": {
+ "x": 222
+ },
+ "multi": false
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": false
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": false
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 2
+ },
+ "updateMods": {
+ "x": 222
+ },
+ "multi": false
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": false
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "retryable-writes-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 12
+ },
+ {
+ "_id": 2,
+ "x": 222
+ },
+ {
+ "_id": 4,
+ "x": 44
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client bulkWrite with multi: true operations fails after retryable top-level error",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "errorCode": 189,
+ "errorLabels": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "updateMany": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ }
+ },
+ {
+ "deleteMany": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 3
+ }
+ }
+ }
+ ]
+ },
+ "expectError": {
+ "errorCode": 189,
+ "errorLabelsContain": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": true,
+ "ordered": true,
+ "ops": [
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": true
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": true
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "errorLabels": [
+ "RetryableWriteError"
+ ],
+ "writeConcernError": {
+ "code": 91,
+ "errmsg": "Replication is being shut down"
+ }
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ },
+ {
+ "updateOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ }
+ },
+ {
+ "replaceOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 2
+ },
+ "replacement": {
+ "x": 222
+ }
+ }
+ },
+ {
+ "deleteOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 3
+ }
+ }
+ }
+ ],
+ "verboseResults": true
+ },
+ "expectResult": {
+ "insertedCount": 1,
+ "upsertedCount": 0,
+ "matchedCount": 2,
+ "modifiedCount": 2,
+ "deletedCount": 1,
+ "insertResults": {
+ "0": {
+ "insertedId": 4
+ }
+ },
+ "updateResults": {
+ "1": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedId": {
+ "$$exists": false
+ }
+ },
+ "2": {
+ "matchedCount": 1,
+ "modifiedCount": 1,
+ "upsertedId": {
+ "$$exists": false
+ }
+ }
+ },
+ "deleteResults": {
+ "3": {
+ "deletedCount": 1
+ }
+ }
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": false
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 2
+ },
+ "updateMods": {
+ "x": 222
+ },
+ "multi": false
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": false
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": false,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": false
+ },
+ {
+ "update": 0,
+ "filter": {
+ "_id": 2
+ },
+ "updateMods": {
+ "x": 222
+ },
+ "multi": false
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": false
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ],
+ "lsid": {
+ "$$exists": true
+ },
+ "txnNumber": {
+ "$$exists": true
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client bulkWrite with multi: true operations fails after retryable writeConcernError",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "client0",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "errorLabels": [
+ "RetryableWriteError"
+ ],
+ "writeConcernError": {
+ "code": 91,
+ "errmsg": "Replication is being shut down"
+ }
+ }
+ }
+ }
+ },
+ {
+ "object": "client0",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "updateMany": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 1
+ },
+ "update": {
+ "$inc": {
+ "x": 1
+ }
+ }
+ }
+ },
+ {
+ "deleteMany": {
+ "namespace": "retryable-writes-tests.coll0",
+ "filter": {
+ "_id": 3
+ }
+ }
+ }
+ ]
+ },
+ "expectError": {
+ "writeConcernErrors": [
+ {
+ "code": 91,
+ "message": "Replication is being shut down"
+ }
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": true,
+ "ordered": true,
+ "ops": [
+ {
+ "update": 0,
+ "filter": {
+ "_id": 1
+ },
+ "updateMods": {
+ "$inc": {
+ "x": 1
+ }
+ },
+ "multi": true
+ },
+ {
+ "delete": 0,
+ "filter": {
+ "_id": 3
+ },
+ "multi": true
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client bulkWrite with retryWrites: false does not retry",
+ "operations": [
+ {
+ "object": "testRunner",
+ "name": "failPoint",
+ "arguments": {
+ "client": "clientRetryWritesFalse",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 1
+ },
+ "data": {
+ "failCommands": [
+ "bulkWrite"
+ ],
+ "errorCode": 189,
+ "errorLabels": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "object": "clientRetryWritesFalse",
+ "name": "clientBulkWrite",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-tests.coll0",
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ }
+ ]
+ },
+ "expectError": {
+ "errorCode": 189,
+ "errorLabelsContain": [
+ "RetryableWriteError"
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "clientRetryWritesFalse",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite",
+ "databaseName": "admin",
+ "command": {
+ "bulkWrite": 1,
+ "errorsOnly": true,
+ "ordered": true,
+ "ops": [
+ {
+ "insert": 0,
+ "document": {
+ "_id": 4,
+ "x": 44
+ }
+ }
+ ],
+ "nsInfo": [
+ {
+ "ns": "retryable-writes-tests.coll0"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/retryable-writes/handshakeError.json b/driver-core/src/test/resources/unified-test-format/retryable-writes/handshakeError.json
index df37bd72322..93cb2e849ec 100644
--- a/driver-core/src/test/resources/unified-test-format/retryable-writes/handshakeError.json
+++ b/driver-core/src/test/resources/unified-test-format/retryable-writes/handshakeError.json
@@ -1,6 +1,6 @@
{
"description": "retryable writes handshake failures",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.2",
@@ -53,6 +53,224 @@
}
],
"tests": [
+ {
+ "description": "client.clientBulkWrite succeeds after retryable handshake network error",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0",
+ "serverless": "forbid"
+ }
+ ],
+ "operations": [
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 2
+ },
+ "data": {
+ "failCommands": [
+ "ping",
+ "saslContinue"
+ ],
+ "closeConnection": true
+ }
+ }
+ }
+ },
+ {
+ "name": "runCommand",
+ "object": "database",
+ "arguments": {
+ "commandName": "ping",
+ "command": {
+ "ping": 1
+ }
+ },
+ "expectError": {
+ "isError": true
+ }
+ },
+ {
+ "name": "clientBulkWrite",
+ "object": "client",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-handshake-tests.coll",
+ "document": {
+ "_id": 8,
+ "x": 88
+ }
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "cmap",
+ "events": [
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ }
+ ]
+ },
+ {
+ "client": "client",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "ping": 1
+ },
+ "databaseName": "retryable-writes-handshake-tests"
+ }
+ },
+ {
+ "commandFailedEvent": {
+ "commandName": "ping"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "bulkWrite"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "8.0",
+ "serverless": "forbid"
+ }
+ ],
+ "operations": [
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 2
+ },
+ "data": {
+ "failCommands": [
+ "ping",
+ "saslContinue"
+ ],
+ "closeConnection": true
+ }
+ }
+ }
+ },
+ {
+ "name": "runCommand",
+ "object": "database",
+ "arguments": {
+ "commandName": "ping",
+ "command": {
+ "ping": 1
+ }
+ },
+ "expectError": {
+ "isError": true
+ }
+ },
+ {
+ "name": "clientBulkWrite",
+ "object": "client",
+ "arguments": {
+ "models": [
+ {
+ "insertOne": {
+ "namespace": "retryable-writes-handshake-tests.coll",
+ "document": {
+ "_id": 8,
+ "x": 88
+ }
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "cmap",
+ "events": [
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ },
+ {
+ "connectionCheckOutStartedEvent": {}
+ }
+ ]
+ },
+ {
+ "client": "client",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "ping": 1
+ },
+ "databaseName": "retryable-writes-handshake-tests"
+ }
+ },
+ {
+ "commandFailedEvent": {
+ "commandName": "ping"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "bulkWrite"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "bulkWrite"
+ }
+ }
+ ]
+ }
+ ]
+ },
{
"description": "collection.insertOne succeeds after retryable handshake network error",
"operations": [
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-error.json
index 5c78ecfe503..62d26494c7c 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-error.json
@@ -1,6 +1,6 @@
{
"description": "auth-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-misc-command-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-misc-command-error.json
index 6e1b645461e..fd62fe604e9 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-misc-command-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-misc-command-error.json
@@ -1,6 +1,6 @@
{
"description": "auth-misc-command-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-error.json
index 7606d2db7ab..84763af32e4 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-error.json
@@ -1,6 +1,6 @@
{
"description": "auth-network-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-timeout-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-timeout-error.json
index 22066e8baeb..3cf9576eba9 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-timeout-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-network-timeout-error.json
@@ -1,6 +1,6 @@
{
"description": "auth-network-timeout-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-shutdown-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-shutdown-error.json
index 5dd7b5bb6fe..b9e503af66e 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-shutdown-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/auth-shutdown-error.json
@@ -1,6 +1,6 @@
{
"description": "auth-shutdown-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/cancel-server-check.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/cancel-server-check.json
index 896cc8d0871..a60ccfcb414 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/cancel-server-check.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/cancel-server-check.json
@@ -1,6 +1,6 @@
{
"description": "cancel-server-check",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.0",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/connectTimeoutMS.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/connectTimeoutMS.json
index 67a4d9da1d3..d3e860a9cb2 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/connectTimeoutMS.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/connectTimeoutMS.json
@@ -1,6 +1,6 @@
{
"description": "connectTimeoutMS",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-error.json
index 651466bfa6d..c1b6db40ca3 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-error.json
@@ -1,6 +1,6 @@
{
"description": "find-network-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-timeout-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-timeout-error.json
index 2bde6daa5df..e5ac9f21aa7 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-timeout-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-network-timeout-error.json
@@ -1,6 +1,6 @@
{
"description": "find-network-timeout-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-shutdown-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-shutdown-error.json
index 624ad352fc9..6e5a2cac055 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-shutdown-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/find-shutdown-error.json
@@ -1,6 +1,6 @@
{
"description": "find-shutdown-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-command-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-command-error.json
index 7d6046b76f5..87958cb2c0b 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-command-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-command-error.json
@@ -1,9 +1,9 @@
{
"description": "hello-command-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
- "minServerVersion": "4.9",
+ "minServerVersion": "4.4.7",
"serverless": "forbid",
"topologies": [
"single",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-network-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-network-error.json
index f44b26a9f91..15ed2b605e2 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-network-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-network-error.json
@@ -1,9 +1,9 @@
{
"description": "hello-network-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
- "minServerVersion": "4.9",
+ "minServerVersion": "4.4.7",
"serverless": "forbid",
"topologies": [
"single",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-timeout.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-timeout.json
index dfa6b48d66b..fe7cf4e78d1 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-timeout.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/hello-timeout.json
@@ -1,6 +1,6 @@
{
"description": "hello-timeout",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-network-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-network-error.json
index e4ba6684ae2..bfe41a4cb66 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-network-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-network-error.json
@@ -1,6 +1,6 @@
{
"description": "insert-network-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-shutdown-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-shutdown-error.json
index 3c724fa5e4c..af7c6c987af 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-shutdown-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/insert-shutdown-error.json
@@ -1,6 +1,6 @@
{
"description": "insert-shutdown-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/interruptInUse-pool-clear.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/interruptInUse-pool-clear.json
new file mode 100644
index 00000000000..d9329646d4c
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/interruptInUse-pool-clear.json
@@ -0,0 +1,591 @@
+{
+ "description": "interruptInUse",
+ "schemaVersion": "1.11",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "4.4",
+ "serverless": "forbid",
+ "topologies": [
+ "replicaset",
+ "sharded"
+ ]
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "setupClient",
+ "useMultipleMongoses": false
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "interruptInUse",
+ "databaseName": "sdam-tests",
+ "documents": []
+ }
+ ],
+ "tests": [
+ {
+ "description": "Connection pool clear uses interruptInUseConnections=true after monitor timeout",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "poolClearedEvent",
+ "connectionClosedEvent",
+ "commandStartedEvent",
+ "commandSucceededEvent",
+ "commandFailedEvent",
+ "connectionCheckedOutEvent",
+ "connectionCheckedInEvent"
+ ],
+ "uriOptions": {
+ "connectTimeoutMS": 500,
+ "heartbeatFrequencyMS": 500,
+ "appname": "interruptInUse",
+ "retryReads": false,
+ "minPoolSize": 0
+ }
+ }
+ },
+ {
+ "database": {
+ "id": "database",
+ "client": "client",
+ "databaseName": "sdam-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection",
+ "database": "database",
+ "collectionName": "interruptInUse"
+ }
+ },
+ {
+ "thread": {
+ "id": "thread1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "insertOne",
+ "object": "collection",
+ "arguments": {
+ "document": {
+ "_id": 1
+ }
+ }
+ },
+ {
+ "name": "runOnThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1",
+ "operation": {
+ "name": "find",
+ "object": "collection",
+ "arguments": {
+ "filter": {
+ "$where": "sleep(2000) || true"
+ }
+ },
+ "expectError": {
+ "isError": true
+ }
+ }
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "setupClient",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 4
+ },
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "blockConnection": true,
+ "blockTimeMS": 1500,
+ "appName": "interruptInUse"
+ }
+ }
+ }
+ },
+ {
+ "name": "waitForThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1"
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "command",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "find"
+ }
+ },
+ {
+ "commandFailedEvent": {
+ "commandName": "find"
+ }
+ }
+ ]
+ },
+ {
+ "client": "client",
+ "eventType": "cmap",
+ "events": [
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "poolClearedEvent": {
+ "interruptInUseConnections": true
+ }
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionClosedEvent": {}
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "interruptInUse",
+ "databaseName": "sdam-tests",
+ "documents": [
+ {
+ "_id": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Error returned from connection pool clear with interruptInUseConnections=true is retryable",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "poolClearedEvent",
+ "connectionClosedEvent",
+ "commandStartedEvent",
+ "commandFailedEvent",
+ "commandSucceededEvent",
+ "connectionCheckedOutEvent",
+ "connectionCheckedInEvent"
+ ],
+ "uriOptions": {
+ "connectTimeoutMS": 500,
+ "heartbeatFrequencyMS": 500,
+ "appname": "interruptInUseRetryable",
+ "retryReads": true,
+ "minPoolSize": 0
+ }
+ }
+ },
+ {
+ "database": {
+ "id": "database",
+ "client": "client",
+ "databaseName": "sdam-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection",
+ "database": "database",
+ "collectionName": "interruptInUse"
+ }
+ },
+ {
+ "thread": {
+ "id": "thread1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "insertOne",
+ "object": "collection",
+ "arguments": {
+ "document": {
+ "_id": 1
+ }
+ }
+ },
+ {
+ "name": "runOnThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1",
+ "operation": {
+ "name": "find",
+ "object": "collection",
+ "arguments": {
+ "filter": {
+ "$where": "sleep(2000) || true"
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "setupClient",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 4
+ },
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "blockConnection": true,
+ "blockTimeMS": 1500,
+ "appName": "interruptInUseRetryable"
+ }
+ }
+ }
+ },
+ {
+ "name": "waitForThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1"
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "command",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "find"
+ }
+ },
+ {
+ "commandFailedEvent": {
+ "commandName": "find"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "find"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "find"
+ }
+ }
+ ]
+ },
+ {
+ "client": "client",
+ "eventType": "cmap",
+ "events": [
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "poolClearedEvent": {
+ "interruptInUseConnections": true
+ }
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionClosedEvent": {}
+ },
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "connectionCheckedInEvent": {}
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "interruptInUse",
+ "databaseName": "sdam-tests",
+ "documents": [
+ {
+ "_id": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Error returned from connection pool clear with interruptInUseConnections=true is retryable for write",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "poolClearedEvent",
+ "connectionClosedEvent",
+ "commandStartedEvent",
+ "commandFailedEvent",
+ "commandSucceededEvent",
+ "connectionCheckedOutEvent",
+ "connectionCheckedInEvent"
+ ],
+ "uriOptions": {
+ "connectTimeoutMS": 500,
+ "heartbeatFrequencyMS": 500,
+ "appname": "interruptInUseRetryableWrite",
+ "retryWrites": true,
+ "minPoolSize": 0
+ }
+ }
+ },
+ {
+ "database": {
+ "id": "database",
+ "client": "client",
+ "databaseName": "sdam-tests"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection",
+ "database": "database",
+ "collectionName": "interruptInUse"
+ }
+ },
+ {
+ "thread": {
+ "id": "thread1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "insertOne",
+ "object": "collection",
+ "arguments": {
+ "document": {
+ "_id": 1
+ }
+ }
+ },
+ {
+ "name": "runOnThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1",
+ "operation": {
+ "name": "updateOne",
+ "object": "collection",
+ "arguments": {
+ "filter": {
+ "$where": "sleep(2000) || true"
+ },
+ "update": {
+ "$set": {
+ "a": "bar"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "setupClient",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": {
+ "times": 4
+ },
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "blockConnection": true,
+ "blockTimeMS": 1500,
+ "appName": "interruptInUseRetryableWrite"
+ }
+ }
+ }
+ },
+ {
+ "name": "waitForThread",
+ "object": "testRunner",
+ "arguments": {
+ "thread": "thread1"
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "command",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "insert"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "update"
+ }
+ },
+ {
+ "commandFailedEvent": {
+ "commandName": "update"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "commandName": "update"
+ }
+ },
+ {
+ "commandSucceededEvent": {
+ "commandName": "update"
+ }
+ }
+ ]
+ },
+ {
+ "client": "client",
+ "eventType": "cmap",
+ "events": [
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "poolClearedEvent": {
+ "interruptInUseConnections": true
+ }
+ },
+ {
+ "connectionCheckedInEvent": {}
+ },
+ {
+ "connectionClosedEvent": {}
+ },
+ {
+ "connectionCheckedOutEvent": {}
+ },
+ {
+ "connectionCheckedInEvent": {}
+ }
+ ]
+ }
+ ],
+ "outcome": [
+ {
+ "collectionName": "interruptInUse",
+ "databaseName": "sdam-tests",
+ "documents": [
+ {
+ "_id": 1,
+ "a": "bar"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/loadbalanced-emit-topology-changed-before-close.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/loadbalanced-emit-topology-changed-before-close.json
new file mode 100644
index 00000000000..30c0657630b
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/loadbalanced-emit-topology-changed-before-close.json
@@ -0,0 +1,88 @@
+{
+ "description": "loadbalanced-emit-topology-description-changed-before-close",
+ "schemaVersion": "1.20",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "load-balanced"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeEvents": [
+ "topologyDescriptionChangedEvent",
+ "topologyOpeningEvent",
+ "topologyClosedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 2
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "sdam",
+ "events": [
+ {
+ "topologyOpeningEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {}
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "newDescription": {
+ "type": "LoadBalanced"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyClosedEvent": {}
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-loadbalanced.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-loadbalanced.json
new file mode 100644
index 00000000000..0ad3b0ceaa5
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-loadbalanced.json
@@ -0,0 +1,166 @@
+{
+ "description": "loadbalanced-logging",
+ "schemaVersion": "1.16",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "load-balanced"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "topologyDescriptionChangedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 2
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-replicaset.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-replicaset.json
new file mode 100644
index 00000000000..e6738225cd0
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-replicaset.json
@@ -0,0 +1,606 @@
+{
+ "description": "replicaset-logging",
+ "schemaVersion": "1.16",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "replicaset"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "setupClient"
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "topologyDescriptionChangedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 4
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Successful heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatSucceededEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatSucceededEvent": {}
+ },
+ "count": 3
+ }
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "serverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "reply": {
+ "$$matchAsDocument": {
+ "$$matchAsRoot": {
+ "ok": 1
+ }
+ }
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "serverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "reply": {
+ "$$matchAsDocument": {
+ "$$matchAsRoot": {
+ "ok": 1
+ }
+ }
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "serverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "reply": {
+ "$$matchAsDocument": {
+ "$$matchAsRoot": {
+ "ok": 1
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Failing heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatFailedEvent"
+ ],
+ "uriOptions": {
+ "appname": "failingHeartbeatLoggingTest"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": "alwaysOn",
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "appName": "failingHeartbeatLoggingTest",
+ "closeConnection": true
+ }
+ },
+ "client": "setupClient"
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatFailedEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "failure": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-sharded.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-sharded.json
new file mode 100644
index 00000000000..61b27f5be0b
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-sharded.json
@@ -0,0 +1,492 @@
+{
+ "description": "sharded-logging",
+ "schemaVersion": "1.16",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "sharded"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "setupClient",
+ "useMultipleMongoses": false
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "topologyDescriptionChangedEvent"
+ ],
+ "useMultipleMongoses": true
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 3
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Successful heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatSucceededEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatSucceededEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "serverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "reply": {
+ "$$matchAsDocument": {
+ "$$matchAsRoot": {
+ "ok": 1
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Failing heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatStartedEvent",
+ "serverHeartbeatFailedEvent"
+ ],
+ "uriOptions": {
+ "appname": "failingHeartbeatLoggingTest"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": "alwaysOn",
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "appName": "failingHeartbeatLoggingTest",
+ "closeConnection": true
+ }
+ },
+ "client": "setupClient"
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatFailedEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "failure": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-standalone.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-standalone.json
new file mode 100644
index 00000000000..1ee6dbe8995
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/logging-standalone.json
@@ -0,0 +1,517 @@
+{
+ "description": "standalone-logging",
+ "schemaVersion": "1.16",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "single"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "setupClient"
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "topologyDescriptionChangedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 2
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring",
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed",
+ "topologyId": {
+ "$$exists": true
+ },
+ "previousDescription": {
+ "$$exists": true
+ },
+ "newDescription": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring",
+ "topologyId": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Successful heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatSucceededEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatSucceededEvent": {}
+ },
+ "count": 1
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ }
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "serverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "reply": {
+ "$$matchAsDocument": {
+ "$$matchAsRoot": {
+ "ok": 1
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "Failing heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeLogMessages": {
+ "topology": "debug"
+ },
+ "observeEvents": [
+ "serverHeartbeatFailedEvent"
+ ],
+ "uriOptions": {
+ "appname": "failingHeartbeatLoggingTest"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "failPoint",
+ "object": "testRunner",
+ "arguments": {
+ "client": "setupClient",
+ "failPoint": {
+ "configureFailPoint": "failCommand",
+ "mode": "alwaysOn",
+ "data": {
+ "failCommands": [
+ "hello",
+ "isMaster"
+ ],
+ "appName": "failingHeartbeatLoggingTest",
+ "closeConnection": true
+ }
+ }
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatFailedEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ],
+ "expectLogMessages": [
+ {
+ "client": "client",
+ "ignoreExtraMessages": true,
+ "ignoreMessages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped topology monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Stopped server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Topology description changed"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting server monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Starting topology monitoring"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat started"
+ }
+ },
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat succeeded"
+ }
+ }
+ ],
+ "messages": [
+ {
+ "level": "debug",
+ "component": "topology",
+ "data": {
+ "message": "Server heartbeat failed",
+ "awaited": {
+ "$$exists": true
+ },
+ "topologyId": {
+ "$$exists": true
+ },
+ "serverHost": {
+ "$$type": "string"
+ },
+ "serverPort": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "driverConnectionId": {
+ "$$exists": true
+ },
+ "durationMS": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "failure": {
+ "$$exists": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/minPoolSize-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/minPoolSize-error.json
index 0234ac99292..bd9e9fcdec7 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/minPoolSize-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/minPoolSize-error.json
@@ -1,9 +1,9 @@
{
"description": "minPoolSize-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
- "minServerVersion": "4.9",
+ "minServerVersion": "4.4.7",
"serverless": "forbid",
"topologies": [
"single"
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/pool-cleared-error.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/pool-cleared-error.json
index 9a7dfd901c5..b7f6924f2ba 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/pool-cleared-error.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/pool-cleared-error.json
@@ -1,6 +1,6 @@
{
"description": "pool-cleared-error",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.9",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/rediscover-quickly-after-step-down.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/rediscover-quickly-after-step-down.json
index c7c2494857a..3147a07a1e6 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/rediscover-quickly-after-step-down.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/rediscover-quickly-after-step-down.json
@@ -1,6 +1,6 @@
{
"description": "rediscover-quickly-after-step-down",
- "schemaVersion": "1.10",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.4",
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/replicaset-emit-topology-changed-before-close.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/replicaset-emit-topology-changed-before-close.json
new file mode 100644
index 00000000000..066a4ffee5f
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/replicaset-emit-topology-changed-before-close.json
@@ -0,0 +1,89 @@
+{
+ "description": "replicaset-emit-topology-description-changed-before-close",
+ "schemaVersion": "1.20",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "replicaset"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeEvents": [
+ "topologyDescriptionChangedEvent",
+ "topologyOpeningEvent",
+ "topologyClosedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 4
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "sdam",
+ "ignoreExtraEvents": false,
+ "events": [
+ {
+ "topologyOpeningEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "ReplicaSetWithPrimary"
+ },
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyClosedEvent": {}
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/serverMonitoringMode.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/serverMonitoringMode.json
index 7d681b4f9ec..4b492f7d853 100644
--- a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/serverMonitoringMode.json
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/serverMonitoringMode.json
@@ -444,6 +444,69 @@
]
}
]
+ },
+ {
+ "description": "poll waits after successful heartbeat",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "uriOptions": {
+ "serverMonitoringMode": "poll",
+ "heartbeatFrequencyMS": 1000000
+ },
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "serverHeartbeatStartedEvent",
+ "serverHeartbeatSucceededEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "db",
+ "client": "client",
+ "databaseName": "sdam-tests"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatSucceededEvent": {}
+ },
+ "count": 1
+ }
+ },
+ {
+ "name": "wait",
+ "object": "testRunner",
+ "arguments": {
+ "ms": 500
+ }
+ },
+ {
+ "name": "assertEventCount",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "serverHeartbeatStartedEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ]
}
]
}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/sharded-emit-topology-changed-before-close.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/sharded-emit-topology-changed-before-close.json
new file mode 100644
index 00000000000..98fb5855314
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/sharded-emit-topology-changed-before-close.json
@@ -0,0 +1,108 @@
+{
+ "description": "sharded-emit-topology-description-changed-before-close",
+ "schemaVersion": "1.20",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "sharded"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeEvents": [
+ "topologyDescriptionChangedEvent",
+ "topologyOpeningEvent",
+ "topologyClosedEvent"
+ ],
+ "useMultipleMongoses": true
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 3
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "sdam",
+ "ignoreExtraEvents": false,
+ "events": [
+ {
+ "topologyOpeningEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {
+ "type": "Sharded"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Sharded"
+ },
+ "newDescription": {
+ "type": "Sharded"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Sharded"
+ },
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyClosedEvent": {}
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/standalone-emit-topology-changed-before-close.json b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/standalone-emit-topology-changed-before-close.json
new file mode 100644
index 00000000000..27b5444d541
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/server-discovery-and-monitoring/standalone-emit-topology-changed-before-close.json
@@ -0,0 +1,97 @@
+{
+ "description": "standalone-emit-topology-description-changed-before-close",
+ "schemaVersion": "1.20",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "single"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Topology lifecycle",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "observeEvents": [
+ "topologyDescriptionChangedEvent",
+ "topologyOpeningEvent",
+ "topologyClosedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 2
+ }
+ },
+ {
+ "name": "close",
+ "object": "client"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "sdam",
+ "ignoreExtraEvents": false,
+ "events": [
+ {
+ "topologyOpeningEvent": {}
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {
+ "type": "Single"
+ }
+ }
+ },
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Single"
+ },
+ "newDescription": {
+ "type": "Unknown"
+ }
+ }
+ },
+ {
+ "topologyClosedEvent": {}
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/sessions/driver-sessions-dirty-session-errors.json b/driver-core/src/test/resources/unified-test-format/sessions/driver-sessions-dirty-session-errors.json
index 361ea83d7b5..6aa1da1df5e 100644
--- a/driver-core/src/test/resources/unified-test-format/sessions/driver-sessions-dirty-session-errors.json
+++ b/driver-core/src/test/resources/unified-test-format/sessions/driver-sessions-dirty-session-errors.json
@@ -11,7 +11,7 @@
{
"minServerVersion": "4.1.8",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions-unsupported-ops.json b/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions-unsupported-ops.json
index 1021b7f2642..c41f74d3370 100644
--- a/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions-unsupported-ops.json
+++ b/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions-unsupported-ops.json
@@ -6,7 +6,7 @@
"minServerVersion": "5.0",
"topologies": [
"replicaset",
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions.json b/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions.json
index 75b577b039f..260f8b6f489 100644
--- a/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions.json
+++ b/driver-core/src/test/resources/unified-test-format/sessions/snapshot-sessions.json
@@ -6,7 +6,7 @@
"minServerVersion": "5.0",
"topologies": [
"replicaset",
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/callback-retry.json b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/callback-retry.json
index 1e07a2a656c..277dfa18ed6 100644
--- a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/callback-retry.json
+++ b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/callback-retry.json
@@ -1,6 +1,6 @@
{
"description": "callback-retry",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.0",
diff --git a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-retry.json b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-retry.json
index 853562e32ea..928f0167e47 100644
--- a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-retry.json
+++ b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-retry.json
@@ -1,6 +1,6 @@
{
"description": "commit-retry",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.0",
diff --git a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror-4.2.json b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror-4.2.json
index 07f190ffb43..0f5a782452c 100644
--- a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror-4.2.json
+++ b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror-4.2.json
@@ -1,6 +1,6 @@
{
"description": "commit-transienttransactionerror-4.2",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.1.6",
diff --git a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror.json b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror.json
index 9584bb61b5b..dd5158d8134 100644
--- a/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror.json
+++ b/driver-core/src/test/resources/unified-test-format/transactions-convenient-api/commit-transienttransactionerror.json
@@ -1,6 +1,6 @@
{
"description": "commit-transienttransactionerror",
- "schemaVersion": "1.3",
+ "schemaVersion": "1.4",
"runOnRequirements": [
{
"minServerVersion": "4.0",
diff --git a/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-errorLabels-forbid_serverless.json b/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-errorLabels-forbid_serverless.json
deleted file mode 100644
index 2a9c44d4b07..00000000000
--- a/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-errorLabels-forbid_serverless.json
+++ /dev/null
@@ -1,351 +0,0 @@
-{
- "description": "retryable-commit-errorLabels-forbid_serverless",
- "schemaVersion": "1.4",
- "runOnRequirements": [
- {
- "minServerVersion": "4.3.1",
- "serverless": "forbid",
- "topologies": [
- "replicaset",
- "sharded",
- "load-balanced"
- ]
- }
- ],
- "createEntities": [
- {
- "client": {
- "id": "client0",
- "useMultipleMongoses": false,
- "observeEvents": [
- "commandStartedEvent"
- ]
- }
- },
- {
- "database": {
- "id": "database0",
- "client": "client0",
- "databaseName": "transaction-tests"
- }
- },
- {
- "collection": {
- "id": "collection0",
- "database": "database0",
- "collectionName": "test"
- }
- },
- {
- "session": {
- "id": "session0",
- "client": "client0"
- }
- },
- {
- "session": {
- "id": "session1",
- "client": "client0"
- }
- }
- ],
- "initialData": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": []
- }
- ],
- "tests": [
- {
- "description": "commitTransaction succeeds after InterruptedAtShutdown",
- "operations": [
- {
- "object": "testRunner",
- "name": "failPoint",
- "arguments": {
- "client": "client0",
- "failPoint": {
- "configureFailPoint": "failCommand",
- "mode": {
- "times": 1
- },
- "data": {
- "failCommands": [
- "commitTransaction"
- ],
- "errorCode": 11600,
- "errorLabels": [
- "RetryableWriteError"
- ],
- "closeConnection": false
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "startTransaction"
- },
- {
- "object": "collection0",
- "name": "insertOne",
- "arguments": {
- "session": "session0",
- "document": {
- "_id": 1
- }
- },
- "expectResult": {
- "$$unsetOrMatches": {
- "insertedId": {
- "$$unsetOrMatches": 1
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "commitTransaction"
- }
- ],
- "expectEvents": [
- {
- "client": "client0",
- "events": [
- {
- "commandStartedEvent": {
- "command": {
- "insert": "test",
- "documents": [
- {
- "_id": 1
- }
- ],
- "ordered": true,
- "readConcern": {
- "$$exists": false
- },
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": true,
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "insert",
- "databaseName": "transaction-tests"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "wtimeout": 10000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- }
- ]
- }
- ],
- "outcome": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
- ]
- },
- {
- "description": "commitTransaction succeeds after ShutdownInProgress",
- "operations": [
- {
- "object": "testRunner",
- "name": "failPoint",
- "arguments": {
- "client": "client0",
- "failPoint": {
- "configureFailPoint": "failCommand",
- "mode": {
- "times": 1
- },
- "data": {
- "failCommands": [
- "commitTransaction"
- ],
- "errorCode": 91,
- "errorLabels": [
- "RetryableWriteError"
- ],
- "closeConnection": false
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "startTransaction"
- },
- {
- "object": "collection0",
- "name": "insertOne",
- "arguments": {
- "session": "session0",
- "document": {
- "_id": 1
- }
- },
- "expectResult": {
- "$$unsetOrMatches": {
- "insertedId": {
- "$$unsetOrMatches": 1
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "commitTransaction"
- }
- ],
- "expectEvents": [
- {
- "client": "client0",
- "events": [
- {
- "commandStartedEvent": {
- "command": {
- "insert": "test",
- "documents": [
- {
- "_id": 1
- }
- ],
- "ordered": true,
- "readConcern": {
- "$$exists": false
- },
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": true,
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "insert",
- "databaseName": "transaction-tests"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "wtimeout": 10000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- }
- ]
- }
- ],
- "outcome": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
- ]
- }
- ]
-}
diff --git a/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-forbid_serverless.json b/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-forbid_serverless.json
deleted file mode 100644
index c99dd816bda..00000000000
--- a/driver-core/src/test/resources/unified-test-format/transactions/retryable-commit-forbid_serverless.json
+++ /dev/null
@@ -1,598 +0,0 @@
-{
- "description": "retryable-commit-forbid_serverless",
- "schemaVersion": "1.4",
- "runOnRequirements": [
- {
- "minServerVersion": "4.0",
- "topologies": [
- "replicaset"
- ]
- },
- {
- "minServerVersion": "4.1.8",
- "serverless": "forbid",
- "topologies": [
- "sharded",
- "load-balanced"
- ]
- }
- ],
- "createEntities": [
- {
- "client": {
- "id": "client0",
- "useMultipleMongoses": false,
- "observeEvents": [
- "commandStartedEvent"
- ]
- }
- },
- {
- "database": {
- "id": "database0",
- "client": "client0",
- "databaseName": "transaction-tests"
- }
- },
- {
- "collection": {
- "id": "collection0",
- "database": "database0",
- "collectionName": "test"
- }
- },
- {
- "session": {
- "id": "session0",
- "client": "client0"
- }
- },
- {
- "client": {
- "id": "client1",
- "useMultipleMongoses": false,
- "uriOptions": {
- "retryWrites": false
- },
- "observeEvents": [
- "commandStartedEvent"
- ]
- }
- },
- {
- "database": {
- "id": "database1",
- "client": "client1",
- "databaseName": "transaction-tests"
- }
- },
- {
- "collection": {
- "id": "collection1",
- "database": "database1",
- "collectionName": "test"
- }
- },
- {
- "session": {
- "id": "session1",
- "client": "client1"
- }
- }
- ],
- "initialData": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": []
- }
- ],
- "tests": [
- {
- "description": "commitTransaction fails after two errors",
- "operations": [
- {
- "object": "testRunner",
- "name": "failPoint",
- "arguments": {
- "client": "client1",
- "failPoint": {
- "configureFailPoint": "failCommand",
- "mode": {
- "times": 2
- },
- "data": {
- "failCommands": [
- "commitTransaction"
- ],
- "closeConnection": true
- }
- }
- }
- },
- {
- "object": "session1",
- "name": "startTransaction"
- },
- {
- "object": "collection1",
- "name": "insertOne",
- "arguments": {
- "session": "session1",
- "document": {
- "_id": 1
- }
- },
- "expectResult": {
- "$$unsetOrMatches": {
- "insertedId": {
- "$$unsetOrMatches": 1
- }
- }
- }
- },
- {
- "object": "session1",
- "name": "commitTransaction",
- "expectError": {
- "errorLabelsContain": [
- "RetryableWriteError",
- "UnknownTransactionCommitResult"
- ],
- "errorLabelsOmit": [
- "TransientTransactionError"
- ]
- }
- },
- {
- "object": "session1",
- "name": "commitTransaction"
- }
- ],
- "expectEvents": [
- {
- "client": "client1",
- "events": [
- {
- "commandStartedEvent": {
- "command": {
- "insert": "test",
- "documents": [
- {
- "_id": 1
- }
- ],
- "ordered": true,
- "readConcern": {
- "$$exists": false
- },
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": true,
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "insert",
- "databaseName": "transaction-tests"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "wtimeout": 10000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "wtimeout": 10000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- }
- ]
- }
- ],
- "outcome": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
- ]
- },
- {
- "description": "commitTransaction applies majority write concern on retries",
- "operations": [
- {
- "object": "testRunner",
- "name": "failPoint",
- "arguments": {
- "client": "client1",
- "failPoint": {
- "configureFailPoint": "failCommand",
- "mode": {
- "times": 2
- },
- "data": {
- "failCommands": [
- "commitTransaction"
- ],
- "closeConnection": true
- }
- }
- }
- },
- {
- "object": "session1",
- "name": "startTransaction",
- "arguments": {
- "writeConcern": {
- "w": 2,
- "journal": true,
- "wtimeoutMS": 5000
- }
- }
- },
- {
- "object": "collection1",
- "name": "insertOne",
- "arguments": {
- "session": "session1",
- "document": {
- "_id": 1
- }
- },
- "expectResult": {
- "$$unsetOrMatches": {
- "insertedId": {
- "$$unsetOrMatches": 1
- }
- }
- }
- },
- {
- "object": "session1",
- "name": "commitTransaction",
- "expectError": {
- "errorLabelsContain": [
- "RetryableWriteError",
- "UnknownTransactionCommitResult"
- ],
- "errorLabelsOmit": [
- "TransientTransactionError"
- ]
- }
- },
- {
- "object": "session1",
- "name": "commitTransaction"
- }
- ],
- "expectEvents": [
- {
- "client": "client1",
- "events": [
- {
- "commandStartedEvent": {
- "command": {
- "insert": "test",
- "documents": [
- {
- "_id": 1
- }
- ],
- "ordered": true,
- "readConcern": {
- "$$exists": false
- },
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": true,
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "insert",
- "databaseName": "transaction-tests"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": 2,
- "j": true,
- "wtimeout": 5000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "j": true,
- "wtimeout": 5000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session1"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "j": true,
- "wtimeout": 5000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- }
- ]
- }
- ],
- "outcome": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
- ]
- },
- {
- "description": "commitTransaction succeeds after connection error",
- "operations": [
- {
- "object": "testRunner",
- "name": "failPoint",
- "arguments": {
- "client": "client0",
- "failPoint": {
- "configureFailPoint": "failCommand",
- "mode": {
- "times": 1
- },
- "data": {
- "failCommands": [
- "commitTransaction"
- ],
- "closeConnection": true
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "startTransaction"
- },
- {
- "object": "collection0",
- "name": "insertOne",
- "arguments": {
- "session": "session0",
- "document": {
- "_id": 1
- }
- },
- "expectResult": {
- "$$unsetOrMatches": {
- "insertedId": {
- "$$unsetOrMatches": 1
- }
- }
- }
- },
- {
- "object": "session0",
- "name": "commitTransaction"
- }
- ],
- "expectEvents": [
- {
- "client": "client0",
- "events": [
- {
- "commandStartedEvent": {
- "command": {
- "insert": "test",
- "documents": [
- {
- "_id": 1
- }
- ],
- "ordered": true,
- "readConcern": {
- "$$exists": false
- },
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": true,
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "insert",
- "databaseName": "transaction-tests"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "$$exists": false
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- },
- {
- "commandStartedEvent": {
- "command": {
- "commitTransaction": 1,
- "lsid": {
- "$$sessionLsid": "session0"
- },
- "txnNumber": {
- "$numberLong": "1"
- },
- "startTransaction": {
- "$$exists": false
- },
- "autocommit": false,
- "writeConcern": {
- "w": "majority",
- "wtimeout": 10000
- }
- },
- "commandName": "commitTransaction",
- "databaseName": "admin"
- }
- }
- ]
- }
- ],
- "outcome": [
- {
- "collectionName": "test",
- "databaseName": "transaction-tests",
- "documents": [
- {
- "_id": 1
- }
- ]
- }
- ]
- }
- ]
-}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_aws_kms_credentials.json b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_aws_kms_credentials.json
new file mode 100644
index 00000000000..e62de800332
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_aws_kms_credentials.json
@@ -0,0 +1,36 @@
+{
+ "description": "kmsProviders-missing_aws_kms_credentials",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "aws": {
+ "accessKeyId": "accessKeyId"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_azure_kms_credentials.json b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_azure_kms_credentials.json
new file mode 100644
index 00000000000..8ef805d0fa6
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_azure_kms_credentials.json
@@ -0,0 +1,36 @@
+{
+ "description": "kmsProviders-missing_azure_kms_credentials",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "azure": {
+ "tenantId": "tenantId"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_gcp_kms_credentials.json b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_gcp_kms_credentials.json
new file mode 100644
index 00000000000..c6da1ce58ca
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-missing_gcp_kms_credentials.json
@@ -0,0 +1,36 @@
+{
+ "description": "kmsProviders-missing_gcp_kms_credentials",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "gcp": {
+ "email": "email"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-no_kms.json b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-no_kms.json
new file mode 100644
index 00000000000..57499b4eaf4
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-fail/kmsProviders-no_kms.json
@@ -0,0 +1,32 @@
+{
+ "description": "clientEncryptionOpts-no_kms",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {}
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/collectionData-createOptions.json b/driver-core/src/test/resources/unified-test-format/valid-pass/collectionData-createOptions.json
new file mode 100644
index 00000000000..19edc2247b0
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/collectionData-createOptions.json
@@ -0,0 +1,79 @@
+{
+ "description": "collectionData-createOptions",
+ "schemaVersion": "1.9",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "3.6",
+ "serverless": "forbid"
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "database0"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "database0",
+ "createOptions": {
+ "capped": true,
+ "size": 4096
+ },
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "collection is created with the correct options",
+ "operations": [
+ {
+ "object": "collection0",
+ "name": "aggregate",
+ "arguments": {
+ "pipeline": [
+ {
+ "$collStats": {
+ "storageStats": {}
+ }
+ },
+ {
+ "$project": {
+ "capped": "$storageStats.capped",
+ "maxSize": "$storageStats.maxSize"
+ }
+ }
+ ]
+ },
+ "expectResult": [
+ {
+ "capped": true,
+ "maxSize": 4096
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/createEntities-operation.json b/driver-core/src/test/resources/unified-test-format/valid-pass/createEntities-operation.json
new file mode 100644
index 00000000000..3fde42919d7
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/createEntities-operation.json
@@ -0,0 +1,74 @@
+{
+ "description": "createEntities-operation",
+ "schemaVersion": "1.9",
+ "tests": [
+ {
+ "description": "createEntities operation",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client1",
+ "observeEvents": [
+ "commandStartedEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database1",
+ "client": "client1",
+ "databaseName": "database1"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection1",
+ "database": "database1",
+ "collectionName": "coll1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "deleteOne",
+ "object": "collection1",
+ "arguments": {
+ "filter": {
+ "_id": 1
+ }
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client1",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "delete": "coll1",
+ "deletes": [
+ {
+ "q": {
+ "_id": 1
+ },
+ "limit": 1
+ }
+ ]
+ },
+ "commandName": "delete",
+ "databaseName": "database1"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/entity-commandCursor.json b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-commandCursor.json
new file mode 100644
index 00000000000..72b74b4a9a8
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-commandCursor.json
@@ -0,0 +1,278 @@
+{
+ "description": "entity-commandCursor",
+ "schemaVersion": "1.3",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client",
+ "useMultipleMongoses": false,
+ "observeEvents": [
+ "commandStartedEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "db",
+ "client": "client",
+ "databaseName": "db"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection",
+ "database": "db",
+ "collectionName": "collection"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "collection",
+ "databaseName": "db",
+ "documents": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ },
+ {
+ "_id": 4,
+ "x": 44
+ },
+ {
+ "_id": 5,
+ "x": 55
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "runCursorCommand creates and exhausts cursor by running getMores",
+ "operations": [
+ {
+ "name": "runCursorCommand",
+ "object": "db",
+ "arguments": {
+ "commandName": "find",
+ "batchSize": 2,
+ "command": {
+ "find": "collection",
+ "filter": {},
+ "batchSize": 2
+ }
+ },
+ "expectResult": [
+ {
+ "_id": 1,
+ "x": 11
+ },
+ {
+ "_id": 2,
+ "x": 22
+ },
+ {
+ "_id": 3,
+ "x": 33
+ },
+ {
+ "_id": 4,
+ "x": 44
+ },
+ {
+ "_id": 5,
+ "x": 55
+ }
+ ]
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "find": "collection",
+ "filter": {},
+ "batchSize": 2,
+ "$db": "db",
+ "lsid": {
+ "$$exists": true
+ }
+ },
+ "commandName": "find"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "command": {
+ "getMore": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "collection": "collection",
+ "$db": "db",
+ "lsid": {
+ "$$exists": true
+ }
+ },
+ "commandName": "getMore"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "command": {
+ "getMore": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "collection": "collection",
+ "$db": "db",
+ "lsid": {
+ "$$exists": true
+ }
+ },
+ "commandName": "getMore"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "description": "createCommandCursor creates a cursor and stores it as an entity that can be iterated one document at a time",
+ "operations": [
+ {
+ "name": "createCommandCursor",
+ "object": "db",
+ "arguments": {
+ "commandName": "find",
+ "batchSize": 2,
+ "command": {
+ "find": "collection",
+ "filter": {},
+ "batchSize": 2
+ }
+ },
+ "saveResultAsEntity": "myRunCommandCursor"
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 1,
+ "x": 11
+ }
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 2,
+ "x": 22
+ }
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 3,
+ "x": 33
+ }
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 4,
+ "x": 44
+ }
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 5,
+ "x": 55
+ }
+ }
+ ]
+ },
+ {
+ "description": "createCommandCursor's cursor can be closed and will perform a killCursors operation",
+ "operations": [
+ {
+ "name": "createCommandCursor",
+ "object": "db",
+ "arguments": {
+ "commandName": "find",
+ "batchSize": 2,
+ "command": {
+ "find": "collection",
+ "filter": {},
+ "batchSize": 2
+ }
+ },
+ "saveResultAsEntity": "myRunCommandCursor"
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "myRunCommandCursor",
+ "expectResult": {
+ "_id": 1,
+ "x": 11
+ }
+ },
+ {
+ "name": "close",
+ "object": "myRunCommandCursor"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "find": "collection",
+ "filter": {},
+ "batchSize": 2,
+ "$db": "db",
+ "lsid": {
+ "$$exists": true
+ }
+ },
+ "commandName": "find"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "command": {
+ "killCursors": "collection",
+ "cursors": {
+ "$$type": "array"
+ }
+ },
+ "commandName": "killCursors"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/entity-cursor-iterateOnce.json b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-cursor-iterateOnce.json
new file mode 100644
index 00000000000..b17ae78b942
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-cursor-iterateOnce.json
@@ -0,0 +1,111 @@
+{
+ "description": "entity-cursor-iterateOnce",
+ "schemaVersion": "1.9",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "database0"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "databaseName": "database0",
+ "collectionName": "coll0",
+ "documents": [
+ {
+ "_id": 1
+ },
+ {
+ "_id": 2
+ },
+ {
+ "_id": 3
+ }
+ ]
+ }
+ ],
+ "tests": [
+ {
+ "description": "iterateOnce",
+ "operations": [
+ {
+ "name": "createFindCursor",
+ "object": "collection0",
+ "arguments": {
+ "filter": {},
+ "batchSize": 2
+ },
+ "saveResultAsEntity": "cursor0"
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "cursor0",
+ "expectResult": {
+ "_id": 1
+ }
+ },
+ {
+ "name": "iterateUntilDocumentOrError",
+ "object": "cursor0",
+ "expectResult": {
+ "_id": 2
+ }
+ },
+ {
+ "name": "iterateOnce",
+ "object": "cursor0"
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "find": "coll0",
+ "filter": {},
+ "batchSize": 2
+ },
+ "commandName": "find",
+ "databaseName": "database0"
+ }
+ },
+ {
+ "commandStartedEvent": {
+ "command": {
+ "getMore": {
+ "$$type": [
+ "int",
+ "long"
+ ]
+ },
+ "collection": "coll0"
+ },
+ "commandName": "getMore"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/entity-find-cursor.json b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-find-cursor.json
index 85b8f69d7f3..6f955d81f4a 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/entity-find-cursor.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/entity-find-cursor.json
@@ -109,7 +109,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"ns": {
"$$type": "string"
@@ -126,7 +129,10 @@
"commandStartedEvent": {
"command": {
"getMore": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"collection": "coll0"
},
@@ -138,7 +144,10 @@
"reply": {
"cursor": {
"id": {
- "$$type": "long"
+ "$$type": [
+ "int",
+ "long"
+ ]
},
"ns": {
"$$type": "string"
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json b/driver-core/src/test/resources/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json
new file mode 100644
index 00000000000..cf7bd60826b
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json
@@ -0,0 +1,68 @@
+{
+ "description": "expectedEventsForClient-topologyDescriptionChangedEvent",
+ "schemaVersion": "1.20",
+ "runOnRequirements": [
+ {
+ "topologies": [
+ "replicaset"
+ ],
+ "minServerVersion": "4.4"
+ }
+ ],
+ "tests": [
+ {
+ "description": "can assert on values of newDescription and previousDescription fields",
+ "operations": [
+ {
+ "name": "createEntities",
+ "object": "testRunner",
+ "arguments": {
+ "entities": [
+ {
+ "client": {
+ "id": "client",
+ "uriOptions": {
+ "directConnection": true
+ },
+ "observeEvents": [
+ "topologyDescriptionChangedEvent"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "waitForEvent",
+ "object": "testRunner",
+ "arguments": {
+ "client": "client",
+ "event": {
+ "topologyDescriptionChangedEvent": {}
+ },
+ "count": 1
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client",
+ "eventType": "sdam",
+ "ignoreExtraEvents": true,
+ "events": [
+ {
+ "topologyDescriptionChangedEvent": {
+ "previousDescription": {
+ "type": "Unknown"
+ },
+ "newDescription": {
+ "type": "Single"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-explicit_kms_credentials.json b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-explicit_kms_credentials.json
new file mode 100644
index 00000000000..7cc74939ebc
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-explicit_kms_credentials.json
@@ -0,0 +1,52 @@
+{
+ "description": "kmsProviders-explicit_kms_credentials",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "aws": {
+ "accessKeyId": "accessKeyId",
+ "secretAccessKey": "secretAccessKey"
+ },
+ "azure": {
+ "tenantId": "tenantId",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ },
+ "gcp": {
+ "email": "email",
+ "privateKey": "cHJpdmF0ZUtleQo="
+ },
+ "kmip": {
+ "endpoint": "endpoint"
+ },
+ "local": {
+ "key": "a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5a2V5"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-mixed_kms_credential_fields.json b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-mixed_kms_credential_fields.json
new file mode 100644
index 00000000000..363f2a45761
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-mixed_kms_credential_fields.json
@@ -0,0 +1,54 @@
+{
+ "description": "kmsProviders-mixed_kms_credential_fields",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "aws": {
+ "accessKeyId": "accessKeyId",
+ "secretAccessKey": {
+ "$$placeholder": 1
+ }
+ },
+ "azure": {
+ "tenantId": "tenantId",
+ "clientId": {
+ "$$placeholder": 1
+ },
+ "clientSecret": {
+ "$$placeholder": 1
+ }
+ },
+ "gcp": {
+ "email": "email",
+ "privateKey": {
+ "$$placeholder": 1
+ }
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-placeholder_kms_credentials.json b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-placeholder_kms_credentials.json
new file mode 100644
index 00000000000..3f7721f01d5
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-placeholder_kms_credentials.json
@@ -0,0 +1,70 @@
+{
+ "description": "kmsProviders-placeholder_kms_credentials",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "aws": {
+ "accessKeyId": {
+ "$$placeholder": 1
+ },
+ "secretAccessKey": {
+ "$$placeholder": 1
+ }
+ },
+ "azure": {
+ "tenantId": {
+ "$$placeholder": 1
+ },
+ "clientId": {
+ "$$placeholder": 1
+ },
+ "clientSecret": {
+ "$$placeholder": 1
+ }
+ },
+ "gcp": {
+ "email": {
+ "$$placeholder": 1
+ },
+ "privateKey": {
+ "$$placeholder": 1
+ }
+ },
+ "kmip": {
+ "endpoint": {
+ "$$placeholder": 1
+ }
+ },
+ "local": {
+ "key": {
+ "$$placeholder": 1
+ }
+ }
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-unconfigured_kms.json b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-unconfigured_kms.json
new file mode 100644
index 00000000000..12ca580941b
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/kmsProviders-unconfigured_kms.json
@@ -0,0 +1,39 @@
+{
+ "description": "kmsProviders-unconfigured_kms",
+ "schemaVersion": "1.8",
+ "runOnRequirements": [
+ {
+ "csfle": true
+ }
+ ],
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0"
+ }
+ },
+ {
+ "clientEncryption": {
+ "id": "clientEncryption0",
+ "clientEncryptionOpts": {
+ "keyVaultClient": "client0",
+ "keyVaultNamespace": "keyvault.datakeys",
+ "kmsProviders": {
+ "aws": {},
+ "azure": {},
+ "gcp": {},
+ "kmip": {},
+ "local": {}
+ }
+ }
+ }
+ }
+ ],
+ "tests": [
+ {
+ "description": "",
+ "skipReason": "DRIVERS-2280: waiting on driver support for on-demand credentials",
+ "operations": []
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/matches-lte-operator.json b/driver-core/src/test/resources/unified-test-format/valid-pass/matches-lte-operator.json
new file mode 100644
index 00000000000..4de65c58387
--- /dev/null
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/matches-lte-operator.json
@@ -0,0 +1,78 @@
+{
+ "description": "matches-lte-operator",
+ "schemaVersion": "1.9",
+ "createEntities": [
+ {
+ "client": {
+ "id": "client0",
+ "observeEvents": [
+ "commandStartedEvent"
+ ]
+ }
+ },
+ {
+ "database": {
+ "id": "database0",
+ "client": "client0",
+ "databaseName": "database0Name"
+ }
+ },
+ {
+ "collection": {
+ "id": "collection0",
+ "database": "database0",
+ "collectionName": "coll0"
+ }
+ }
+ ],
+ "initialData": [
+ {
+ "collectionName": "coll0",
+ "databaseName": "database0Name",
+ "documents": []
+ }
+ ],
+ "tests": [
+ {
+ "description": "special lte matching operator",
+ "operations": [
+ {
+ "name": "insertOne",
+ "object": "collection0",
+ "arguments": {
+ "document": {
+ "_id": 1,
+ "y": 1
+ }
+ }
+ }
+ ],
+ "expectEvents": [
+ {
+ "client": "client0",
+ "events": [
+ {
+ "commandStartedEvent": {
+ "command": {
+ "insert": "coll0",
+ "documents": [
+ {
+ "_id": {
+ "$$lte": 1
+ },
+ "y": {
+ "$$lte": 2
+ }
+ }
+ ]
+ },
+ "commandName": "insert",
+ "databaseName": "database0Name"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-crud.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-crud.json
index 0790d9b789f..94e4ec56829 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-crud.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-crud.json
@@ -322,7 +322,7 @@
"minServerVersion": "4.1.0",
"topologies": [
"replicaset",
- "sharded-replicaset"
+ "sharded"
],
"serverless": "forbid"
}
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-retryable-writes.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-retryable-writes.json
index 50160799f33..f19aa3f9d87 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-retryable-writes.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-retryable-writes.json
@@ -1,14 +1,6 @@
{
"description": "poc-retryable-writes",
"schemaVersion": "1.0",
- "runOnRequirements": [
- {
- "minServerVersion": "3.6",
- "topologies": [
- "replicaset"
- ]
- }
- ],
"createEntities": [
{
"client": {
@@ -79,6 +71,14 @@
"tests": [
{
"description": "FindOneAndUpdate is committed on first attempt",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "3.6",
+ "topologies": [
+ "replicaset"
+ ]
+ }
+ ],
"operations": [
{
"name": "failPoint",
@@ -132,6 +132,14 @@
},
{
"description": "FindOneAndUpdate is not committed on first attempt",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "3.6",
+ "topologies": [
+ "replicaset"
+ ]
+ }
+ ],
"operations": [
{
"name": "failPoint",
@@ -188,6 +196,14 @@
},
{
"description": "FindOneAndUpdate is never committed",
+ "runOnRequirements": [
+ {
+ "minServerVersion": "3.6",
+ "topologies": [
+ "replicaset"
+ ]
+ }
+ ],
"operations": [
{
"name": "failPoint",
@@ -245,15 +261,10 @@
"description": "InsertMany succeeds after PrimarySteppedDown",
"runOnRequirements": [
{
- "minServerVersion": "4.0",
- "topologies": [
- "replicaset"
- ]
- },
- {
- "minServerVersion": "4.1.7",
+ "minServerVersion": "4.3.1",
"topologies": [
- "sharded-replicaset"
+ "replicaset",
+ "sharded"
]
}
],
@@ -345,7 +356,7 @@
{
"minServerVersion": "4.1.7",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
@@ -406,15 +417,10 @@
"description": "InsertOne fails after multiple retryable writeConcernErrors",
"runOnRequirements": [
{
- "minServerVersion": "4.0",
- "topologies": [
- "replicaset"
- ]
- },
- {
- "minServerVersion": "4.1.7",
+ "minServerVersion": "4.3.1",
"topologies": [
- "sharded-replicaset"
+ "replicaset",
+ "sharded"
]
}
],
@@ -433,6 +439,9 @@
"failCommands": [
"insert"
],
+ "errorLabels": [
+ "RetryableWriteError"
+ ],
"writeConcernError": {
"code": 91,
"errmsg": "Replication is being shut down"
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-sessions.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-sessions.json
index 75f34894286..117c9e7d009 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-sessions.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-sessions.json
@@ -264,7 +264,7 @@
{
"minServerVersion": "4.1.8",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-convenient-api.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-convenient-api.json
index 820ed659276..9ab44a9c548 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-convenient-api.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-convenient-api.json
@@ -11,7 +11,7 @@
{
"minServerVersion": "4.1.8",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-mongos-pin-auto.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-mongos-pin-auto.json
index a0b297d59a5..de08edec442 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-mongos-pin-auto.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions-mongos-pin-auto.json
@@ -5,7 +5,7 @@
{
"minServerVersion": "4.1.8",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions.json b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions.json
index 0355ca20605..2055a3b7057 100644
--- a/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions.json
+++ b/driver-core/src/test/resources/unified-test-format/valid-pass/poc-transactions.json
@@ -11,7 +11,7 @@
{
"minServerVersion": "4.1.8",
"topologies": [
- "sharded-replicaset"
+ "sharded"
]
}
],
@@ -93,7 +93,7 @@
"minServerVersion": "4.3.4",
"topologies": [
"replicaset",
- "sharded-replicaset"
+ "sharded"
]
}
],
@@ -203,7 +203,7 @@
"minServerVersion": "4.3.4",
"topologies": [
"replicaset",
- "sharded-replicaset"
+ "sharded"
]
}
],
diff --git a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy
index 21df76e401e..3af81fc992c 100644
--- a/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/client/model/AggregatesSpecification.groovy
@@ -23,6 +23,7 @@ import com.mongodb.client.model.search.SearchOperator
import org.bson.BsonDocument
import org.bson.BsonInt32
import org.bson.Document
+import org.bson.Vector
import org.bson.conversions.Bson
import spock.lang.IgnoreIf
import spock.lang.Specification
@@ -855,7 +856,7 @@ class AggregatesSpecification extends Specification {
BsonDocument vectorSearchDoc = toBson(
vectorSearch(
fieldPath('fieldName').multi('ignored'),
- [1.0d, 2.0d],
+ vector,
'indexName',
1,
approximateVectorSearchOptions(2)
@@ -868,13 +869,20 @@ class AggregatesSpecification extends Specification {
vectorSearchDoc == parse('''{
"$vectorSearch": {
"path": "fieldName",
- "queryVector": [1.0, 2.0],
+ "queryVector": ''' + queryVector + ''',
"index": "indexName",
"numCandidates": {"$numberLong": "2"},
"limit": {"$numberLong": "1"},
"filter": {"fieldName": {"$ne": "fieldValue"}}
}
}''')
+
+ where:
+ vector | queryVector
+ Vector.int8Vector(new byte[]{127, 7}) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}'
+ Vector.floatVector(new float[]{127.0f, 7.0f}) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}'
+ Vector.packedBitVector(new byte[]{127, 7}, (byte) 0) | '{"$binary": {"base64": "EAB/Bw==", "subType": "09"}}'
+ [1.0d, 2.0d] | "[1.0, 2.0]"
}
def 'should render exact $vectorSearch'() {
@@ -882,7 +890,7 @@ class AggregatesSpecification extends Specification {
BsonDocument vectorSearchDoc = toBson(
vectorSearch(
fieldPath('fieldName').multi('ignored'),
- [1.0d, 2.0d],
+ vector,
'indexName',
1,
exactVectorSearchOptions()
@@ -895,13 +903,19 @@ class AggregatesSpecification extends Specification {
vectorSearchDoc == parse('''{
"$vectorSearch": {
"path": "fieldName",
- "queryVector": [1.0, 2.0],
+ "queryVector": ''' + queryVector + ''',
"index": "indexName",
"exact": true,
"limit": {"$numberLong": "1"},
"filter": {"fieldName": {"$ne": "fieldValue"}}
}
}''')
+
+ where:
+ vector | queryVector
+ Vector.int8Vector(new byte[]{127, 7}) | '{"$binary": {"base64": "AwB/Bw==", "subType": "09"}}'
+ Vector.floatVector(new float[]{127.0f, 7.0f}) | '{"$binary": {"base64": "JwAAAP5CAADgQA==", "subType": "09"}}'
+ [1.0d, 2.0d] | "[1.0, 2.0]"
}
def 'should create string representation for simple stages'() {
diff --git a/driver-core/src/test/unit/com/mongodb/connection/ClusterSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/connection/ClusterSettingsSpecification.groovy
index 9898d0f0569..36da5c61e2d 100644
--- a/driver-core/src/test/unit/com/mongodb/connection/ClusterSettingsSpecification.groovy
+++ b/driver-core/src/test/unit/com/mongodb/connection/ClusterSettingsSpecification.groovy
@@ -180,16 +180,6 @@ class ClusterSettingsSpecification extends Specification {
thrown(IllegalArgumentException)
}
- def 'when srvHost contains less than three parts (host, domain, top-level domain, should throw IllegalArgumentException'() {
- when:
- def builder = ClusterSettings.builder()
- builder.srvHost('foo.bar')
- builder.build()
-
- then:
- thrown(IllegalArgumentException)
- }
-
def 'when connection string is applied to builder, all properties should be set'() {
when:
def settings = ClusterSettings.builder()
diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java
new file mode 100644
index 00000000000..27ed86e7b63
--- /dev/null
+++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 com.mongodb.internal.connection;
+
+import com.mongodb.ClusterFixture;
+import com.mongodb.MongoException;
+import com.mongodb.ServerAddress;
+import com.mongodb.connection.ClusterConnectionMode;
+import com.mongodb.connection.ClusterId;
+import com.mongodb.connection.ClusterSettings;
+import com.mongodb.connection.ClusterType;
+import com.mongodb.connection.ServerSettings;
+import com.mongodb.internal.dns.DefaultDnsResolver;
+import com.mongodb.internal.dns.DnsResolver;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static java.util.Collections.singletonList;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * See https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/tests/README.md
+ */
+class InitialDnsSeedListDiscoveryProseTest {
+ private static final String SRV_SERVICE_NAME = "mongodb";
+
+ private DnsMultiServerCluster cluster;
+
+ @AfterEach
+ void tearDown() {
+ if (cluster != null) {
+ cluster.close();
+ }
+ }
+
+ @ParameterizedTest(name = "mongodb+srv://{0} => {1}")
+ @CsvSource({
+ "localhost, test.mongo.localhost",
+ "mongo.local, test.driver.mongo.local"
+ })
+ @DisplayName("1. Allow SRVs with fewer than 3 '.' separated parts")
+ void testAllowSRVsWithFewerThanThreeParts(final String srvHost, final String resolvedHost) {
+ doTest(srvHost, resolvedHost, false);
+ }
+
+ @ParameterizedTest(name = "mongodb+srv://{0} => {1}")
+ @CsvSource({
+ "localhost, localhost.mongodb",
+ "mongo.local, test_1.evil.local",
+ "blogs.mongodb.com, blogs.evil.com"
+ })
+ @DisplayName("2. Throw when return address does not end with SRV domain")
+ void testThrowWhenReturnAddressDoesnotEndWithSRVDomain(final String srvHost, final String resolvedHost) {
+ doTest(srvHost, resolvedHost, true);
+ }
+
+ @ParameterizedTest(name = "mongodb+srv://{0} => {1}")
+ @CsvSource({
+ "localhost, localhost",
+ "mongo.local, mongo.local"
+ })
+ @DisplayName("3. Throw when return address is identical to SRV hostname and the SRV hostname has fewer than three `.` separated parts")
+ void testThrowWhenReturnAddressIsIdenticalToSRVHostname(final String srvHost, final String resolvedHost) {
+ doTest(srvHost, resolvedHost, true);
+ }
+
+ @ParameterizedTest(name = "mongodb+srv://{0} => {1}")
+ @CsvSource({
+ "localhost, test_1.cluster_1localhost",
+ "mongo.local, test_1.my_hostmongo.local",
+ "blogs.mongodb.com, cluster.testmongodb.com"
+ })
+ @DisplayName("4. Throw when return address does not contain '.' separating shared part of domain")
+ void testThrowWhenReturnAddressDoesnotContainSharedPartOfDomain(final String srvHost, final String resolvedHost) {
+ doTest(srvHost, resolvedHost, true);
+ }
+
+ private void doTest(final String srvHost, final String resolvedHost, final boolean throwException) {
+ final ClusterId clusterId = new ClusterId();
+
+ final DnsResolver dnsResolver = new DefaultDnsResolver((name, type) -> singletonList(String.format("10 5 27017 %s",
+ resolvedHost)));
+
+ final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory = mock(DnsSrvRecordMonitorFactory.class);
+ when(dnsSrvRecordMonitorFactory.create(eq(srvHost), eq(SRV_SERVICE_NAME), any(DnsSrvRecordInitializer.class))).thenAnswer(
+ invocation -> new DefaultDnsSrvRecordMonitor(srvHost, SRV_SERVICE_NAME, 10, 10,
+ invocation.getArgument(2), clusterId, dnsResolver));
+
+ final ClusterSettings.Builder settingsBuilder = ClusterSettings.builder()
+ .mode(ClusterConnectionMode.MULTIPLE)
+ .requiredClusterType(ClusterType.SHARDED)
+ .srvHost(srvHost);
+
+ final ClusterableServerFactory serverFactory = mock(ClusterableServerFactory.class);
+ when(serverFactory.getSettings()).thenReturn(ServerSettings.builder().build());
+ when(serverFactory.create(any(Cluster.class), any(ServerAddress.class))).thenReturn(mock(ClusterableServer.class));
+
+ cluster = new DnsMultiServerCluster(clusterId, settingsBuilder.build(),
+ serverFactory,
+ dnsSrvRecordMonitorFactory);
+
+ ClusterFixture.sleep(100);
+
+ final MongoException mongoException = cluster.getSrvResolutionException();
+ if (throwException) {
+ Assertions.assertNotNull(mongoException);
+ } else {
+ Assertions.assertNull(mongoException);
+ }
+ }
+}
+
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ChangeStreamsTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ChangeStreamsTest.java
index 5a48dc343af..db5537b12d2 100644
--- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ChangeStreamsTest.java
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ChangeStreamsTest.java
@@ -48,6 +48,12 @@ final class ChangeStreamsTest extends UnifiedReactiveStreamsTest {
"Test that comment is not set on getMore - pre 4.4"
);
+ private static final List TESTS_WITH_EXTRA_EVENTS =
+ Arrays.asList(
+ "Test with document comment",
+ "Test with string comment"
+ );
+
private static final List REQUIRES_BATCH_CURSOR_CREATION_WAITING =
Arrays.asList(
"Change Stream should error when an invalid aggregation stage is passed in",
@@ -59,6 +65,7 @@ final class ChangeStreamsTest extends UnifiedReactiveStreamsTest {
protected void skips(final String fileDescription, final String testDescription) {
assumeFalse(ERROR_REQUIRED_FROM_CHANGE_STREAM_INITIALIZATION_TESTS.contains(testDescription));
assumeFalse(EVENT_SENSITIVE_TESTS.contains(testDescription));
+ assumeFalse(TESTS_WITH_EXTRA_EVENTS.contains(testDescription));
}
@BeforeEach
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
index 5b12ba14de9..7301a21b372 100644
--- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
@@ -29,6 +29,6 @@ private static Collection data() throws URISyntaxException, IOExcepti
@Override
protected void skips(final String fileDescription, final String testDescription) {
- com.mongodb.client.unified.UnifiedServerDiscoveryAndMonitoringTest.doSkips(getDefinition());
+ com.mongodb.client.unified.UnifiedServerDiscoveryAndMonitoringTest.doSkips(fileDescription, testDescription);
}
}
diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java
new file mode 100644
index 00000000000..f5b8e63f8c3
--- /dev/null
+++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/vector/VectorFunctionalTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 com.mongodb.reactivestreams.client.vector;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.vector.AbstractVectorFunctionalTest;
+import com.mongodb.reactivestreams.client.MongoClients;
+import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;
+
+public class VectorFunctionalTest extends AbstractVectorFunctionalTest {
+ @Override
+ protected MongoClient getMongoClient(final MongoClientSettings settings) {
+ return new SyncMongoClient(MongoClients.create(settings));
+ }
+}
diff --git a/driver-scala/build.gradle b/driver-scala/build.gradle
index 4490ed39538..e9e9e15040e 100644
--- a/driver-scala/build.gradle
+++ b/driver-scala/build.gradle
@@ -19,8 +19,8 @@ archivesBaseName = 'mongo-scala-driver'
dependencies {
- implementation project(path: ':bson-scala', configuration: 'default')
- implementation project(path: ':driver-reactive-streams', configuration: 'default')
+ api project(path: ':bson-scala', configuration: 'default')
+ api project(path: ':driver-reactive-streams', configuration: 'default')
compileOnly 'com.google.code.findbugs:jsr305:1.3.9'
testImplementation project(':driver-sync')
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudTest.java
index 5c494452823..22c5a5e3807 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudTest.java
@@ -43,6 +43,22 @@ public static void doSkips(final String fileDescription, final String testDescri
assumeFalse(testDescription.equals("Aggregate with $out includes read preference for 5.0+ server"));
assumeFalse(testDescription.equals("Database-level aggregate with $out includes read preference for 5.0+ server"));
}
+ if (fileDescription.equals("updateOne-sort")) {
+ assumeFalse(testDescription.equals("UpdateOne with sort option"), "Skipping until JAVA-5622 is implemented");
+ assumeFalse(testDescription.equals("updateOne with sort option unsupported (server-side error)"), "Skipping until JAVA-5622 is implemented");
+ }
+ if (fileDescription.equals("replaceOne-sort")) {
+ assumeFalse(testDescription.equals("ReplaceOne with sort option"), "Skipping until JAVA-5622 is implemented");
+ assumeFalse(testDescription.equals("replaceOne with sort option unsupported (server-side error)"), "Skipping until JAVA-5622 is implemented");
+ }
+ if (fileDescription.equals("BulkWrite updateOne-sort")) {
+ assumeFalse(testDescription.equals("BulkWrite updateOne with sort option"), "Skipping until JAVA-5622 is implemented");
+ assumeFalse(testDescription.equals("BulkWrite updateOne with sort option unsupported (server-side error)"), "Skipping until JAVA-5622 is implemented");
+ }
+ if (fileDescription.equals("BulkWrite replaceOne-sort")) {
+ assumeFalse(testDescription.equals("BulkWrite replaceOne with sort option"), "Skipping until JAVA-5622 is implemented");
+ assumeFalse(testDescription.equals("BulkWrite replaceOne with sort option unsupported (server-side error)"), "Skipping until JAVA-5622 is implemented");
+ }
}
@Override
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableReadsTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableReadsTest.java
index 62712eaab0e..4099191556d 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableReadsTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableReadsTest.java
@@ -39,6 +39,11 @@ public static void doSkips(final String fileDescription, @SuppressWarnings("unus
assumeFalse(fileDescription.equals("listDatabaseObjects-serverErrors"));
assumeFalse(fileDescription.equals("listCollectionObjects"));
assumeFalse(fileDescription.equals("listCollectionObjects-serverErrors"));
+
+ // JAVA-5224
+ assumeFalse(fileDescription.equals("ReadConcernMajorityNotAvailableYet is a retryable read")
+ && testDescription.equals("Find succeeds on second attempt after ReadConcernMajorityNotAvailableYet"),
+ "JAVA-5224");
}
private static Collection data() throws URISyntaxException, IOException {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableWritesTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableWritesTest.java
index 794a027ebaf..ffd44441c81 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableWritesTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedRetryableWritesTest.java
@@ -41,6 +41,8 @@ public static void doSkips(final String description) {
if (isDiscoverableReplicaSet() && serverVersionLessThan(4, 4)) {
assumeFalse(description.equals("RetryableWriteError label is added based on writeConcernError in pre-4.4 mongod response"));
}
+ assumeFalse(description.contains("client bulkWrite"), "JAVA-4586");
+ assumeFalse(description.contains("client.clientBulkWrite"), "JAVA-4586");
}
private static Collection data() throws URISyntaxException, IOException {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
index c384a50967e..fbdc5f4ea3e 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedServerDiscoveryAndMonitoringTest.java
@@ -16,8 +16,6 @@
package com.mongodb.client.unified;
-import org.bson.BsonDocument;
-import org.bson.BsonString;
import org.junit.jupiter.params.provider.Arguments;
import java.io.IOException;
@@ -33,14 +31,30 @@ private static Collection data() throws URISyntaxException, IOExcepti
@Override
protected void skips(final String fileDescription, final String testDescription) {
- doSkips(getDefinition());
+ doSkips(fileDescription, testDescription);
}
- public static void doSkips(final BsonDocument definition) {
- String description = definition.getString("description", new BsonString("")).getValue();
- assumeFalse(description.equals("connect with serverMonitoringMode=auto >=4.4"),
+ public static void doSkips(final String fileDescription, final String testDescription) {
+ assumeFalse(testDescription.equals("connect with serverMonitoringMode=auto >=4.4"),
"Skipping because our server monitoring events behave differently for now");
- assumeFalse(description.equals("connect with serverMonitoringMode=stream >=4.4"),
+ assumeFalse(testDescription.equals("connect with serverMonitoringMode=stream >=4.4"),
"Skipping because our server monitoring events behave differently for now");
+
+ assumeFalse(fileDescription.equals("standalone-logging"), "Skipping until JAVA-4770 is implemented");
+ assumeFalse(fileDescription.equals("replicaset-logging"), "Skipping until JAVA-4770 is implemented");
+ assumeFalse(fileDescription.equals("sharded-logging"), "Skipping until JAVA-4770 is implemented");
+ assumeFalse(fileDescription.equals("loadbalanced-logging"), "Skipping until JAVA-4770 is implemented");
+
+ assumeFalse(fileDescription.equals("standalone-emit-topology-description-changed-before-close"),
+ "Skipping until JAVA-5229 is implemented");
+ assumeFalse(fileDescription.equals("replicaset-emit-topology-description-changed-before-close"),
+ "Skipping until JAVA-5229 is implemented");
+ assumeFalse(fileDescription.equals("sharded-emit-topology-description-changed-before-close"),
+ "Skipping until JAVA-5229 is implemented");
+ assumeFalse(fileDescription.equals("loadbalanced-emit-topology-description-changed-before-close"),
+ "Skipping until JAVA-5229 is implemented");
+
+ assumeFalse(testDescription.equals("poll waits after successful heartbeat"), "Skipping until JAVA-5564 is implemented");
+ assumeFalse(fileDescription.equals("interruptInUse"), "Skipping until JAVA-4536 is implemented");
}
}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestValidator.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestValidator.java
index ecb04294bf8..0626ab89e09 100644
--- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestValidator.java
+++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestValidator.java
@@ -31,6 +31,13 @@ protected void skips(final String fileDescription, final String testDescription)
assumeFalse(testDescription.equals("InsertOne fails after multiple retryable writeConcernErrors") && serverVersionLessThan(4, 4),
"MongoDB releases prior to 4.4 incorrectly add errorLabels as a field within the writeConcernError document "
+ "instead of as a top-level field. Rather than handle that in code, we skip the test on older server versions.");
+ // Feature to be implemented in scope of JAVA-5389
+ assumeFalse(fileDescription.equals("expectedEventsForClient-topologyDescriptionChangedEvent"));
+ // Feature to be implemented in scope JAVA-4862
+ assumeFalse(fileDescription.equals("entity-commandCursor"));
+ // To be investigated in JAVA-5631
+ assumeFalse(fileDescription.equals("kmsProviders-explicit_kms_credentials"));
+ assumeFalse(fileDescription.equals("kmsProviders-mixed_kms_credential_fields"));
}
private static Collection data() throws URISyntaxException, IOException {
diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java
new file mode 100644
index 00000000000..c3edf6983da
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/vector/AbstractVectorFunctionalTest.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 com.mongodb.client.vector;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.ReadConcern;
+import com.mongodb.ReadPreference;
+import com.mongodb.WriteConcern;
+import com.mongodb.client.Fixture;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.model.OperationTest;
+import org.bson.BsonBinary;
+import org.bson.BsonBinarySubType;
+import org.bson.BsonInvalidOperationException;
+import org.bson.Document;
+import org.bson.Float32Vector;
+import org.bson.Int8Vector;
+import org.bson.PackedBitVector;
+import org.bson.Vector;
+import org.bson.codecs.configuration.CodecRegistry;
+import org.bson.codecs.pojo.PojoCodecProvider;
+import org.bson.types.Binary;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
+import static org.bson.Vector.DataType.FLOAT32;
+import static org.bson.Vector.DataType.INT8;
+import static org.bson.Vector.DataType.PACKED_BIT;
+import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
+import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public abstract class AbstractVectorFunctionalTest extends OperationTest {
+
+ private static final byte VECTOR_SUBTYPE = BsonBinarySubType.VECTOR.getValue();
+ private static final String FIELD_VECTOR = "vector";
+ private static final CodecRegistry CODEC_REGISTRY = fromRegistries(getDefaultCodecRegistry(),
+ fromProviders(PojoCodecProvider
+ .builder()
+ .automatic(true).build()));
+ private MongoCollection documentCollection;
+
+ private MongoClient mongoClient;
+
+ @BeforeEach
+ public void setUp() {
+ super.beforeEach();
+ mongoClient = getMongoClient(getMongoClientSettingsBuilder()
+ .codecRegistry(CODEC_REGISTRY)
+ .build());
+ documentCollection = mongoClient
+ .getDatabase(getDatabaseName())
+ .getCollection(getCollectionName());
+ }
+
+ @AfterEach
+ @SuppressWarnings("try")
+ public void afterEach() {
+ try (MongoClient ignore = mongoClient) {
+ super.afterEach();
+ }
+ }
+
+ private static MongoClientSettings.Builder getMongoClientSettingsBuilder() {
+ return Fixture.getMongoClientSettingsBuilder()
+ .readConcern(ReadConcern.MAJORITY)
+ .writeConcern(WriteConcern.MAJORITY)
+ .readPreference(ReadPreference.primary());
+ }
+
+ protected abstract MongoClient getMongoClient(MongoClientSettings settings);
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1, 2, 3, 4, 5, 6, 7, 8})
+ void shouldThrowExceptionForInvalidPackedBitArrayPaddingWhenDecodeEmptyVector(final byte invalidPadding) {
+ //given
+ Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding});
+ documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector));
+
+ // when & then
+ BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> {
+ findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+ });
+ assertEquals("Padding must be 0 if vector is empty, but found: " + invalidPadding, exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1})
+ void shouldThrowExceptionForInvalidFloat32Padding(final byte invalidPadding) {
+ // given
+ Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{FLOAT32.getValue(), invalidPadding, 10, 20, 30, 40});
+ documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector));
+
+ // when & then
+ BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> {
+ findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+ });
+ assertEquals("Padding must be 0 for FLOAT32 data type, but found: " + invalidPadding, exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 1})
+ void shouldThrowExceptionForInvalidInt8Padding(final byte invalidPadding) {
+ // given
+ Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{INT8.getValue(), invalidPadding, 10, 20, 30, 40});
+ documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector));
+
+ // when & then
+ BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> {
+ findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+ });
+ assertEquals("Padding must be 0 for INT8 data type, but found: " + invalidPadding, exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = {-1, 8})
+ void shouldThrowExceptionForInvalidPackedBitPadding(final byte invalidPadding) {
+ // given
+ Binary invalidVector = new Binary(VECTOR_SUBTYPE, new byte[]{PACKED_BIT.getValue(), invalidPadding, 10, 20, 30, 40});
+ documentCollection.insertOne(new Document(FIELD_VECTOR, invalidVector));
+
+ // when & then
+ BsonInvalidOperationException exception = Assertions.assertThrows(BsonInvalidOperationException.class, ()-> {
+ findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+ });
+ assertEquals("Padding must be between 0 and 7 bits, but found: " + invalidPadding, exception.getMessage());
+ }
+
+ private static Stream provideValidVectors() {
+ return Stream.of(
+ Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f}),
+ Vector.int8Vector(new byte[]{10, 20, 30, 40}),
+ Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideValidVectors")
+ void shouldStoreAndRetrieveValidVector(final Vector expectedVector) {
+ // Given
+ Document documentToInsert = new Document(FIELD_VECTOR, expectedVector)
+ .append("otherField", 1); // to test that the next field is not affected
+ documentCollection.insertOne(documentToInsert);
+
+ // when & then
+ Vector actualVector = findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+
+ assertEquals(expectedVector, actualVector);
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideValidVectors")
+ void shouldStoreAndRetrieveValidVectorWithBsonBinary(final Vector expectedVector) {
+ // Given
+ Document documentToInsert = new Document(FIELD_VECTOR, new BsonBinary(expectedVector));
+ documentCollection.insertOne(documentToInsert);
+
+ // when & then
+ Vector actualVector = findExactlyOne(documentCollection)
+ .get(FIELD_VECTOR, Vector.class);
+
+ assertEquals(actualVector, actualVector);
+ }
+
+ @Test
+ void shouldStoreAndRetrieveValidVectorWithFloatVectorPojo() {
+ // given
+ MongoCollection floatVectorPojoMongoCollection = mongoClient
+ .getDatabase(getDatabaseName())
+ .getCollection(getCollectionName()).withDocumentClass(FloatVectorPojo.class);
+ Float32Vector vector = Vector.floatVector(new float[]{1.1f, 2.2f, 3.3f});
+
+ // whe
+ floatVectorPojoMongoCollection.insertOne(new FloatVectorPojo(vector));
+ FloatVectorPojo floatVectorPojo = floatVectorPojoMongoCollection.find().first();
+
+ // then
+ Assertions.assertNotNull(floatVectorPojo);
+ assertEquals(vector, floatVectorPojo.getVector());
+ }
+
+ @Test
+ void shouldStoreAndRetrieveValidVectorWithInt8VectorPojo() {
+ // given
+ MongoCollection floatVectorPojoMongoCollection = mongoClient
+ .getDatabase(getDatabaseName())
+ .getCollection(getCollectionName()).withDocumentClass(Int8VectorPojo.class);
+ Int8Vector vector = Vector.int8Vector(new byte[]{10, 20, 30, 40});
+
+ // when
+ floatVectorPojoMongoCollection.insertOne(new Int8VectorPojo(vector));
+ Int8VectorPojo int8VectorPojo = floatVectorPojoMongoCollection.find().first();
+
+ // then
+ Assertions.assertNotNull(int8VectorPojo);
+ assertEquals(vector, int8VectorPojo.getVector());
+ }
+
+ @Test
+ void shouldStoreAndRetrieveValidVectorWithPackedBitVectorPojo() {
+ // given
+ MongoCollection floatVectorPojoMongoCollection = mongoClient
+ .getDatabase(getDatabaseName())
+ .getCollection(getCollectionName()).withDocumentClass(PackedBitVectorPojo.class);
+
+ PackedBitVector vector = Vector.packedBitVector(new byte[]{(byte) 0b10101010, (byte) 0b01010101}, (byte) 3);
+
+ // when
+ floatVectorPojoMongoCollection.insertOne(new PackedBitVectorPojo(vector));
+ PackedBitVectorPojo packedBitVectorPojo = floatVectorPojoMongoCollection.find().first();
+
+ // then
+ Assertions.assertNotNull(packedBitVectorPojo);
+ assertEquals(vector, packedBitVectorPojo.getVector());
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideValidVectors")
+ void shouldStoreAndRetrieveValidVectorWithGenericVectorPojo(final Vector actualVector) {
+ // given
+ MongoCollection floatVectorPojoMongoCollection = mongoClient
+ .getDatabase(getDatabaseName())
+ .getCollection(getCollectionName()).withDocumentClass(VectorPojo.class);
+
+ // when
+ floatVectorPojoMongoCollection.insertOne(new VectorPojo(actualVector));
+ VectorPojo vectorPojo = floatVectorPojoMongoCollection.find().first();
+
+ //then
+ Assertions.assertNotNull(vectorPojo);
+ assertEquals(actualVector, vectorPojo.getVector());
+ }
+
+ private Document findExactlyOne(final MongoCollection collection) {
+ List documents = new ArrayList<>();
+ collection.find().into(documents);
+ assertEquals(1, documents.size(), "Expected exactly one document, but found: " + documents.size());
+ return documents.get(0);
+ }
+
+ public static class VectorPojo {
+ private Vector vector;
+
+ public VectorPojo() {
+ }
+
+ public VectorPojo(final Vector vector) {
+ this.vector = vector;
+ }
+
+ public Vector getVector() {
+ return vector;
+ }
+
+ public void setVector(final Vector vector) {
+ this.vector = vector;
+ }
+ }
+
+ public static class Int8VectorPojo {
+ private Int8Vector vector;
+
+ public Int8VectorPojo() {
+ }
+
+ public Int8VectorPojo(final Int8Vector vector) {
+ this.vector = vector;
+ }
+
+ public Vector getVector() {
+ return vector;
+ }
+
+ public void setVector(final Int8Vector vector) {
+ this.vector = vector;
+ }
+ }
+
+ public static class PackedBitVectorPojo {
+ private PackedBitVector vector;
+
+ public PackedBitVectorPojo() {
+ }
+
+ public PackedBitVectorPojo(final PackedBitVector vector) {
+ this.vector = vector;
+ }
+
+ public Vector getVector() {
+ return vector;
+ }
+
+ public void setVector(final PackedBitVector vector) {
+ this.vector = vector;
+ }
+ }
+
+ public static class FloatVectorPojo {
+ private Float32Vector vector;
+
+ public FloatVectorPojo() {
+ }
+
+ public FloatVectorPojo(final Float32Vector vector) {
+ this.vector = vector;
+ }
+
+ public Vector getVector() {
+ return vector;
+ }
+
+ public void setVector(final Float32Vector vector) {
+ this.vector = vector;
+ }
+ }
+}
diff --git a/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java b/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java
new file mode 100644
index 00000000000..63d756a8f35
--- /dev/null
+++ b/driver-sync/src/test/functional/com/mongodb/client/vector/VectorFunctionalTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008-present MongoDB, Inc.
+ *
+ * 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 com.mongodb.client.vector;
+
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+
+public class VectorFunctionalTest extends AbstractVectorFunctionalTest {
+ @Override
+ protected MongoClient getMongoClient(final MongoClientSettings settings) {
+ return MongoClients.create(settings);
+ }
+}
diff --git a/graalvm-native-image-app/build.gradle b/graalvm-native-image-app/build.gradle
index 713b8c29a1a..b3d7335f9d9 100644
--- a/graalvm-native-image-app/build.gradle
+++ b/graalvm-native-image-app/build.gradle
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+// Note requires a Gradle project flag `-PincludeGraalvm` (see settings.gradle).
+
plugins {
id 'application'
id 'org.graalvm.buildtools.native' version '0.9.23'
diff --git a/graalvm-native-image-app/readme.md b/graalvm-native-image-app/readme.md
index a659b7d1c07..c47a9829851 100644
--- a/graalvm-native-image-app/readme.md
+++ b/graalvm-native-image-app/readme.md
@@ -47,12 +47,12 @@ you need to inform Gradle about that location as specified in https://docs.gradl
Assuming that your MongoDB deployment is accessible at `mongodb://localhost:27017`,
run from the driver project root directory:
-| # | Command | Description |
-|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
-| 0 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeCompile` | Build the application relying on the reachability metadata stored in `graalvm-native-image-app/src/main/resources/META-INF/native-image`. |
-| 1 | `env JAVA_HOME="${JDK17}" ./gradlew clean && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PjavaVersion=21 -Pagent :graalvm-native-image-app:run && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew :graalvm-native-image-app:metadataCopy` | Collect the reachability metadata and update the files storing it. Do this before building the application only if building fails otherwise. |
-| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. |
-| 3 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeRun` | Run the application using Gradle, build it if necessary relying on the stored reachability metadata. |
+| # | Command | Description |
+|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
+| 0 | `env JAVA_HOME="${JDK17}" ./gradlew -PincludeGraalvm -PjavaVersion=21 :graalvm-native-image-app:nativeCompile` | Build the application relying on the reachability metadata stored in `graalvm-native-image-app/src/main/resources/META-INF/native-image`. |
+| 1 | `env JAVA_HOME="${JDK17}" ./gradlew clean && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PincludeGraalvm -PjavaVersion=21 -Pagent :graalvm-native-image-app:run && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PincludeGraalvm :graalvm-native-image-app:metadataCopy` | Collect the reachability metadata and update the files storing it. Do this before building the application only if building fails otherwise. |
+| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. |
+| 3 | `env JAVA_HOME="${JDK17}" ./gradlew -PincludeGraalvm -PjavaVersion=21 :graalvm-native-image-app:nativeRun` | Run the application using Gradle, build it if necessary relying on the stored reachability metadata. |
#### Specifying a custom connection string
diff --git a/gradle.properties b/gradle.properties
index e31c63fbd62..12f1750c442 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,8 +16,8 @@
org.gradle.daemon=true
org.gradle.jvmargs=-Duser.country=US -Duser.language=en
-scalaVersions=2.11.12,2.12.15,2.13.6
-defaultScalaVersions=2.13.6
+scalaVersions=2.11.12,2.12.20,2.13.15
+defaultScalaVersions=2.13.15
runOnceTasks=clean,release
org.gradle.java.installations.auto-download=false
org.gradle.java.installations.fromEnv=JDK8,JDK11,JDK17,JDK21,JDK21_GRAALVM
diff --git a/gradle/javaToolchain.gradle b/gradle/javaToolchain.gradle
index 187b143eea6..f1c779dab33 100644
--- a/gradle/javaToolchain.gradle
+++ b/gradle/javaToolchain.gradle
@@ -80,7 +80,7 @@ allprojects {
options.encoding = "UTF-8"
options.release.set(17)
}
- } else if (project == project(':graalvm-native-image-app')) {
+ } else if (project.name == 'graalvm-native-image-app') {
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.release.set(DEFAULT_JDK_VERSION)
diff --git a/gradle/javadoc.gradle b/gradle/javadoc.gradle
index 8d425b693b8..b986747c647 100644
--- a/gradle/javadoc.gradle
+++ b/gradle/javadoc.gradle
@@ -17,8 +17,9 @@ import static org.gradle.util.CollectionUtils.single
*/
-def projectsThatDoNotPublishJavaDocs = project(":util").allprojects + project(":driver-benchmarks") + project("driver-workload-executor") + project("driver-lambda") + project(":graalvm-native-image-app")
-def javaMainProjects = subprojects - projectsThatDoNotPublishJavaDocs
+def projectNamesThatDoNotPublishJavaDocs =["driver-benchmarks", "driver-lambda", "driver-workload-executor", "graalvm-native-image-app", "util",
+ "spock", "taglets"]
+def javaMainProjects = subprojects.findAll { !projectNamesThatDoNotPublishJavaDocs.contains(it.name) }
task docs {
dependsOn javaMainProjects.collect { it.tasks.withType(Javadoc) + it.tasks.withType(ScalaDoc) }
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index f72773c5ad7..9add25f9261 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -72,8 +72,10 @@ ext {
}
}
-def projectsNotPublishedToMaven = project(":util").allprojects + project(":driver-benchmarks") + project("driver-workload-executor") + project("driver-lambda") + project(":graalvm-native-image-app")
-def publishedProjects = subprojects - projectsNotPublishedToMaven
+
+def projectNamesNotToBePublished = ["driver-benchmarks", "driver-lambda", "driver-workload-executor", "graalvm-native-image-app", "util",
+ "spock", "taglets"]
+def publishedProjects = subprojects.findAll { !projectNamesNotToBePublished.contains(it.name) }
def scalaProjects = publishedProjects.findAll { it.name.contains('scala') }
def javaProjects = publishedProjects - scalaProjects
def projectsWithManifest = publishedProjects.findAll {it.name != 'driver-legacy' }
diff --git a/settings.gradle b/settings.gradle
index b1c5e185d37..4ebbb10c4e0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -32,4 +32,7 @@ include ':driver-scala'
include ':mongodb-crypt'
include 'util:spock'
include 'util:taglets'
-include ':graalvm-native-image-app'
+
+if(hasProperty("includeGraalvm")) {
+ include ':graalvm-native-image-app'
+}
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy