Skip to content

Commit f059514

Browse files
Spec runner
1 parent bf0dd5d commit f059514

File tree

15 files changed

+625
-30
lines changed

15 files changed

+625
-30
lines changed

lib/mongo.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
autoload :CGI, 'cgi'
3434

3535
require 'bson'
36+
require 'opentelemetry-api'
3637

3738
require 'mongo/id'
3839
require 'mongo/bson'

lib/mongo/tracing/open_telemetry/command_tracer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def span_attributes(message, connection)
6565
'db.namespace' => database(message),
6666
'db.collection.name' => collection_name(message),
6767
'db.command.name' => command_name(message),
68+
'db.query.summary' => command_span_name(message),
6869
'server.port' => connection.address.port,
6970
'server.address' => connection.address.host,
7071
'network.transport' => connection.transport.to_s,

lib/mongo/tracing/open_telemetry/operation_tracer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def trace_operation(operation, operation_context)
5858
private
5959

6060
def operation_name(operation)
61-
operation.class.name.split('::').last
61+
operation.class.name.split('::').last.downcase
6262
end
6363

6464
def span_attributes(operation)

spec/runners/unified.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def define_unified_spec_tests(base_path, paths, expect_failure: false)
9999
test.run
100100
test.assert_outcome
101101
test.assert_events
102+
test.assert_tracing_messages
102103
test.cleanup
103104
end
104105
end

spec/runners/unified/assertions.rb

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,14 @@ def assert_documents_match(actual, expected)
140140
end
141141
end
142142

143-
def assert_document_matches(actual, expected, msg)
144-
unless actual == expected
145-
raise Error::ResultMismatch, "#{msg} does not match"
143+
def assert_document_matches(actual, expected, msg, as_root: false)
144+
if !as_root && actual.keys.to_set != expected.keys.to_set
145+
raise Error::ResultMismatch, "#{msg} keys do not match: expected #{expected.keys}, actual #{actual.keys}"
146+
end
147+
expected.each do |key, expected_value|
148+
raise Error::ResultMismatch, "#{msg} has no key #{key}" unless actual.key?(key)
149+
actual_value = actual[key]
150+
assert_value_matches(actual_value, expected_value, "#{msg} key #{key}")
146151
end
147152
end
148153

@@ -383,6 +388,14 @@ def assert_value_matches(actual, expected, msg)
383388
if actual.nil? || actual >= expected_v
384389
raise Error::ResultMismatch, "Actual value #{actual} should be less than #{expected_v}"
385390
end
391+
when '$$matchAsDocument'
392+
actual_v = BSON::ExtJSON.parse(actual)
393+
match_as_root = false
394+
if expected_v.keys.first == '$$matchAsRoot'
395+
expected_v = expected_v.values.first
396+
match_as_root = true
397+
end
398+
assert_document_matches(actual_v, expected_v, msg, as_root: match_as_root)
386399
else
387400
raise NotImplementedError, "Unknown operator #{operator}"
388401
end
@@ -392,5 +405,44 @@ def assert_value_matches(actual, expected, msg)
392405
end
393406
end
394407
end
408+
409+
def assert_tracing_messages
410+
return unless @expected_tracing_messages
411+
@expected_tracing_messages.each do |spec|
412+
spec = UsingHash[spec]
413+
client_id = spec.use!('client')
414+
client = entities.get(:client, client_id)
415+
tracer = @tracers.fetch(client)
416+
expected_spans = spec.use!('spans')
417+
ignore_extra_spans = if ignore = spec.use('ignoreExtraSpans')
418+
# Ruby treats 0 as truthy, whereas the spec tests use it as falsy.
419+
ignore == 0 ? false : ignore
420+
else
421+
false
422+
end
423+
actual_spans = tracer.span_hierarchy
424+
if (!ignore_extra_spans && actual_spans.length != expected_spans.length) ||
425+
(ignore_extra_spans && actual_spans.length < expected_spans.length)
426+
raise Error::ResultMismatch, "Span count mismatch: expected #{expected_spans.length}, actual #{actual_spans.length}\nExpected: #{expected_spans}\nActual: #{actual_spans}"
427+
end
428+
expected_spans.each_with_index do |expected, i|
429+
assert_span_matches(actual_spans[i], expected)
430+
end
431+
end
432+
end
433+
434+
def assert_span_matches(actual, expected)
435+
assert_eq(actual.name, expected.use!('name'), 'Span name does not match')
436+
expected_attributes = UsingHash[expected.use!('tags')]
437+
expected_attributes.each do |key, value|
438+
actual_value = actual.attributes[key]
439+
assert_value_matches(actual_value, value, "Span attribute #{key}")
440+
end
441+
442+
expected_nested_spans = expected.use('nested') || []
443+
expected_nested_spans.each_with_index do |nested_expected, i|
444+
assert_span_matches(actual.nested[i], nested_expected)
445+
end
446+
end
395447
end
396448
end

