From 3aed4cd0f155b5db6fa2e4f459bfa867bb7ca1c4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 17 Aug 2026 07:03:26 -0500 Subject: [PATCH 1/2] 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) From 823b83f9824ed2cba27961ce78cdd266d9a7e840 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 21 Aug 2026 10:37:14 -0500 Subject: [PATCH 2/2] PYTHON-6036 Trim the unified tracing runner comments and docstrings Cut each to the invariant a reader needs, dropping spec references and restatements of the code. --- test/asynchronous/unified_format.py | 46 ++++++++--------------------- test/unified_format.py | 46 ++++++++--------------------- test/unified_format_shared.py | 22 ++++---------- 3 files changed, 32 insertions(+), 82 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index ae81c11c88..28486e1cef 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -242,10 +242,8 @@ 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. + # The one client entity created with observeTracingMessages. Spans carry + # no attribute identifying their client, so a second one is rejected. self._tracing_client_id: Optional[str] = None self.test: UnifiedSpecTestMixinV1 = test_class @@ -341,9 +339,7 @@ async def _create_entity(self, entity_spec, uri=None): 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. + # Tests match the full command, so never truncate. "query_text_max_length": 1_000_000 if enable_payload else None, } @@ -564,10 +560,8 @@ 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. + # Only for test files that use observeTracingMessages: span processors + # accumulate on the process-wide provider and can never be removed. cls._tracing_exporter = None uses_tracing = any( "observeTracingMessages" in entity.get("client", {}) @@ -593,12 +587,8 @@ 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. + # The span processor can never be removed from the shared process-wide + # TracerProvider, so without this the exporter accumulates every span. if cls._tracing_exporter is not None: cls._tracing_exporter.shutdown() @@ -639,9 +629,6 @@ 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 " @@ -1535,9 +1522,8 @@ def format_logs(log_list): 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). + # A list of per-client blocks, like expectLogMessages, though only one + # client with observeTracingMessages is supported. exporter = self._tracing_exporter if exporter is None: self.fail( @@ -1548,8 +1534,7 @@ async def check_tracing_messages(self, operations, spec): 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. + # Rebuild the parent/child tree from the exporter's flat, finish-ordered list. 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 @@ -1557,14 +1542,9 @@ async def check_tracing_messages(self, operations, spec): 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. + # Unlike ignoreExtraEvents, extra spans can finish interleaved + # anywhere rather than only at the end, so match by name in + # order instead of truncating the tail. filtered = [] expected_iter = iter(expected_list) current_expected = next(expected_iter, None) diff --git a/test/unified_format.py b/test/unified_format.py index bda9798787..1a134eb40c 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -241,10 +241,8 @@ 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. + # The one client entity created with observeTracingMessages. Spans carry + # no attribute identifying their client, so a second one is rejected. self._tracing_client_id: Optional[str] = None self.test: UnifiedSpecTestMixinV1 = test_class @@ -340,9 +338,7 @@ def _create_entity(self, entity_spec, uri=None): 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. + # Tests match the full command, so never truncate. "query_text_max_length": 1_000_000 if enable_payload else None, } @@ -563,10 +559,8 @@ 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. + # Only for test files that use observeTracingMessages: span processors + # accumulate on the process-wide provider and can never be removed. cls._tracing_exporter = None uses_tracing = any( "observeTracingMessages" in entity.get("client", {}) @@ -592,12 +586,8 @@ 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. + # The span processor can never be removed from the shared process-wide + # TracerProvider, so without this the exporter accumulates every span. if cls._tracing_exporter is not None: cls._tracing_exporter.shutdown() @@ -638,9 +628,6 @@ 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 " @@ -1522,9 +1509,8 @@ def format_logs(log_list): 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). + # A list of per-client blocks, like expectLogMessages, though only one + # client with observeTracingMessages is supported. exporter = self._tracing_exporter if exporter is None: self.fail( @@ -1535,8 +1521,7 @@ def check_tracing_messages(self, operations, spec): 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. + # Rebuild the parent/child tree from the exporter's flat, finish-ordered list. 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 @@ -1544,14 +1529,9 @@ def check_tracing_messages(self, operations, spec): 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. + # Unlike ignoreExtraEvents, extra spans can finish interleaved + # anywhere rather than only at the end, so match by name in + # order instead of truncating the tail. filtered = [] expected_iter = iter(expected_list) current_expected = next(expected_iter, None) diff --git a/test/unified_format_shared.py b/test/unified_format_shared.py index 80b36b3dac..ae74a73d70 100644 --- a/test/unified_format_shared.py +++ b/test/unified_format_shared.py @@ -588,22 +588,12 @@ def match_result(self, expectation, actual, in_recursive_call=False, test=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. + """Adapt one span attribute value to what the generic match evaluator expects. + + Span attributes are plain Python primitives, not the BSON-decoded documents + the evaluator matches against. Ints widen to ``Int64`` for the ``$$type`` + "long" alias, and ``db.mongodb.lsid`` is rebuilt from its UUID string into + the document shape :meth:`_operation_sessionLsid` expects. """ if key == "db.mongodb.lsid" and isinstance(value, str): try: