From 719599eda93c1c9f092f4bd53ca47384c4345210 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 17 Aug 2026 07:03:26 -0500 Subject: [PATCH] PYTHON-6036 Vendor the OpenTelemetry unified spec tests Wire observeTracingMessages and expectTracingMessages into the unified test format runner and vendor the spec's tracing fixtures, plus a resync-specs.sh entry to refresh them. Span attributes are plain Python primitives rather than the BSON-decoded documents the generic match evaluator compares against, so MatchEvaluatorUtil.match_span_attributes adapts them: ints widen to Int64 so the $$type "long" alias matches, and db.mongodb.lsid is rebuilt from its UUID string into the document shape the sessionLsid operator expects. The getMore fixture is held back for the change that adds getMore spans; the other 22 do not exercise getMore. --- .evergreen/resync-specs.sh | 3 + .../test_open_telemetry_unified.py | 40 ++ test/asynchronous/unified_format.py | 146 +++++ test/open_telemetry/operation/aggregate.json | 134 +++++ .../operation/atlas_search.json | 324 +++++++++++ test/open_telemetry/operation/bulk_write.json | 333 +++++++++++ test/open_telemetry/operation/count.json | 123 ++++ .../operation/create_collection.json | 190 ++++++ .../operation/create_indexes.json | 134 +++++ test/open_telemetry/operation/delete.json | 139 +++++ test/open_telemetry/operation/distinct.json | 127 ++++ .../operation/drop_collection.json | 129 +++++ .../operation/drop_indexes.json | 152 +++++ test/open_telemetry/operation/find.json | 291 ++++++++++ .../operation/find_and_modify.json | 150 +++++ .../operation/find_without_query_text.json | 119 ++++ test/open_telemetry/operation/insert.json | 146 +++++ .../operation/list_collections.json | 108 ++++ .../operation/list_databases.json | 104 ++++ .../operation/list_indexes.json | 118 ++++ test/open_telemetry/operation/map_reduce.json | 177 ++++++ test/open_telemetry/operation/retries.json | 209 +++++++ test/open_telemetry/operation/update.json | 153 +++++ .../transaction/convenient.json | 326 +++++++++++ test/open_telemetry/transaction/core_api.json | 545 ++++++++++++++++++ test/test_open_telemetry_unified.py | 40 ++ test/unified_format.py | 146 +++++ test/unified_format_shared.py | 39 +- 28 files changed, 4644 insertions(+), 1 deletion(-) create mode 100644 test/asynchronous/test_open_telemetry_unified.py create mode 100644 test/open_telemetry/operation/aggregate.json create mode 100644 test/open_telemetry/operation/atlas_search.json create mode 100644 test/open_telemetry/operation/bulk_write.json create mode 100644 test/open_telemetry/operation/count.json create mode 100644 test/open_telemetry/operation/create_collection.json create mode 100644 test/open_telemetry/operation/create_indexes.json create mode 100644 test/open_telemetry/operation/delete.json create mode 100644 test/open_telemetry/operation/distinct.json create mode 100644 test/open_telemetry/operation/drop_collection.json create mode 100644 test/open_telemetry/operation/drop_indexes.json create mode 100644 test/open_telemetry/operation/find.json create mode 100644 test/open_telemetry/operation/find_and_modify.json create mode 100644 test/open_telemetry/operation/find_without_query_text.json create mode 100644 test/open_telemetry/operation/insert.json create mode 100644 test/open_telemetry/operation/list_collections.json create mode 100644 test/open_telemetry/operation/list_databases.json create mode 100644 test/open_telemetry/operation/list_indexes.json create mode 100644 test/open_telemetry/operation/map_reduce.json create mode 100644 test/open_telemetry/operation/retries.json create mode 100644 test/open_telemetry/operation/update.json create mode 100644 test/open_telemetry/transaction/convenient.json create mode 100644 test/open_telemetry/transaction/core_api.json create mode 100644 test/test_open_telemetry_unified.py diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 6a2133cb64..984b16d8e6 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -127,6 +127,9 @@ do cpjson command-logging-and-monitoring/tests/logging command_logging cpjson command-logging-and-monitoring/tests/monitoring command_monitoring ;; + open-telemetry|otel|open_telemetry) + cpjson open-telemetry/tests open_telemetry + ;; crud|CRUD) cpjson crud/tests/ crud ;; diff --git a/test/asynchronous/test_open_telemetry_unified.py b/test/asynchronous/test_open_telemetry_unified.py new file mode 100644 index 0000000000..ffba03d0dc --- /dev/null +++ b/test/asynchronous/test_open_telemetry_unified.py @@ -0,0 +1,40 @@ +# Copyright 2026-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. + +"""Run the OpenTelemetry unified format spec tests.""" + +from __future__ import annotations + +import sys + +sys.path[0:0] = [""] + +import pytest + +from test import unittest +from test.asynchronous.unified_format import generate_test_classes, get_test_path + +_IS_SYNC = False + +pytestmark = pytest.mark.otel + +globals().update( + generate_test_classes( + get_test_path("open_telemetry"), + module=__name__, + ) +) + +if __name__ == "__main__": + unittest.main() diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 5e6e24e9c4..ae81c11c88 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -38,6 +38,7 @@ import pytest import pymongo +import pymongo._otel as _otel from bson import SON, json_util from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.objectid import ObjectId @@ -93,6 +94,7 @@ PLACEHOLDER_MAP, EventListenerUtil, MatchEvaluatorUtil, + _shared_test_provider, coerce_result, parse_bulk_write_error_result, parse_bulk_write_result, @@ -113,6 +115,16 @@ _IS_SYNC = False +_HAS_OTEL_TEST_DEPS = False +if _otel._HAS_OPENTELEMETRY: + try: + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter + + _HAS_OTEL_TEST_DEPS = True + except ImportError: + pass + IS_INTERRUPTED = False @@ -230,6 +242,11 @@ def __init__(self, test_class): self._entities: dict[str, Any] = {} self._listeners: dict[str, EventListenerUtil] = {} self._session_lsids: dict[str, Mapping[str, Any]] = {} + # The id of the (at most one, today) client entity created with + # observeTracingMessages. Spans carry no attribute identifying which + # client emitted them, so multi-client tracing correlation isn't + # supported; _create_entity fails loudly if a second one appears. + self._tracing_client_id: Optional[str] = None self.test: UnifiedSpecTestMixinV1 = test_class def __contains__(self, item): @@ -311,6 +328,25 @@ async def _create_entity(self, entity_spec, uri=None): ) self._listeners[spec["id"]] = listener kwargs["event_listeners"] = [listener] + + observe_tracing = spec.get("observeTracingMessages") + if observe_tracing is not None: + if self._tracing_client_id is not None: + self.test.fail( + "Multiple clients with observeTracingMessages are not supported " + f"by the unified test format runner (already tracking " + f"{self._tracing_client_id!r}, got {spec['id']!r})" + ) + self._tracing_client_id = spec["id"] + enable_payload = observe_tracing.get("enableCommandPayload", False) + kwargs["tracing"] = { + "enabled": True, + # Tests asserting db.query.text match the full, untruncated + # command, so an effectively-unlimited length avoids + # truncating and failing that assertion. + "query_text_max_length": 1_000_000 if enable_payload else None, + } + if spec.get("useMultipleMongoses"): if async_client_context.load_balancer: kwargs["h"] = async_client_context.MULTI_MONGOS_LB_URI @@ -482,6 +518,8 @@ class UnifiedSpecTestMixinV1(AsyncIntegrationTest): TEST_SPEC: Any TEST_PATH = "" # This gets filled in by generate_test_classes mongos_clients: list[AsyncMongoClient] = [] + # Set in setUpClass, only for test files that use observeTracingMessages. + _tracing_exporter: Optional[Any] = None @staticmethod async def should_run_on(run_on_spec): @@ -526,6 +564,23 @@ async def insert_initial_data(self, initial_data): @classmethod def setUpClass(cls) -> None: + # Only register a span exporter (and the shared SDK TracerProvider it + # depends on) for test files that actually use observeTracingMessages, + # to avoid needlessly accumulating span processors on the process-wide + # provider for the (vast majority of) unified-format suites that don't. + cls._tracing_exporter = None + uses_tracing = any( + "observeTracingMessages" in entity.get("client", {}) + for entity in cls.TEST_SPEC.get("createEntities", []) + ) + if uses_tracing: + if not _HAS_OTEL_TEST_DEPS: + raise unittest.SkipTest( + "observeTracingMessages requires opentelemetry-sdk to be installed" + ) + cls._tracing_exporter = InMemorySpanExporter() + _shared_test_provider().add_span_processor(SimpleSpanProcessor(cls._tracing_exporter)) + # Speed up the tests by decreasing the heartbeat frequency. cls.knobs = client_knobs( heartbeat_frequency=0.1, @@ -538,6 +593,14 @@ def setUpClass(cls) -> None: @classmethod def tearDownClass(cls) -> None: cls.knobs.disable() + # The exporter's span processor can never be removed from the shared process-wide + # TracerProvider (see _shared_test_provider), so without this, every span emitted by any + # client anywhere in the process for the rest of the test run keeps getting appended to this + # (otherwise dead) class's exporter: an unbounded memory leak across a full test run, and + # needless per-span export overhead for every other tracing-enabled test class that runs + # afterwards. shutdown() makes further export() calls into this exporter no-ops. + if cls._tracing_exporter is not None: + cls._tracing_exporter.shutdown() async def asyncSetUp(self): # super call creates internal client cls.client @@ -576,6 +639,14 @@ def maybe_skip_test(self, spec): self.skipTest("PyMongo does not support the symbol type") if "timeoutms applied to entire download" in description: self.skipTest("PyMongo's open_download_stream does not cap the stream's lifetime") + # Removed API: PyMongo no longer exposes map_reduce/inline_map_reduce at + # all (mapReduce is deprecated server-side), so there's no code path left + # that could send this command; this operation can never be exercised. + if class_name == "testoperationmapreduce" and description == "mapreduce": + self.skipTest( + "PyMongo removed the map_reduce/inline_map_reduce Collection methods " + "(mapReduce is deprecated server-side); this operation cannot be exercised" + ) if any( x in description for x in [ @@ -1463,6 +1534,77 @@ def format_logs(log_list): self.match_evaluator.match_result(expected_data, actual_data) self.match_evaluator.match_result(expected_msg, actual_msg) + async def check_tracing_messages(self, operations, spec): + # Like expectLogMessages/expectEvents, expectTracingMessages is a list of + # per-client blocks (even though only one client with + # observeTracingMessages is currently supported, see entity.py above). + exporter = self._tracing_exporter + if exporter is None: + self.fail( + "expectTracingMessages requires a client entity created with observeTracingMessages" + ) + + exporter.clear() + await self.run_operations(operations) + finished_spans = exporter.get_finished_spans() + + # Reconstruct the parent/child span tree from the flat, finish-ordered + # list the in-memory exporter records, keyed by each span's parent id. + children_by_parent_id = defaultdict(list) + for span in finished_spans: + parent_id = span.parent.span_id if span.parent is not None else None + children_by_parent_id[parent_id].append(span) + + def check_span_list(expected_list, actual_list, ignore_extra_spans): + if ignore_extra_spans: + # Per the unified-test-format spec, "additional unexpected spans + # are allowed". Unlike ignoreExtraEvents (which only tolerates + # a trailing tail), spans from concurrent/out-of-band activity + # (e.g. a testRunner-issued configureFailPoint command) can + # finish interleaved anywhere among the expected ones, not just + # at the end. Filter down to just the spans that line up (by + # name, in order) with the expected list, dropping anything + # else, instead of naively truncating the tail. + filtered = [] + expected_iter = iter(expected_list) + current_expected = next(expected_iter, None) + for actual in actual_list: + if current_expected is not None and actual.name == current_expected["name"]: + filtered.append(actual) + current_expected = next(expected_iter, None) + actual_list = filtered + self.assertEqual( + len(expected_list), + len(actual_list), + f"expected spans {[e['name'] for e in expected_list]} but got " + f"{[a.name for a in actual_list]}", + ) + for expected, actual in zip(expected_list, actual_list): + self.assertEqual(expected["name"], actual.name) + self.match_evaluator.match_span_attributes( + expected["attributes"], actual.attributes + ) + expected_nested = expected.get("nested") + if expected_nested is not None: + actual_children = children_by_parent_id[actual.context.span_id] + check_span_list(expected_nested, actual_children, ignore_extra_spans) + + for client_spec in spec: + expected_client_id = client_spec["client"] + tracing_client_id = self.entity_map._tracing_client_id + self.assertEqual( + expected_client_id, + tracing_client_id, + f"expectTracingMessages.client {expected_client_id!r} does not match the " + f"client with observeTracingMessages enabled ({tracing_client_id!r})", + ) + + ignore_extra_spans = client_spec.get("ignoreExtraSpans", False) + expected_spans = client_spec["spans"] + self.assertTrue(expected_spans, "expectTracingMessages spans must be non-empty") + + check_span_list(expected_spans, children_by_parent_id[None], ignore_extra_spans) + async def verify_outcome(self, spec): for collection_data in spec: coll_name = collection_data["collectionName"] @@ -1551,6 +1693,10 @@ async def _run_scenario(self, spec, uri=None): expect_log_messages = spec["expectLogMessages"] self.assertTrue(expect_log_messages, "expectEvents must be non-empty") await self.check_log_messages(spec["operations"], expect_log_messages) + elif "expectTracingMessages" in spec: + expect_tracing_messages = spec["expectTracingMessages"] + self.assertTrue(expect_tracing_messages, "expectTracingMessages must be non-empty") + await self.check_tracing_messages(spec["operations"], expect_tracing_messages) else: # process operations await self.run_operations(spec["operations"]) diff --git a/test/open_telemetry/operation/aggregate.json b/test/open_telemetry/operation/aggregate.json new file mode 100644 index 0000000000..ad8e189b11 --- /dev/null +++ b/test/open_telemetry/operation/aggregate.json @@ -0,0 +1,134 @@ +{ + "description": "operation aggregate", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-aggregate" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-aggregate", + "documents": [] + } + ], + "tests": [ + { + "description": "aggregation", + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "aggregate operation-aggregate.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.operation.name": "aggregate", + "db.operation.summary": "aggregate operation-aggregate.test" + }, + "nested": [ + { + "name": "aggregate", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.command.name": "aggregate", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "aggregate operation-aggregate.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/atlas_search.json b/test/open_telemetry/operation/atlas_search.json new file mode 100644 index 0000000000..b787a7308e --- /dev/null +++ b/test/open_telemetry/operation/atlas_search.json @@ -0,0 +1,324 @@ +{ + "description": "operation atlas_search", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-atlas-search" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-atlas-search", + "documents": [] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.5", + "maxServerVersion": "7.0.99", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + }, + { + "minServerVersion": "7.2.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "atlas search indexes", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + }, + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + }, + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "createSearchIndexes operation-atlas-search.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "createSearchIndexes", + "db.operation.summary": "createSearchIndexes operation-atlas-search.test" + }, + "nested": [ + { + "name": "createSearchIndexes", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.command.name": "createSearchIndexes", + "db.collection.name": "test", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": true + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "exception.message": { + "$$exists": true + }, + "exception.type": { + "$$exists": true + }, + "exception.stacktrace": { + "$$exists": true + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "createSearchIndexes operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "createSearchIndexes": "test", + "indexes": [ + { + "type": "search", + "definition": { + "mappings": { + "dynamic": true + } + } + } + ] + } + } + } + } + } + ] + }, + { + "name": "updateSearchIndex operation-atlas-search.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "updateSearchIndex", + "db.operation.summary": "updateSearchIndex operation-atlas-search.test" + }, + "nested": [ + { + "name": "updateSearchIndex", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.command.name": "updateSearchIndex", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": true + }, + "exception.message": { + "$$exists": true + }, + "exception.type": { + "$$exists": true + }, + "exception.stacktrace": { + "$$exists": true + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "updateSearchIndex operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "updateSearchIndex": "test", + "name": "test index", + "definition": {} + } + } + } + } + } + ] + }, + { + "name": "dropSearchIndex operation-atlas-search.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "dropSearchIndex", + "db.operation.summary": "dropSearchIndex operation-atlas-search.test" + }, + "nested": [ + { + "name": "dropSearchIndex", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-atlas-search", + "db.command.name": "dropSearchIndex", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": true + }, + "exception.message": { + "$$exists": true + }, + "exception.type": { + "$$exists": true + }, + "exception.stacktrace": { + "$$exists": true + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "dropSearchIndex operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "dropSearchIndex": "test", + "name": "test index" + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/bulk_write.json b/test/open_telemetry/operation/bulk_write.json new file mode 100644 index 0000000000..312a55f383 --- /dev/null +++ b/test/open_telemetry/operation/bulk_write.json @@ -0,0 +1,333 @@ +{ + "description": "operation bulk_write", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-bulk-write-0" + } + }, + { + "database": { + "id": "database1", + "client": "client0", + "databaseName": "operation-bulk-write-1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test1" + } + } + ], + "initialData": [ + { + "collectionName": "test0", + "databaseName": "operation-bulk-write-0", + "documents": [] + }, + { + "collectionName": "test1", + "databaseName": "operation-bulk-write-1", + "documents": [] + } + ], + "_yamlAnchors": { + "namespace0": "operation-bulk-write-0.test0", + "namespace1": "operation-bulk-write-1.test1" + }, + "tests": [ + { + "description": "bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "operation-bulk-write-0.test0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "operation-bulk-write-0.test0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "operation-bulk-write-0.test0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ] + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "bulkWrite admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "bulkWrite", + "db.operation.summary": "bulkWrite admin" + }, + "nested": [ + { + "name": "bulkWrite", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "bulkWrite", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "bulkWrite admin", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "multi": false, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + } + }, + { + "update": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + } + }, + { + "update": 1, + "multi": false, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true + }, + { + "delete": 0, + "multi": false, + "filter": { + "_id": 5 + } + }, + { + "delete": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + ], + "nsInfo": [ + { + "ns": "operation-bulk-write-0.test0" + }, + { + "ns": "operation-bulk-write-1.test1" + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/count.json b/test/open_telemetry/operation/count.json new file mode 100644 index 0000000000..97a12bda29 --- /dev/null +++ b/test/open_telemetry/operation/count.json @@ -0,0 +1,123 @@ +{ + "description": "operation count", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-count" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-count", + "documents": [] + } + ], + "tests": [ + { + "description": "estimated document count", + "operations": [ + { + "object": "collection0", + "name": "estimatedDocumentCount", + "arguments": {}, + "expectResult": 0 + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "count operation-count.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-count", + "db.collection.name": "test", + "db.operation.name": "count", + "db.operation.summary": "count operation-count.test" + }, + "nested": [ + { + "name": "count", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-count", + "db.collection.name": "test", + "db.command.name": "count", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "count operation-count.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "count": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/create_collection.json b/test/open_telemetry/operation/create_collection.json new file mode 100644 index 0000000000..8522e198aa --- /dev/null +++ b/test/open_telemetry/operation/create_collection.json @@ -0,0 +1,190 @@ +{ + "description": "operation create collection", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-create-collection" + } + } + ], + "initialData": [ + { + "collectionName": "newlyCreatedCollection", + "databaseName": "operation-create-collection", + "documents": [] + } + ], + "tests": [ + { + "description": "create collection", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "newlyCreatedCollection" + } + }, + { + "object": "database0", + "name": "createCollection", + "arguments": { + "collection": "newlyCreatedCollection" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "dropCollection operation-create-collection.newlyCreatedCollection", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.operation.name": "dropCollection", + "db.operation.summary": "dropCollection operation-create-collection.newlyCreatedCollection" + }, + "nested": [ + { + "name": "drop", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.command.name": "drop", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "drop operation-create-collection.newlyCreatedCollection", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "drop": "newlyCreatedCollection" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + }, + { + "name": "createCollection operation-create-collection.newlyCreatedCollection", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.operation.name": "createCollection", + "db.operation.summary": "createCollection operation-create-collection.newlyCreatedCollection" + }, + "nested": [ + { + "name": "create", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.command.name": "create", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "create operation-create-collection.newlyCreatedCollection", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "create": "newlyCreatedCollection" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/create_indexes.json b/test/open_telemetry/operation/create_indexes.json new file mode 100644 index 0000000000..d62d47a9a8 --- /dev/null +++ b/test/open_telemetry/operation/create_indexes.json @@ -0,0 +1,134 @@ +{ + "description": "operation create_indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-create-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-create-indexes", + "documents": [] + } + ], + "tests": [ + { + "description": "create indexes", + "operations": [ + { + "object": "collection0", + "name": "createIndex", + "arguments": { + "keys": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "createIndexes operation-create-indexes.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-indexes", + "db.collection.name": "test", + "db.operation.name": "createIndexes", + "db.operation.summary": "createIndexes operation-create-indexes.test" + }, + "nested": [ + { + "name": "createIndexes", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-create-indexes", + "db.collection.name": "test", + "db.command.name": "createIndexes", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "createIndexes operation-create-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "createIndexes": "test", + "indexes": [ + { + "key": { + "x": 1 + }, + "name": "x_1" + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/delete.json b/test/open_telemetry/operation/delete.json new file mode 100644 index 0000000000..70d6babc26 --- /dev/null +++ b/test/open_telemetry/operation/delete.json @@ -0,0 +1,139 @@ +{ + "description": "operation delete", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-delete" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-delete", + "documents": [] + } + ], + "tests": [ + { + "description": "delete elements", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "delete operation-delete.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-delete", + "db.collection.name": "test", + "db.operation.name": "delete", + "db.operation.summary": "delete operation-delete.test" + }, + "nested": [ + { + "name": "delete", + "attributes": { + "db.system.name": "mongodb", + "db.command.name": "delete", + "db.namespace": "operation-delete", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "delete operation-delete.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "delete": "test", + "ordered": true, + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "limit": 0 + } + ] + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/distinct.json b/test/open_telemetry/operation/distinct.json new file mode 100644 index 0000000000..6fdabc682d --- /dev/null +++ b/test/open_telemetry/operation/distinct.json @@ -0,0 +1,127 @@ +{ + "description": "operation distinct", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-distinct" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-distinct", + "documents": [] + } + ], + "tests": [ + { + "description": "distinct on a field", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": {} + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "distinct operation-distinct.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-distinct", + "db.collection.name": "test", + "db.operation.name": "distinct", + "db.operation.summary": "distinct operation-distinct.test" + }, + "nested": [ + { + "name": "distinct", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-distinct", + "db.collection.name": "test", + "db.command.name": "distinct", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "distinct operation-distinct.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "distinct": "test", + "key": "x", + "query": {} + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/drop_collection.json b/test/open_telemetry/operation/drop_collection.json new file mode 100644 index 0000000000..4dea1252fc --- /dev/null +++ b/test/open_telemetry/operation/drop_collection.json @@ -0,0 +1,129 @@ +{ + "description": "operation drop collection", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "7.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-drop-collection" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-drop-collection", + "documents": [] + } + ], + "tests": [ + { + "description": "drop collection", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "dropCollection operation-drop-collection.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-drop-collection", + "db.collection.name": "test", + "db.operation.name": "dropCollection", + "db.operation.summary": "dropCollection operation-drop-collection.test" + }, + "nested": [ + { + "name": "drop", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-drop-collection", + "db.collection.name": "test", + "db.command.name": "drop", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "drop operation-drop-collection.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "drop": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/drop_indexes.json b/test/open_telemetry/operation/drop_indexes.json new file mode 100644 index 0000000000..e8f20fbb14 --- /dev/null +++ b/test/open_telemetry/operation/drop_indexes.json @@ -0,0 +1,152 @@ +{ + "description": "operation drop indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-drop-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "client": { + "id": "clientWithoutTracing", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "databaseWithoutTracing", + "client": "clientWithoutTracing", + "databaseName": "operation-drop-indexes" + } + }, + { + "collection": { + "id": "collectionWithoutTracing", + "database": "databaseWithoutTracing", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-drop-indexes", + "documents": [] + } + ], + "tests": [ + { + "description": "drop indexes", + "operations": [ + { + "name": "createIndex", + "object": "collectionWithoutTracing", + "arguments": { + "keys": { + "x": 1 + }, + "name": "x_1" + } + }, + { + "name": "dropIndexes", + "object": "collection0" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "dropIndexes operation-drop-indexes.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-drop-indexes", + "db.collection.name": "test", + "db.operation.name": "dropIndexes", + "db.operation.summary": "dropIndexes operation-drop-indexes.test" + }, + "nested": [ + { + "name": "dropIndexes", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-drop-indexes", + "db.collection.name": "test", + "db.command.name": "dropIndexes", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "dropIndexes operation-drop-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "dropIndexes": "test", + "index": "*" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/find.json b/test/open_telemetry/operation/find.json new file mode 100644 index 0000000000..555a972b9b --- /dev/null +++ b/test/open_telemetry/operation/find.json @@ -0,0 +1,291 @@ +{ + "description": "operation find", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + }, + { + "description": "find an element retrying failed command", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": "89", + "exception.message": { + "$$type": "string" + }, + "exception.type": { + "$$type": "string" + }, + "exception.stacktrace": { + "$$type": "string" + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/find_and_modify.json b/test/open_telemetry/operation/find_and_modify.json new file mode 100644 index 0000000000..291f1b6167 --- /dev/null +++ b/test/open_telemetry/operation/find_and_modify.json @@ -0,0 +1,150 @@ +{ + "description": "operation find_one_and_update", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find-one-and-update" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find-one-and-update", + "documents": [] + } + ], + "tests": [ + { + "description": "findOneAndUpdate", + "runOnRequirements": [ + { + "minServerVersion": "4.4" + } + ], + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$set": { + "x": 5 + } + } + ], + "comment": "comment" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "findAndModify operation-find-one-and-update.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find-one-and-update", + "db.collection.name": "test", + "db.operation.name": "findAndModify", + "db.operation.summary": "findAndModify operation-find-one-and-update.test" + }, + "nested": [ + { + "name": "findAndModify", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find-one-and-update", + "db.collection.name": "test", + "db.command.name": "findAndModify", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "findAndModify operation-find-one-and-update.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": [ + { + "$set": { + "x": 5 + } + } + ], + "comment": "comment" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/find_without_query_text.json b/test/open_telemetry/operation/find_without_query_text.json new file mode 100644 index 0000000000..0a2464ba60 --- /dev/null +++ b/test/open_telemetry/operation/find_without_query_text.json @@ -0,0 +1,119 @@ +{ + "description": "operation find without db.query.text", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": false + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$exists": false + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/insert.json b/test/open_telemetry/operation/insert.json new file mode 100644 index 0000000000..6ab0450357 --- /dev/null +++ b/test/open_telemetry/operation/insert.json @@ -0,0 +1,146 @@ +{ + "description": "operation insert", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-insert" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-insert", + "documents": [] + } + ], + "tests": [ + { + "description": "insert one element", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "insert operation-insert.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-insert", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert operation-insert.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system.name": "mongodb", + "db.command.name": "insert", + "db.namespace": "operation-insert", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "insert operation-insert.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": { + "$$unsetOrMatches": 1 + }, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "operation-insert", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/list_collections.json b/test/open_telemetry/operation/list_collections.json new file mode 100644 index 0000000000..e3a28dd36a --- /dev/null +++ b/test/open_telemetry/operation/list_collections.json @@ -0,0 +1,108 @@ +{ + "description": "operation list_collections", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-list-collections" + } + } + ], + "tests": [ + { + "description": "List collections", + "operations": [ + { + "object": "database0", + "name": "listCollections" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listCollections operation-list-collections", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-list-collections", + "db.operation.name": "listCollections", + "db.operation.summary": "listCollections operation-list-collections", + "db.collection.name": { + "$$exists": false + } + }, + "nested": [ + { + "name": "listCollections", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-list-collections", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "listCollections", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listCollections operation-list-collections", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listCollections": 1 + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/list_databases.json b/test/open_telemetry/operation/list_databases.json new file mode 100644 index 0000000000..ae86b87073 --- /dev/null +++ b/test/open_telemetry/operation/list_databases.json @@ -0,0 +1,104 @@ +{ + "description": "operation list_databases", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + } + ], + "tests": [ + { + "description": "list databases", + "operations": [ + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listDatabases admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.operation.name": "listDatabases", + "db.operation.summary": "listDatabases admin", + "db.collection.name": { + "$$exists": false + } + }, + "nested": [ + { + "name": "listDatabases", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "listDatabases", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listDatabases admin", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listDatabases": 1 + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/list_indexes.json b/test/open_telemetry/operation/list_indexes.json new file mode 100644 index 0000000000..d37138d275 --- /dev/null +++ b/test/open_telemetry/operation/list_indexes.json @@ -0,0 +1,118 @@ +{ + "description": "operation list_indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-list-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-list-indexes", + "documents": [] + } + ], + "tests": [ + { + "description": "List indexes", + "operations": [ + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listIndexes operation-list-indexes.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-list-indexes", + "db.collection.name": "test", + "db.operation.name": "listIndexes", + "db.operation.summary": "listIndexes operation-list-indexes.test" + }, + "nested": [ + { + "name": "listIndexes", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-list-indexes", + "db.collection.name": "test", + "db.command.name": "listIndexes", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listIndexes operation-list-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listIndexes": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/map_reduce.json b/test/open_telemetry/operation/map_reduce.json new file mode 100644 index 0000000000..9230f7fd86 --- /dev/null +++ b/test/open_telemetry/operation/map_reduce.json @@ -0,0 +1,177 @@ +{ + "description": "operation map_reduce", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-map-reduce" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-map-reduce", + "documents": [ + { + "_id": 1, + "x": 0 + }, + { + "_id": 2, + "x": 1 + }, + { + "_id": 3, + "x": 2 + } + ] + } + ], + "tests": [ + { + "description": "mapReduce", + "operations": [ + { + "object": "collection0", + "name": "mapReduce", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "expectResult": [ + { + "_id": 0, + "value": 6 + } + ] + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "mapReduce operation-map-reduce.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-map-reduce", + "db.collection.name": "test", + "db.operation.name": "mapReduce", + "db.operation.summary": "mapReduce operation-map-reduce.test" + }, + "nested": [ + { + "name": "mapReduce", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-map-reduce", + "db.collection.name": "test", + "db.command.name": "mapReduce", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "mapReduce operation-map-reduce.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "mapReduce": "test", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/retries.json b/test/open_telemetry/operation/retries.json new file mode 100644 index 0000000000..d42f83f333 --- /dev/null +++ b/test/open_telemetry/operation/retries.json @@ -0,0 +1,209 @@ +{ + "description": "retries", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-retries" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-retries", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element with retries", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "find operation-retries.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-retries", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-retries.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-retries", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": "89", + "exception.message": { + "$$type": "string" + }, + "exception.type": { + "$$type": "string" + }, + "exception.stacktrace": { + "$$type": "string" + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "find operation-retries.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-retries", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-retries.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/operation/update.json b/test/open_telemetry/operation/update.json new file mode 100644 index 0000000000..f31054dc64 --- /dev/null +++ b/test/open_telemetry/operation/update.json @@ -0,0 +1,153 @@ +{ + "description": "operation update", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-update" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-update", + "documents": [] + } + ], + "tests": [ + { + "description": "update one element", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "update operation-update.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-update", + "db.collection.name": "test", + "db.operation.name": "update", + "db.operation.summary": "update operation-update.test" + }, + "nested": [ + { + "name": "update", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "operation-update", + "db.collection.name": "test", + "db.command.name": "update", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "update operation-update.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "update": "test", + "ordered": true, + "txnNumber": { + "$$unsetOrMatches": 1 + }, + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/transaction/convenient.json b/test/open_telemetry/transaction/convenient.json new file mode 100644 index 0000000000..a8f0c212d1 --- /dev/null +++ b/test/open_telemetry/transaction/convenient.json @@ -0,0 +1,326 @@ +{ + "description": "convenient transactions", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "convenient-transaction-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + }, + { + "session": { + "id": "session", + "client": "client" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "convenient-transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction", + "operations": [ + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + }, + "session": "session" + } + } + ] + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system.name": "mongodb" + }, + "nested": [ + { + "name": "insert convenient-transaction-tests.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert convenient-transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.command.name": "insert", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "insert convenient-transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session" + }, + "db.mongodb.txn_number": 1, + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "commitTransaction admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "commitTransaction", + "db.operation.summary": "commitTransaction admin" + }, + "nested": [ + { + "name": "commitTransaction", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "commitTransaction admin", + "db.command.name": "commitTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session" + }, + "db.mongodb.txn_number": 1, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "commitTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + }, + { + "name": "find convenient-transaction-tests.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.operation.summary": "find convenient-transaction-tests.test", + "db.operation.name": "find" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find convenient-transaction-tests.test", + "db.query.text": { + "$$exists": true + } + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "convenient-transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/test/open_telemetry/transaction/core_api.json b/test/open_telemetry/transaction/core_api.json new file mode 100644 index 0000000000..925cdb5162 --- /dev/null +++ b/test/open_telemetry/transaction/core_api.json @@ -0,0 +1,545 @@ +{ + "description": "transaction spans", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commit transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system.name": "mongodb" + }, + "nested": [ + { + "name": "insert transaction-tests.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.command.name": "insert", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "insert transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "commitTransaction admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "commitTransaction", + "db.operation.summary": "commitTransaction admin" + }, + "nested": [ + { + "name": "commitTransaction", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "commitTransaction admin", + "db.command.name": "commitTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "commitTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + }, + { + "name": "find transaction-tests.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.summary": "find transaction-tests.test", + "db.operation.name": "find" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.command.name": "find", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$exists": true + }, + "db.query.summary": "find transaction-tests.test" + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "abort transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": { + "$$exists": false + }, + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": { + "$$exists": false + }, + "db.operation.summary": { + "$$exists": false + } + }, + "nested": [ + { + "name": "insert transaction-tests.test", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.command.name": "insert", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "insert transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "abortTransaction admin", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "abortTransaction", + "db.operation.summary": "abortTransaction admin" + }, + "nested": [ + { + "name": "abortTransaction", + "attributes": { + "db.system.name": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "abortTransaction admin", + "db.command.name": "abortTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "network.transport": "tcp", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "abortTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/test/test_open_telemetry_unified.py b/test/test_open_telemetry_unified.py new file mode 100644 index 0000000000..d5e8adcb4f --- /dev/null +++ b/test/test_open_telemetry_unified.py @@ -0,0 +1,40 @@ +# Copyright 2026-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. + +"""Run the OpenTelemetry unified format spec tests.""" + +from __future__ import annotations + +import sys + +sys.path[0:0] = [""] + +import pytest + +from test import unittest +from test.unified_format import generate_test_classes, get_test_path + +_IS_SYNC = True + +pytestmark = pytest.mark.otel + +globals().update( + generate_test_classes( + get_test_path("open_telemetry"), + module=__name__, + ) +) + +if __name__ == "__main__": + unittest.main() diff --git a/test/unified_format.py b/test/unified_format.py index 4892a91e52..bda9798787 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -38,6 +38,7 @@ import pytest import pymongo +import pymongo._otel as _otel from bson import SON, json_util from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.objectid import ObjectId @@ -91,6 +92,7 @@ PLACEHOLDER_MAP, EventListenerUtil, MatchEvaluatorUtil, + _shared_test_provider, coerce_result, parse_bulk_write_error_result, parse_bulk_write_result, @@ -112,6 +114,16 @@ _IS_SYNC = True +_HAS_OTEL_TEST_DEPS = False +if _otel._HAS_OPENTELEMETRY: + try: + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter + + _HAS_OTEL_TEST_DEPS = True + except ImportError: + pass + IS_INTERRUPTED = False @@ -229,6 +241,11 @@ def __init__(self, test_class): self._entities: dict[str, Any] = {} self._listeners: dict[str, EventListenerUtil] = {} self._session_lsids: dict[str, Mapping[str, Any]] = {} + # The id of the (at most one, today) client entity created with + # observeTracingMessages. Spans carry no attribute identifying which + # client emitted them, so multi-client tracing correlation isn't + # supported; _create_entity fails loudly if a second one appears. + self._tracing_client_id: Optional[str] = None self.test: UnifiedSpecTestMixinV1 = test_class def __contains__(self, item): @@ -310,6 +327,25 @@ def _create_entity(self, entity_spec, uri=None): ) self._listeners[spec["id"]] = listener kwargs["event_listeners"] = [listener] + + observe_tracing = spec.get("observeTracingMessages") + if observe_tracing is not None: + if self._tracing_client_id is not None: + self.test.fail( + "Multiple clients with observeTracingMessages are not supported " + f"by the unified test format runner (already tracking " + f"{self._tracing_client_id!r}, got {spec['id']!r})" + ) + self._tracing_client_id = spec["id"] + enable_payload = observe_tracing.get("enableCommandPayload", False) + kwargs["tracing"] = { + "enabled": True, + # Tests asserting db.query.text match the full, untruncated + # command, so an effectively-unlimited length avoids + # truncating and failing that assertion. + "query_text_max_length": 1_000_000 if enable_payload else None, + } + if spec.get("useMultipleMongoses"): if client_context.load_balancer: kwargs["h"] = client_context.MULTI_MONGOS_LB_URI @@ -481,6 +517,8 @@ class UnifiedSpecTestMixinV1(IntegrationTest): TEST_SPEC: Any TEST_PATH = "" # This gets filled in by generate_test_classes mongos_clients: list[MongoClient] = [] + # Set in setUpClass, only for test files that use observeTracingMessages. + _tracing_exporter: Optional[Any] = None @staticmethod def should_run_on(run_on_spec): @@ -525,6 +563,23 @@ def insert_initial_data(self, initial_data): @classmethod def setUpClass(cls) -> None: + # Only register a span exporter (and the shared SDK TracerProvider it + # depends on) for test files that actually use observeTracingMessages, + # to avoid needlessly accumulating span processors on the process-wide + # provider for the (vast majority of) unified-format suites that don't. + cls._tracing_exporter = None + uses_tracing = any( + "observeTracingMessages" in entity.get("client", {}) + for entity in cls.TEST_SPEC.get("createEntities", []) + ) + if uses_tracing: + if not _HAS_OTEL_TEST_DEPS: + raise unittest.SkipTest( + "observeTracingMessages requires opentelemetry-sdk to be installed" + ) + cls._tracing_exporter = InMemorySpanExporter() + _shared_test_provider().add_span_processor(SimpleSpanProcessor(cls._tracing_exporter)) + # Speed up the tests by decreasing the heartbeat frequency. cls.knobs = client_knobs( heartbeat_frequency=0.1, @@ -537,6 +592,14 @@ def setUpClass(cls) -> None: @classmethod def tearDownClass(cls) -> None: cls.knobs.disable() + # The exporter's span processor can never be removed from the shared process-wide + # TracerProvider (see _shared_test_provider), so without this, every span emitted by any + # client anywhere in the process for the rest of the test run keeps getting appended to this + # (otherwise dead) class's exporter: an unbounded memory leak across a full test run, and + # needless per-span export overhead for every other tracing-enabled test class that runs + # afterwards. shutdown() makes further export() calls into this exporter no-ops. + if cls._tracing_exporter is not None: + cls._tracing_exporter.shutdown() def setUp(self): # super call creates internal client cls.client @@ -575,6 +638,14 @@ def maybe_skip_test(self, spec): self.skipTest("PyMongo does not support the symbol type") if "timeoutms applied to entire download" in description: self.skipTest("PyMongo's open_download_stream does not cap the stream's lifetime") + # Removed API: PyMongo no longer exposes map_reduce/inline_map_reduce at + # all (mapReduce is deprecated server-side), so there's no code path left + # that could send this command; this operation can never be exercised. + if class_name == "testoperationmapreduce" and description == "mapreduce": + self.skipTest( + "PyMongo removed the map_reduce/inline_map_reduce Collection methods " + "(mapReduce is deprecated server-side); this operation cannot be exercised" + ) if any( x in description for x in [ @@ -1450,6 +1521,77 @@ def format_logs(log_list): self.match_evaluator.match_result(expected_data, actual_data) self.match_evaluator.match_result(expected_msg, actual_msg) + def check_tracing_messages(self, operations, spec): + # Like expectLogMessages/expectEvents, expectTracingMessages is a list of + # per-client blocks (even though only one client with + # observeTracingMessages is currently supported, see entity.py above). + exporter = self._tracing_exporter + if exporter is None: + self.fail( + "expectTracingMessages requires a client entity created with observeTracingMessages" + ) + + exporter.clear() + self.run_operations(operations) + finished_spans = exporter.get_finished_spans() + + # Reconstruct the parent/child span tree from the flat, finish-ordered + # list the in-memory exporter records, keyed by each span's parent id. + children_by_parent_id = defaultdict(list) + for span in finished_spans: + parent_id = span.parent.span_id if span.parent is not None else None + children_by_parent_id[parent_id].append(span) + + def check_span_list(expected_list, actual_list, ignore_extra_spans): + if ignore_extra_spans: + # Per the unified-test-format spec, "additional unexpected spans + # are allowed". Unlike ignoreExtraEvents (which only tolerates + # a trailing tail), spans from concurrent/out-of-band activity + # (e.g. a testRunner-issued configureFailPoint command) can + # finish interleaved anywhere among the expected ones, not just + # at the end. Filter down to just the spans that line up (by + # name, in order) with the expected list, dropping anything + # else, instead of naively truncating the tail. + filtered = [] + expected_iter = iter(expected_list) + current_expected = next(expected_iter, None) + for actual in actual_list: + if current_expected is not None and actual.name == current_expected["name"]: + filtered.append(actual) + current_expected = next(expected_iter, None) + actual_list = filtered + self.assertEqual( + len(expected_list), + len(actual_list), + f"expected spans {[e['name'] for e in expected_list]} but got " + f"{[a.name for a in actual_list]}", + ) + for expected, actual in zip(expected_list, actual_list): + self.assertEqual(expected["name"], actual.name) + self.match_evaluator.match_span_attributes( + expected["attributes"], actual.attributes + ) + expected_nested = expected.get("nested") + if expected_nested is not None: + actual_children = children_by_parent_id[actual.context.span_id] + check_span_list(expected_nested, actual_children, ignore_extra_spans) + + for client_spec in spec: + expected_client_id = client_spec["client"] + tracing_client_id = self.entity_map._tracing_client_id + self.assertEqual( + expected_client_id, + tracing_client_id, + f"expectTracingMessages.client {expected_client_id!r} does not match the " + f"client with observeTracingMessages enabled ({tracing_client_id!r})", + ) + + ignore_extra_spans = client_spec.get("ignoreExtraSpans", False) + expected_spans = client_spec["spans"] + self.assertTrue(expected_spans, "expectTracingMessages spans must be non-empty") + + check_span_list(expected_spans, children_by_parent_id[None], ignore_extra_spans) + def verify_outcome(self, spec): for collection_data in spec: coll_name = collection_data["collectionName"] @@ -1536,6 +1678,10 @@ def _run_scenario(self, spec, uri=None): expect_log_messages = spec["expectLogMessages"] self.assertTrue(expect_log_messages, "expectEvents must be non-empty") self.check_log_messages(spec["operations"], expect_log_messages) + elif "expectTracingMessages" in spec: + expect_tracing_messages = spec["expectTracingMessages"] + self.assertTrue(expect_tracing_messages, "expectTracingMessages must be non-empty") + self.check_tracing_messages(spec["operations"], expect_tracing_messages) else: # process operations self.run_operations(spec["operations"]) diff --git a/test/unified_format_shared.py b/test/unified_format_shared.py index 77eea4d753..80b36b3dac 100644 --- a/test/unified_format_shared.py +++ b/test/unified_format_shared.py @@ -25,8 +25,9 @@ import os import time import types +import uuid from collections import abc -from collections.abc import MutableMapping +from collections.abc import Mapping, MutableMapping from typing import Any, Union import pymongo._otel as _otel @@ -585,6 +586,42 @@ def match_result(self, expectation, actual, in_recursive_call=False, test=True): return isinstance(actual, type(expectation)) and expectation == actual return True + @staticmethod + def _normalize_span_attribute(key: str, value: Any) -> Any: + """Adapt one OTel span attribute value to what the generic match + evaluator expects, since span attributes are plain Python primitives + rather than the BSON-decoded documents/events it normally matches + against. + + - Widen plain Python ints (excluding bools) to ``bson.Int64``: span + attributes carry no int32/int64 distinction, but the $$type matcher's + "long" alias maps to ``Int64`` specifically (see + BSON_TYPE_ALIAS_MAP), so a bare ``int`` (e.g. ``server.port``) would + otherwise fail a ``$$type: ["long", "string"]`` check. ``Int64`` is a + subclass of ``int``, so this is safe for "int" checks too. + - Reconstruct ``db.mongodb.lsid`` (formatted by pymongo/_otel.py as a + plain UUID string, per the OTel spec's attribute table) back into the + ``{"id": Binary(...)}`` document shape :meth:`_operation_sessionLsid` + compares against, that operator being designed for + command-monitoring-style raw command documents. + """ + if key == "db.mongodb.lsid" and isinstance(value, str): + try: + return {"id": Binary.from_uuid(uuid.UUID(value))} + except ValueError: + return value + if isinstance(value, bool): + return value + if isinstance(value, int): + return Int64(value) + return value + + def match_span_attributes(self, expected: Mapping[str, Any], actual: Mapping[str, Any]) -> None: + """Match one span's attributes against an ``expectTracingMessages`` entry.""" + self.match_result( + expected, {k: self._normalize_span_attribute(k, v) for k, v in actual.items()} + ) + def match_server_description(self, actual: ServerDescription, spec: dict) -> None: for field, expected in spec.items(): field = camel_to_snake(field)