spec/runners/unified/test.rb

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def initialize(spec, **opts)
3737
@description = @test_spec.use('description')
3838
@outcome = @test_spec.use('outcome')
3939
@expected_events = @test_spec.use('expectEvents')
40+
@expected_tracing_messages = @test_spec.use('expectTracingMessages')
4041
@expected_spans = @test_spec.use('expectSpans')
4142
@skip_reason = @test_spec.use('skipReason')
4243
if req = @test_spec.use('runOnRequirements')
@@ -55,6 +56,7 @@ def initialize(spec, **opts)
5556
end
5657
@test_spec.freeze
5758
@subscribers = {}
59+
@tracers = {}
5860
@observe_sensitive = {}
5961
@options = opts
6062
end
@@ -196,17 +198,24 @@ def generate_entities(es)
196198
end
197199
end
198200

199-
observe_spans = spec.use('observeSpans')
200-
if observe_spans
201+
observe_tracing_messages = spec.use('observeTracingMessages')
202+
tracer = ::Tracing::Tracer.new
203+
if observe_tracing_messages
201204
opts[:tracing] = {
202205
enabled: true,
203-
# tracer: tracer,
206+
tracer: tracer,
204207
}
208+
if observe_tracing_messages['enableCommandPayload']
209+
# Set the maximum length of the query text to reasonably high
210+
# value so that we can capture the full query text
211+
opts[:tracing][:query_text_max_length] = 4096
212+
end
205213
end
206214

207215
create_client(**opts).tap do |client|
208216
@observe_sensitive[id] = spec.use('observeSensitiveCommands')
209217
@subscribers[client] ||= subscriber
218+
@tracers[client] ||= tracer
210219
end
211220
when 'database'
212221
client = entities.get(:client, spec.use!('client'))
@@ -611,9 +620,5 @@ def bson_error
611620
BSON::String.const_get(:IllegalKey) :
612621
BSON::Error
613622
end
614-
615-
def tracer
616-
@tracer ||= ::Tracing::Tracer.new
617-
end
618623
end
619624
end

spec/spec_tests/data/crud_unified/find-comment.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ createEntities:
66
- client:
77
id: &client0 client0
88
observeEvents: [ commandStartedEvent ]
9-
observeSpans: true
109
- database:
1110
id: &database0 database0
1211
client: *client0
@@ -48,15 +47,6 @@ tests:
4847
find: *collection0Name
4948
filter: *filter
5049
comment: "comment"
51-
expectSpans:
52-
- client: *client0
53-
spans:
54-
- name: "find"
55-
attributes:
56-
mongodb.collection: *collection0Name
57-
mongodb.database: *database0Name
58-
mongodb.filter: *filter
59-
mongodb.comment: "comment"
6050

6151
- description: "find with document comment"
6252
runOnRequirements:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# OpenTelemetry Tests
2+
3+
______________________________________________________________________
4+
5+
## Testing
6+
7+
### Automated Tests
8+
9+
The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of
10+
the OpenTelemetry specification. These tests utilize the
11+
[Unified Test Format](../../unified-test-format/unified-test-format.md).
12+
13+
For each test, create a MongoClient, configure it to enable tracing.
14+
15+
```yaml
16+
createEntities:
17+
- client:
18+
id: client0
19+
observeTracingMessages:
20+
enableCommandPayload: true
21+
```
22+
23+
These tests require the ability to collect tracing [spans](../open-telemetry.md#span) data in a structured form as
24+
described in the
25+
[Unified Test Format specification.expectTracingMessages](../../unified-test-format/unified-test-format.md#expectTracingMessages).
26+
For example the Java driver uses [Micrometer](https://jira.mongodb.org/browse/JAVA-5732) to collect tracing spans.
27+
28+
```yaml
29+
expectTracingMessages:
30+
client: client0
31+
ignoreExtraSpans: false
32+
spans:
33+
...
34+
```
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
description: cursor retrieval
2+
schemaVersion: '1.26'
3+
createEntities:
4+
- client:
5+
id: &client0 client0
6+
useMultipleMongoses: false
7+
observeTracingMessages:
8+
enableCommandPayload: true
9+
- database:
10+
id: &database0 database0
11+
client: *client0
12+
databaseName: cursor
13+
- collection:
14+
id: &collection0 collection0
15+
database: *database0
16+
collectionName: test
17+
initialData:
18+
- collectionName: test
19+
databaseName: cursor
20+
documents:
21+
- { _id: 1 }
22+
- { _id: 2 }
23+
- { _id: 3 }
24+
- { _id: 4 }
25+
- { _id: 5 }
26+
- { _id: 6 }
27+
tests:
28+
- description: find with a cursor
29+
operations:
30+
- name: find
31+
object: *collection0
32+
arguments:
33+
filter: { _id: { $gt: 1 } }
34+
batchSize: 2
35+
expectResult:
36+
- { _id: 2 }
37+
- { _id: 3 }
38+
- { _id: 4 }
39+
- { _id: 5 }
40+
- { _id: 6 }
41+
expectTracingMessages:
42+
client: *client0
43+
ignoreExtraSpans: false
44+
spans:
45+
- name: find cursor.test
46+
tags:
47+
db.system: mongodb
48+
db.namespace: cursor
49+
db.collection.name: test
50+
db.operation.name: find
51+
db.operation.summary: find cursor.test
52+
nested:
53+
- name: command find
54+
tags:
55+
db.system: mongodb
56+
db.namespace: cursor
57+
db.collection.name: cursor.$cmd
58+
db.command.name: find
59+
network.transport: tcp
60+
db.mongodb.cursor_id: { $$exists: false }
61+
db.response.status_code: { $$exists: false }
62+
exception.message: { $$exists: false }
63+
exception.type: { $$exists: false }
64+
exception.stacktrace: { $$exists: false }
65+
server.address: { $$type: string }
66+
server.port: { $$type: ['int', 'long'] }
67+
server.type: { $$type: string }
68+
db.query.summary: find
69+
db.query.text:
70+
$$matchAsDocument:
71+
$$matchAsRoot:
72+
find: test
73+
filter: { _id: { $gt: 1 } }
74+
batchSize: 2
75+
db.mongodb.server_connection_id:
76+
$$type: [ 'int', 'long' ]
77+
db.mongodb.driver_connection_id:
78+
$$type: [ 'int', 'long' ]
79+
80+
- name: command getMore
81+
tags:
82+
db.system: mongodb
83+
db.namespace: cursor
84+
db.collection.name: cursor.$cmd
85+
db.command.name: getMore
86+
network.transport: tcp
87+
db.mongodb.cursor_id: { $$type: [ 'int', 'long' ] }
88+
db.response.status_code: { $$exists: false }
89+
exception.message: { $$exists: false }
90+
exception.type: { $$exists: false }
91+
exception.stacktrace: { $$exists: false }
92+
server.address: { $$type: string }
93+
server.port: { $$type: ['int', 'long'] }
94+
server.type: { $$type: string }
95+
db.query.summary: getMore
96+
db.query.text:
97+
$$matchAsDocument:
98+
$$matchAsRoot:
99+
getMore: { $$type: long }
100+
collection: test
101+
batchSize: 2
102+
db.mongodb.server_connection_id:
103+
$$type: [ 'int', 'long' ]
104+
db.mongodb.driver_connection_id:
105+
$$type: [ 'int', 'long' ]
106+
- name: command getMore
107+
tags:
108+
db.system: mongodb
109+
db.namespace: cursor
110+
db.collection.name: cursor.$cmd
111+
db.command.name: getMore
112+
network.transport: tcp
113+
db.mongodb.cursor_id: { $$type: [ 'int', 'long' ] }
114+
db.response.status_code: { $$exists: false }
115+
exception.message: { $$exists: false }
116+
exception.type: { $$exists: false }
117+
exception.stacktrace: { $$exists: false }
118+
server.address: { $$type: string }
119+
server.port: { $$type: ['int', 'long'] }
120+
server.type: { $$type: string }
121+
db.query.summary: getMore
122+
db.query.text:
123+
$$matchAsDocument:
124+
$$matchAsRoot:
125+
getMore: { $$type: long }
126+
collection: test
127+
batchSize: 2
128+
db.mongodb.server_connection_id:
129+
$$type: [ 'int', 'long' ]
130+
db.mongodb.driver_connection_id:
131+
$$type: [ 'int', 'long' ]

0 commit comments

Comments
 (0)