datadog-ci 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -1
- data/lib/datadog/ci/contrib/minitest/helpers.rb +21 -6
- data/lib/datadog/ci/contrib/rspec/anonymous_example_name.rb +515 -0
- data/lib/datadog/ci/contrib/rspec/example.rb +23 -5
- data/lib/datadog/ci/contrib/simplecov/result_extractor.rb +9 -1
- data/lib/datadog/ci/test_discovery/component.rb +4 -0
- data/lib/datadog/ci/test_tracing/component.rb +8 -0
- data/lib/datadog/ci/test_tracing/deprecated_total_coverage_metric.rb +11 -1
- data/lib/datadog/ci/utils/test_name.rb +112 -0
- data/lib/datadog/ci/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1aa81f9987d876f143abe3a9b7721e1a5b35178c5c854dcdaf79b872c5a0c9e
|
|
4
|
+
data.tar.gz: 261ea8b1b233ac66c0ba7b417018fe477afb05a274ed6607b6bf3c235c58c3ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05aed65898a6b065f99e6a9fd2aab34023fd3ac77aaacc4146d39d8a1e6653ad5cb6d970afc5e6d590a4ddf6eec3f54704089a5ea49d7bf2f614d27d1572ed91
|
|
7
|
+
data.tar.gz: f89daf423afed1bd0ac7c227a28c4a980b92916fcf03a078a9278eb880e059c9fb40f8dc3b0887645e8a806f2e879e70bf23eaaa7500cd3e6e6e2d4b518a449f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.35.0] - 2026-07-23
|
|
4
|
+
|
|
3
5
|
## [1.34.0] - 2026-06-25
|
|
4
6
|
|
|
5
7
|
### Added
|
|
@@ -665,7 +667,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil
|
|
|
665
667
|
|
|
666
668
|
- Ruby versions < 2.7 no longer supported ([#8][])
|
|
667
669
|
|
|
668
|
-
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.
|
|
670
|
+
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v1.35.0...main
|
|
671
|
+
[1.35.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.34.0...v1.35.0
|
|
669
672
|
[1.34.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.33.0...v1.34.0
|
|
670
673
|
[1.33.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.32.0...v1.33.0
|
|
671
674
|
[1.32.0]: https://github.com/DataDog/datadog-ci-rb/compare/v1.31.0...v1.32.0
|
|
@@ -81,11 +81,14 @@ module Datadog
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def self.extract_runnable_source_location(klass, method_name)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
method_source_location = extract_source_location_from_method(klass, method_name)
|
|
85
|
+
klass_source_location = extract_source_location_from_class(klass)
|
|
86
|
+
|
|
87
|
+
if defined?(::Minitest::Spec) && klass&.ancestors&.include?(::Minitest::Spec)
|
|
88
|
+
method_source_location || klass_source_location
|
|
89
|
+
else
|
|
90
|
+
klass_source_location || method_source_location
|
|
87
91
|
end
|
|
88
|
-
source_location
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
def self.skip_test_suite(test_suite)
|
|
@@ -102,9 +105,21 @@ module Datadog
|
|
|
102
105
|
end
|
|
103
106
|
|
|
104
107
|
def self.extract_source_location_from_class(klass)
|
|
105
|
-
return
|
|
108
|
+
return nil if klass.nil? || klass.name.nil?
|
|
109
|
+
|
|
110
|
+
empty_source_location_to_nil(SourceCode::ConstantResolver.safely_get_const_source_location(klass.name))
|
|
111
|
+
end
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
def self.extract_source_location_from_method(klass, method_name)
|
|
114
|
+
return nil if klass.nil?
|
|
115
|
+
return nil if method_name.nil? || method_name.empty?
|
|
116
|
+
|
|
117
|
+
empty_source_location_to_nil(klass.instance_method(method_name).source_location)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.empty_source_location_to_nil(source_location)
|
|
121
|
+
return nil if source_location.nil? || source_location.empty?
|
|
122
|
+
source_location
|
|
108
123
|
end
|
|
109
124
|
end
|
|
110
125
|
end
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module CI
|
|
5
|
+
module Contrib
|
|
6
|
+
module RSpec
|
|
7
|
+
module AnonymousExampleName
|
|
8
|
+
ISEQ_SIMPLE_DATA_FORMAT = "YARVInstructionSequence/SimpleDataFormat"
|
|
9
|
+
|
|
10
|
+
SELF_VALUE = :__datadog_rspec_self__
|
|
11
|
+
EMPTY_ARGUMENTS = [].freeze
|
|
12
|
+
MINIMUM_RUBY_VERSION = Gem::Version.new("3.2")
|
|
13
|
+
|
|
14
|
+
EXPECTATION_METHODS = {
|
|
15
|
+
to: "is expected to",
|
|
16
|
+
not_to: "is expected not to",
|
|
17
|
+
to_not: "is expected not to"
|
|
18
|
+
}.freeze
|
|
19
|
+
SHOULD_METHODS = {
|
|
20
|
+
should: "is expected to",
|
|
21
|
+
should_not: "is expected not to"
|
|
22
|
+
}.freeze
|
|
23
|
+
OPERATOR_METHODS = %i[== != === =~ !~ < <= > >= + - * / %].each_with_object({}) { |method_name, methods|
|
|
24
|
+
methods[method_name] = true
|
|
25
|
+
}.freeze
|
|
26
|
+
MATCHER_CHAIN_METHODS = %i[
|
|
27
|
+
and
|
|
28
|
+
at_least
|
|
29
|
+
at_most
|
|
30
|
+
by
|
|
31
|
+
by_at_least
|
|
32
|
+
by_at_most
|
|
33
|
+
exactly
|
|
34
|
+
from
|
|
35
|
+
of
|
|
36
|
+
once
|
|
37
|
+
or
|
|
38
|
+
times
|
|
39
|
+
to
|
|
40
|
+
twice
|
|
41
|
+
with
|
|
42
|
+
within
|
|
43
|
+
].each_with_object({}) { |method_name, methods|
|
|
44
|
+
methods[method_name] = true
|
|
45
|
+
}.freeze
|
|
46
|
+
PRETTY_MATCHER_PREFIXES = %w[be_ have_ raise_ respond_ yield_ start_ end_].freeze
|
|
47
|
+
PRETTY_MATCHER_METHODS = %i[
|
|
48
|
+
be
|
|
49
|
+
change
|
|
50
|
+
contain_exactly
|
|
51
|
+
eq
|
|
52
|
+
eql
|
|
53
|
+
equal
|
|
54
|
+
exist
|
|
55
|
+
include
|
|
56
|
+
match
|
|
57
|
+
output
|
|
58
|
+
raise_error
|
|
59
|
+
satisfy
|
|
60
|
+
throw_symbol
|
|
61
|
+
].each_with_object({}) { |method_name, methods|
|
|
62
|
+
methods[method_name] = true
|
|
63
|
+
}.freeze
|
|
64
|
+
MAX_RENDERED_VALUE_LENGTH = 80
|
|
65
|
+
MAX_RENDERED_NAME_LENGTH = 180
|
|
66
|
+
|
|
67
|
+
def self.supported?
|
|
68
|
+
return false unless defined?(RubyVM::InstructionSequence)
|
|
69
|
+
return false unless RUBY_ENGINE == "ruby"
|
|
70
|
+
|
|
71
|
+
Gem::Version.new(RUBY_VERSION) >= MINIMUM_RUBY_VERSION
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.call(target)
|
|
75
|
+
return nil if target.nil?
|
|
76
|
+
return nil unless supported?
|
|
77
|
+
|
|
78
|
+
iseq = iseq_for(target)
|
|
79
|
+
return nil unless iseq
|
|
80
|
+
|
|
81
|
+
rendered_name = render_iseq(iseq)
|
|
82
|
+
trim(rendered_name, MAX_RENDERED_NAME_LENGTH) if rendered_name
|
|
83
|
+
rescue => e
|
|
84
|
+
warn_anonymous_example_name_error(e)
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.iseq_for(target)
|
|
89
|
+
return target if target.is_a?(RubyVM::InstructionSequence)
|
|
90
|
+
|
|
91
|
+
RubyVM::InstructionSequence.of(target)
|
|
92
|
+
end
|
|
93
|
+
private_class_method :iseq_for
|
|
94
|
+
|
|
95
|
+
def self.render_iseq(iseq)
|
|
96
|
+
body = iseq.to_a[13]
|
|
97
|
+
return nil unless body.is_a?(Array)
|
|
98
|
+
|
|
99
|
+
# Most anonymous RSpec examples end with `<expectation>.to <matcher>`.
|
|
100
|
+
# Render only that known shape by walking backwards from the final matcher call:
|
|
101
|
+
# it avoids simulating unrelated subject code, so dynamic values do not
|
|
102
|
+
# leak into the name and dangerous user code is not evaluated.
|
|
103
|
+
expectation_call_index = final_expectation_call_index(body)
|
|
104
|
+
return nil unless expectation_call_index
|
|
105
|
+
|
|
106
|
+
call_data = body[expectation_call_index][1]
|
|
107
|
+
return nil unless call_data[:orig_argc].to_i == 1
|
|
108
|
+
|
|
109
|
+
matcher_expression = render_expression_ending_at(body, expectation_call_index - 1)
|
|
110
|
+
return nil unless matcher_expression
|
|
111
|
+
|
|
112
|
+
method_name = call_data[:mid]
|
|
113
|
+
return nil if EXPECTATION_METHODS.key?(method_name) && !expectation_receiver_at?(body, matcher_expression[1])
|
|
114
|
+
|
|
115
|
+
name_prefix = EXPECTATION_METHODS[method_name] || SHOULD_METHODS[method_name]
|
|
116
|
+
"#{name_prefix} #{matcher_expression[0]}"
|
|
117
|
+
end
|
|
118
|
+
private_class_method :render_iseq
|
|
119
|
+
|
|
120
|
+
def self.final_expectation_call_index(body)
|
|
121
|
+
index = body.length - 1
|
|
122
|
+
|
|
123
|
+
while index >= 0
|
|
124
|
+
entry = body[index]
|
|
125
|
+
|
|
126
|
+
if entry.is_a?(Array)
|
|
127
|
+
call_data = entry[1]
|
|
128
|
+
if call_data.is_a?(Hash)
|
|
129
|
+
method_name = call_data[:mid]
|
|
130
|
+
return index if EXPECTATION_METHODS.key?(method_name) || SHOULD_METHODS.key?(method_name)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
index -= 1
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
private_class_method :final_expectation_call_index
|
|
140
|
+
|
|
141
|
+
def self.expectation_receiver_at?(body, index)
|
|
142
|
+
index = previous_instruction_index(body, index)
|
|
143
|
+
return false unless index
|
|
144
|
+
|
|
145
|
+
instruction = body[index]
|
|
146
|
+
call_data = instruction[1]
|
|
147
|
+
return false unless call_data.is_a?(Hash)
|
|
148
|
+
|
|
149
|
+
method_name = call_data[:mid]
|
|
150
|
+
method_name == :expect || method_name == :is_expected
|
|
151
|
+
end
|
|
152
|
+
private_class_method :expectation_receiver_at?
|
|
153
|
+
|
|
154
|
+
# Render the expression that produced the stack value at `index`.
|
|
155
|
+
#
|
|
156
|
+
# YARV bytecode is stack-based: matcher arguments are pushed before the
|
|
157
|
+
# final `to`/`not_to` call consumes them. We walk backwards from the
|
|
158
|
+
# producer instruction, render only shapes we understand, and return the
|
|
159
|
+
# previous instruction index so callers can continue rendering earlier
|
|
160
|
+
# stack values without evaluating user code.
|
|
161
|
+
def self.render_expression_ending_at(body, index)
|
|
162
|
+
return nil unless index
|
|
163
|
+
|
|
164
|
+
index = previous_instruction_index(body, index)
|
|
165
|
+
return nil unless index
|
|
166
|
+
|
|
167
|
+
instruction = body[index]
|
|
168
|
+
opcode = instruction[0]
|
|
169
|
+
|
|
170
|
+
case opcode
|
|
171
|
+
when :putself
|
|
172
|
+
[SELF_VALUE, index - 1]
|
|
173
|
+
when :putnil
|
|
174
|
+
["nil", index - 1]
|
|
175
|
+
when :putobject, :putchilledstring, :putstring
|
|
176
|
+
[render_literal(instruction[1]), index - 1]
|
|
177
|
+
when :duparray
|
|
178
|
+
[render_array_literal(instruction[1]), index - 1]
|
|
179
|
+
when :duphash
|
|
180
|
+
[render_hash_literal(instruction[1]), index - 1]
|
|
181
|
+
when :newarray
|
|
182
|
+
render_new_array_expression(body, index)
|
|
183
|
+
when :newhash
|
|
184
|
+
render_new_hash_expression(body, index)
|
|
185
|
+
when :opt_getconstant_path
|
|
186
|
+
[render_constant_path(instruction[1]), index - 1]
|
|
187
|
+
when :pop
|
|
188
|
+
render_optimized_new_expression(body, index)
|
|
189
|
+
when :send, :opt_send_without_block, :opt_plus, :opt_minus, :opt_mult, :opt_div,
|
|
190
|
+
:opt_mod, :opt_eq, :opt_neq, :opt_lt, :opt_le, :opt_gt, :opt_ge
|
|
191
|
+
render_send_expression(body, index, instruction)
|
|
192
|
+
else
|
|
193
|
+
render_special_expression(index, instruction)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
private_class_method :render_expression_ending_at
|
|
197
|
+
|
|
198
|
+
# `newarray` consumes N already-pushed stack values. Render those values
|
|
199
|
+
# backwards, then restore source order so matcher names read like Ruby.
|
|
200
|
+
def self.render_new_array_expression(body, index)
|
|
201
|
+
expression = render_expression_list_ending_at(body, index - 1, body[index][1].to_i)
|
|
202
|
+
return nil unless expression
|
|
203
|
+
|
|
204
|
+
[render_array_literal(expression[0]), expression[1]]
|
|
205
|
+
end
|
|
206
|
+
private_class_method :render_new_array_expression
|
|
207
|
+
|
|
208
|
+
# `newhash` consumes alternating key/value stack entries. We render the
|
|
209
|
+
# pairs instead of inspecting a runtime Hash, keeping generated names
|
|
210
|
+
# deterministic even when the original values are local variables.
|
|
211
|
+
def self.render_new_hash_expression(body, index)
|
|
212
|
+
expression = render_expression_list_ending_at(body, index - 1, body[index][1].to_i)
|
|
213
|
+
return nil unless expression
|
|
214
|
+
|
|
215
|
+
values = expression[0]
|
|
216
|
+
pairs = values.each_slice(2).map { |key, value| "#{key} => #{value}" }
|
|
217
|
+
["{#{pairs.join(", ")}}", expression[1]]
|
|
218
|
+
end
|
|
219
|
+
private_class_method :render_new_hash_expression
|
|
220
|
+
|
|
221
|
+
def self.render_optimized_new_expression(body, index)
|
|
222
|
+
# Ruby 4 compiles zero-argument `Class.new` with `opt_new` followed by
|
|
223
|
+
# constructor bookkeeping and a final `pop`. Collapse that whole shape
|
|
224
|
+
# back to the receiver class so `Object.new` renders as stable `Object`.
|
|
225
|
+
swap_index = previous_instruction_index(body, index - 1)
|
|
226
|
+
return nil unless swap_index
|
|
227
|
+
return nil unless body[swap_index][0] == :swap
|
|
228
|
+
|
|
229
|
+
receiver_index = optimized_new_receiver_index(body, swap_index - 1)
|
|
230
|
+
return nil unless receiver_index
|
|
231
|
+
|
|
232
|
+
receiver_expression = render_expression_ending_at(body, receiver_index)
|
|
233
|
+
return nil unless receiver_expression
|
|
234
|
+
|
|
235
|
+
[render_send(receiver_expression[0], :new, EMPTY_ARGUMENTS, nil), receiver_expression[1]]
|
|
236
|
+
end
|
|
237
|
+
private_class_method :render_optimized_new_expression
|
|
238
|
+
|
|
239
|
+
# Ruby 4's optimized constructor bytecode places the receiver before
|
|
240
|
+
# `putnil`, `swap`, and `opt_new`. Find that receiver so the outer `pop`
|
|
241
|
+
# handler can render `Object.new` as the stable class name `Object`.
|
|
242
|
+
def self.optimized_new_receiver_index(body, index)
|
|
243
|
+
while index >= 0
|
|
244
|
+
entry = body[index]
|
|
245
|
+
|
|
246
|
+
if entry.is_a?(Array) && entry[0] == :opt_new
|
|
247
|
+
call_data = entry[1]
|
|
248
|
+
if call_data.is_a?(Hash) && call_data[:mid] == :new && call_data[:orig_argc].to_i.zero?
|
|
249
|
+
swap_index = previous_instruction_index(body, index - 1)
|
|
250
|
+
return nil unless swap_index
|
|
251
|
+
return nil unless body[swap_index][0] == :swap
|
|
252
|
+
|
|
253
|
+
nil_index = previous_instruction_index(body, swap_index - 1)
|
|
254
|
+
return nil unless nil_index
|
|
255
|
+
return nil unless body[nil_index][0] == :putnil
|
|
256
|
+
|
|
257
|
+
return previous_instruction_index(body, nil_index - 1)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
index -= 1
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
private_class_method :optimized_new_receiver_index
|
|
267
|
+
|
|
268
|
+
# Method calls consume arguments first and the receiver last. Render that
|
|
269
|
+
# stack shape backwards, then format the send as matcher-style text when
|
|
270
|
+
# it belongs to RSpec's expectation DSL.
|
|
271
|
+
def self.render_send_expression(body, index, instruction)
|
|
272
|
+
call_data = instruction[1]
|
|
273
|
+
return nil unless call_data.is_a?(Hash)
|
|
274
|
+
|
|
275
|
+
arguments_expression = render_expression_list_ending_at(body, index - 1, call_data[:orig_argc].to_i)
|
|
276
|
+
return nil unless arguments_expression
|
|
277
|
+
|
|
278
|
+
arguments = arguments_expression[0]
|
|
279
|
+
receiver_end_index = arguments_expression[1]
|
|
280
|
+
|
|
281
|
+
receiver_index = previous_instruction_index(body, receiver_end_index)
|
|
282
|
+
return nil unless receiver_index
|
|
283
|
+
|
|
284
|
+
receiver_instruction = body[receiver_index]
|
|
285
|
+
if receiver_instruction[0] == :putself
|
|
286
|
+
receiver = SELF_VALUE
|
|
287
|
+
previous_index = receiver_index - 1
|
|
288
|
+
else
|
|
289
|
+
receiver_expression = render_expression_ending_at(body, receiver_index)
|
|
290
|
+
return nil unless receiver_expression
|
|
291
|
+
|
|
292
|
+
receiver = receiver_expression[0]
|
|
293
|
+
previous_index = receiver_expression[1]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
block_iseq = instruction[2] if iseq_array?(instruction[2])
|
|
297
|
+
rendered_send = render_send(receiver, call_data[:mid], arguments, block_iseq)
|
|
298
|
+
|
|
299
|
+
[rendered_send, previous_index]
|
|
300
|
+
end
|
|
301
|
+
private_class_method :render_send_expression
|
|
302
|
+
|
|
303
|
+
# Render `count` adjacent stack expressions ending at `index`.
|
|
304
|
+
# Arguments appear on the stack left-to-right, but because we scan from
|
|
305
|
+
# the end, each rendered value is written back into its original slot.
|
|
306
|
+
def self.render_expression_list_ending_at(body, index, count)
|
|
307
|
+
return [EMPTY_ARGUMENTS, index] if count <= 0
|
|
308
|
+
|
|
309
|
+
arguments = Array.new(count)
|
|
310
|
+
argument_index = count - 1
|
|
311
|
+
|
|
312
|
+
while argument_index >= 0
|
|
313
|
+
expression = render_expression_ending_at(body, index)
|
|
314
|
+
return nil unless expression
|
|
315
|
+
|
|
316
|
+
arguments[argument_index] = expression[0]
|
|
317
|
+
index = expression[1]
|
|
318
|
+
argument_index -= 1
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
[arguments, index]
|
|
322
|
+
end
|
|
323
|
+
private_class_method :render_expression_list_ending_at
|
|
324
|
+
|
|
325
|
+
# Instruction sequence bodies also contain labels and events. Only array
|
|
326
|
+
# entries are executable instructions for the subset we render here.
|
|
327
|
+
def self.previous_instruction_index(body, index)
|
|
328
|
+
while index >= 0
|
|
329
|
+
return index if body[index].is_a?(Array)
|
|
330
|
+
|
|
331
|
+
index -= 1
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
nil
|
|
335
|
+
end
|
|
336
|
+
private_class_method :previous_instruction_index
|
|
337
|
+
|
|
338
|
+
# Handle compact Ruby opcodes that are common in tiny matcher arguments.
|
|
339
|
+
# Locals are intentionally rendered as `local`; reading their runtime
|
|
340
|
+
# values would make names unstable and could execute user code.
|
|
341
|
+
def self.render_special_expression(index, instruction)
|
|
342
|
+
opcode = instruction[0].to_s
|
|
343
|
+
|
|
344
|
+
if opcode.start_with?("putobject_INT2FIX_")
|
|
345
|
+
[opcode.delete_prefix("putobject_INT2FIX_").delete_suffix("_"), index - 1]
|
|
346
|
+
elsif opcode.start_with?("getlocal")
|
|
347
|
+
["local", index - 1]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
private_class_method :render_special_expression
|
|
351
|
+
|
|
352
|
+
# Convert a rendered send into the text users expect from an RSpec
|
|
353
|
+
# generated description. For constructor calls on constants, keep only
|
|
354
|
+
# the class name so `Object.new` does not introduce object identity.
|
|
355
|
+
def self.render_send(receiver, method_name, arguments, block_iseq)
|
|
356
|
+
if receiver == SELF_VALUE && arguments.empty? && !block_iseq
|
|
357
|
+
return render_method_name(receiver, method_name, arguments, block_iseq)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
return receiver if method_name == :new && arguments.empty? && constant_name?(receiver)
|
|
361
|
+
|
|
362
|
+
render_method_call(receiver, method_name, arguments, block_iseq)
|
|
363
|
+
end
|
|
364
|
+
private_class_method :render_send
|
|
365
|
+
|
|
366
|
+
# Format non-trivial sends. Matcher calls and matcher chains use RSpec's
|
|
367
|
+
# human-readable style (`include "x"`, `change by 1`); ordinary nested
|
|
368
|
+
# sends keep Ruby-ish receiver syntax (`local.to_s`) for clarity.
|
|
369
|
+
def self.render_method_call(receiver, method_name, arguments, block_iseq)
|
|
370
|
+
matcher_chain = MATCHER_CHAIN_METHODS.key?(method_name)
|
|
371
|
+
method_text = render_method_name(receiver, method_name, arguments, block_iseq)
|
|
372
|
+
argument_text = arguments.join(", ")
|
|
373
|
+
receiver_text = (receiver == SELF_VALUE) ? "self" : receiver.to_s
|
|
374
|
+
|
|
375
|
+
if OPERATOR_METHODS.key?(method_name)
|
|
376
|
+
return "#{receiver_text} #{method_name} #{argument_text}".strip
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
if receiver == SELF_VALUE
|
|
380
|
+
return argument_text.empty? ? method_text : "#{method_text} #{argument_text}"
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
if matcher_chain
|
|
384
|
+
return argument_text.empty? ? "#{receiver} #{method_text}" : "#{receiver} #{method_text} #{argument_text}"
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
argument_suffix = argument_text.empty? ? "" : "(#{argument_text})"
|
|
388
|
+
"#{receiver_text}.#{method_name}#{argument_suffix}"
|
|
389
|
+
end
|
|
390
|
+
private_class_method :render_method_call
|
|
391
|
+
|
|
392
|
+
# RSpec generated descriptions turn matcher-like method names into prose
|
|
393
|
+
# only when the send is part of matcher DSL text. Keep ordinary nested
|
|
394
|
+
# Ruby calls (`local.to_s`) untouched.
|
|
395
|
+
def self.render_method_name(receiver, method_name, arguments, block_iseq)
|
|
396
|
+
method_text = method_name.to_s
|
|
397
|
+
return method_text unless humanize_method_name?(receiver, method_name, method_text, arguments, block_iseq)
|
|
398
|
+
|
|
399
|
+
method_text.tr("_", " ")
|
|
400
|
+
end
|
|
401
|
+
private_class_method :render_method_name
|
|
402
|
+
|
|
403
|
+
def self.humanize_method_name?(receiver, method_name, method_text, arguments, block_iseq)
|
|
404
|
+
return false unless receiver == SELF_VALUE || MATCHER_CHAIN_METHODS.key?(method_name) || block_iseq
|
|
405
|
+
|
|
406
|
+
pretty_matcher_method?(method_name, method_text) || !arguments.empty? || block_iseq
|
|
407
|
+
end
|
|
408
|
+
private_class_method :humanize_method_name?
|
|
409
|
+
|
|
410
|
+
# RSpec turns many matcher method names into words in generated example
|
|
411
|
+
# descriptions. Mirror that only for known matcher methods/prefixes.
|
|
412
|
+
def self.pretty_matcher_method?(method_name, method_text)
|
|
413
|
+
PRETTY_MATCHER_METHODS.key?(method_name) ||
|
|
414
|
+
PRETTY_MATCHER_PREFIXES.any? { |prefix| method_text.start_with?(prefix) }
|
|
415
|
+
end
|
|
416
|
+
private_class_method :pretty_matcher_method?
|
|
417
|
+
|
|
418
|
+
# Literal operands are embedded in bytecode and safe to read. For any
|
|
419
|
+
# object-like value, use the class name instead of `inspect` so memory
|
|
420
|
+
# addresses cannot leak into test identities.
|
|
421
|
+
def self.render_literal(value)
|
|
422
|
+
rendered_value =
|
|
423
|
+
case value
|
|
424
|
+
when nil
|
|
425
|
+
"nil"
|
|
426
|
+
when true
|
|
427
|
+
"true"
|
|
428
|
+
when false
|
|
429
|
+
"false"
|
|
430
|
+
when Symbol
|
|
431
|
+
value.inspect
|
|
432
|
+
when String
|
|
433
|
+
value.inspect
|
|
434
|
+
when Numeric
|
|
435
|
+
value.to_s
|
|
436
|
+
when Regexp
|
|
437
|
+
value.inspect
|
|
438
|
+
when Array
|
|
439
|
+
render_array_literal(value)
|
|
440
|
+
when Hash
|
|
441
|
+
render_hash_literal(value)
|
|
442
|
+
else
|
|
443
|
+
value.class.name || "value"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
trim(rendered_value, MAX_RENDERED_VALUE_LENGTH)
|
|
447
|
+
end
|
|
448
|
+
private_class_method :render_literal
|
|
449
|
+
|
|
450
|
+
# Keep array literals readable while recursively applying the same stable
|
|
451
|
+
# rendering rules to their elements.
|
|
452
|
+
def self.render_array_literal(value)
|
|
453
|
+
return "[]" unless value.is_a?(Array)
|
|
454
|
+
|
|
455
|
+
"[#{value.map { |element| render_literal(element) }.join(", ")}]"
|
|
456
|
+
end
|
|
457
|
+
private_class_method :render_array_literal
|
|
458
|
+
|
|
459
|
+
# Sort hash keys so equivalent literal hashes render consistently across
|
|
460
|
+
# Ruby versions and construction paths.
|
|
461
|
+
def self.render_hash_literal(value)
|
|
462
|
+
return "{}" unless value.is_a?(Hash)
|
|
463
|
+
|
|
464
|
+
pairs = value.keys.sort_by(&:to_s).map do |key|
|
|
465
|
+
"#{render_literal(key)} => #{render_literal(value[key])}"
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
"{#{pairs.join(", ")}}"
|
|
469
|
+
end
|
|
470
|
+
private_class_method :render_hash_literal
|
|
471
|
+
|
|
472
|
+
# `opt_getconstant_path` stores constants as path parts. Join them into a
|
|
473
|
+
# Ruby-looking constant name without resolving the constant at runtime.
|
|
474
|
+
def self.render_constant_path(value)
|
|
475
|
+
return "constant" unless value.is_a?(Array)
|
|
476
|
+
|
|
477
|
+
value.compact.join("::")
|
|
478
|
+
end
|
|
479
|
+
private_class_method :render_constant_path
|
|
480
|
+
|
|
481
|
+
# Used when collapsing zero-argument constructor calls; only collapse
|
|
482
|
+
# real constant paths, not arbitrary receiver text.
|
|
483
|
+
def self.constant_name?(value)
|
|
484
|
+
value.is_a?(String) && value.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/)
|
|
485
|
+
end
|
|
486
|
+
private_class_method :constant_name?
|
|
487
|
+
|
|
488
|
+
# Block matchers carry the block body as an embedded instruction
|
|
489
|
+
# sequence. Its presence changes matcher wording, but rendering the
|
|
490
|
+
# block body would risk pulling subject code into the name.
|
|
491
|
+
def self.iseq_array?(value)
|
|
492
|
+
value.is_a?(Array) && value[0] == ISEQ_SIMPLE_DATA_FORMAT
|
|
493
|
+
end
|
|
494
|
+
private_class_method :iseq_array?
|
|
495
|
+
|
|
496
|
+
# Bound rendered values and names so unusually large literals do not
|
|
497
|
+
# create oversized span names.
|
|
498
|
+
def self.trim(value, max_length)
|
|
499
|
+
return value if value.length <= max_length
|
|
500
|
+
|
|
501
|
+
"#{value[0, max_length - 3]}..."
|
|
502
|
+
end
|
|
503
|
+
private_class_method :trim
|
|
504
|
+
|
|
505
|
+
def self.warn_anonymous_example_name_error(error)
|
|
506
|
+
Datadog.logger.warn { "Unable to compute RSpec anonymous example name: #{error.class}: #{error.message}" }
|
|
507
|
+
|
|
508
|
+
nil
|
|
509
|
+
end
|
|
510
|
+
private_class_method :warn_anonymous_example_name_error
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
end
|
|
@@ -7,6 +7,7 @@ require_relative "../../source_code/path_filter"
|
|
|
7
7
|
require_relative "../../utils/bundle"
|
|
8
8
|
require_relative "../../utils/test_run"
|
|
9
9
|
require_relative "../instrumentation"
|
|
10
|
+
require_relative "anonymous_example_name"
|
|
10
11
|
require_relative "ext"
|
|
11
12
|
|
|
12
13
|
module Datadog
|
|
@@ -110,11 +111,7 @@ module Datadog
|
|
|
110
111
|
def datadog_test_name
|
|
111
112
|
return @datadog_test_name if defined?(@datadog_test_name)
|
|
112
113
|
|
|
113
|
-
test_name = full_description.strip
|
|
114
|
-
if metadata[:description].empty?
|
|
115
|
-
# for unnamed it blocks this appends something like "example at ./spec/some_spec.rb:10"
|
|
116
|
-
test_name << " #{description}"
|
|
117
|
-
end
|
|
114
|
+
test_name = datadog_unnamed_example? ? datadog_unnamed_example_name : full_description.strip
|
|
118
115
|
|
|
119
116
|
# remove example group description from test name to avoid duplication
|
|
120
117
|
test_name = test_name.sub(datadog_test_suite_description, "").strip
|
|
@@ -266,6 +263,27 @@ module Datadog
|
|
|
266
263
|
end
|
|
267
264
|
end
|
|
268
265
|
|
|
266
|
+
# ============================================
|
|
267
|
+
# Test name helpers
|
|
268
|
+
# ============================================
|
|
269
|
+
|
|
270
|
+
def datadog_unnamed_example?
|
|
271
|
+
# @type var description_args: Array[untyped]
|
|
272
|
+
description_args = Array(metadata[:description_args])
|
|
273
|
+
description_args.empty? || description_args.all? { |description_arg| description_arg.to_s.strip.empty? }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def datadog_unnamed_example_name
|
|
277
|
+
example_name = AnonymousExampleName.call(datadog_anonymous_example_block)
|
|
278
|
+
example_name ||= "anonymous example"
|
|
279
|
+
|
|
280
|
+
"#{metadata[:example_group][:full_description]} #{example_name}"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def datadog_anonymous_example_block
|
|
284
|
+
metadata[:block] || @example_block
|
|
285
|
+
end
|
|
286
|
+
|
|
269
287
|
# ============================================
|
|
270
288
|
# Source location resolution
|
|
271
289
|
# ============================================
|
|
@@ -22,7 +22,15 @@ module Datadog
|
|
|
22
22
|
::SimpleCov::ResultAdapter.call(::Coverage.peek_result)
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# SimpleCov 1.0 changed add_not_loaded_files to return a
|
|
26
|
+
# [coverage, not_loaded_files] tuple instead of a coverage hash.
|
|
27
|
+
processed = add_not_loaded_files(result)
|
|
28
|
+
if processed.is_a?(::Array)
|
|
29
|
+
coverage, not_loaded_files = processed
|
|
30
|
+
::SimpleCov::Result.new(coverage, not_loaded_files: not_loaded_files)
|
|
31
|
+
else
|
|
32
|
+
::SimpleCov::Result.new(processed)
|
|
33
|
+
end
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def datadog_configuration
|
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require_relative "../ext/test"
|
|
6
6
|
require_relative "../ext/test_discovery"
|
|
7
|
+
require_relative "../utils/test_name"
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
9
10
|
module CI
|
|
@@ -76,6 +77,9 @@ module Datadog
|
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def record_test(name:, suite:, module_name:, parameters:, source_file:)
|
|
80
|
+
name = name.nil? ? nil : Utils::TestName.normalize(name)
|
|
81
|
+
suite = suite.nil? ? nil : Utils::TestName.normalize(suite)
|
|
82
|
+
|
|
79
83
|
test_info = {
|
|
80
84
|
"name" => name,
|
|
81
85
|
"suite" => suite,
|
|
@@ -16,6 +16,7 @@ require_relative "../ext/test"
|
|
|
16
16
|
require_relative "../git/local_repository"
|
|
17
17
|
require_relative "../utils/file_storage"
|
|
18
18
|
require_relative "../utils/stateful"
|
|
19
|
+
require_relative "../utils/test_name"
|
|
19
20
|
|
|
20
21
|
require_relative "../worker"
|
|
21
22
|
|
|
@@ -109,6 +110,7 @@ module Datadog
|
|
|
109
110
|
def start_test_suite(test_suite_name, service: nil, tags: {})
|
|
110
111
|
return skip_tracing unless test_suite_level_visibility_enabled
|
|
111
112
|
|
|
113
|
+
test_suite_name = Utils::TestName.normalize(test_suite_name)
|
|
112
114
|
context = @local_test_suites_mode ? @context : maybe_remote_context
|
|
113
115
|
|
|
114
116
|
test_suite = context.start_test_suite(test_suite_name, service: service, tags: tags)
|
|
@@ -117,6 +119,9 @@ module Datadog
|
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
def trace_test(test_name, test_suite_name, service: nil, tags: {}, &block)
|
|
122
|
+
test_name = Utils::TestName.normalize(test_name)
|
|
123
|
+
test_suite_name = Utils::TestName.normalize(test_suite_name)
|
|
124
|
+
|
|
120
125
|
test_suite = active_test_suite(test_suite_name)
|
|
121
126
|
tags[Ext::Test::TAG_SUITE] ||= test_suite_name
|
|
122
127
|
|
|
@@ -165,6 +170,8 @@ module Datadog
|
|
|
165
170
|
# we return the single active test suite because most of the time there is only one test suite running
|
|
166
171
|
return single_active_test_suite if test_suite_name.nil?
|
|
167
172
|
|
|
173
|
+
test_suite_name = Utils::TestName.normalize(test_suite_name)
|
|
174
|
+
|
|
168
175
|
# when fetching test_suite to use as test's context, try local context instance first
|
|
169
176
|
local_test_suite = @context.active_test_suite(test_suite_name)
|
|
170
177
|
return local_test_suite if local_test_suite
|
|
@@ -194,6 +201,7 @@ module Datadog
|
|
|
194
201
|
end
|
|
195
202
|
|
|
196
203
|
def deactivate_test_suite(test_suite_name)
|
|
204
|
+
test_suite_name = Utils::TestName.normalize(test_suite_name)
|
|
197
205
|
test_suite = active_test_suite(test_suite_name)
|
|
198
206
|
on_test_suite_finished(test_suite) if test_suite
|
|
199
207
|
|
|
@@ -12,7 +12,7 @@ module Datadog
|
|
|
12
12
|
return
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
unless
|
|
15
|
+
unless simplecov_running?
|
|
16
16
|
Datadog.logger.debug("SimpleCov is not running, skipping code coverage extraction")
|
|
17
17
|
return
|
|
18
18
|
end
|
|
@@ -30,6 +30,16 @@ module Datadog
|
|
|
30
30
|
|
|
31
31
|
test_session.set_tag(Ext::Test::TAG_CODE_COVERAGE_LINES_PCT, result.covered_percent)
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
# SimpleCov 1.0 removed the `running` accessor and relies on Ruby's
|
|
35
|
+
# Coverage.running? instead.
|
|
36
|
+
def self.simplecov_running?
|
|
37
|
+
return ::SimpleCov.running if ::SimpleCov.respond_to?(:running)
|
|
38
|
+
return false unless defined?(::Coverage)
|
|
39
|
+
|
|
40
|
+
::Coverage.running?
|
|
41
|
+
end
|
|
42
|
+
private_class_method :simplecov_running?
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module CI
|
|
5
|
+
module Utils
|
|
6
|
+
module TestName
|
|
7
|
+
MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"
|
|
8
|
+
WEEKDAY = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)"
|
|
9
|
+
CONSTANT_NAME = "[A-Z]\\w*(?:::[A-Z]\\w*)*"
|
|
10
|
+
|
|
11
|
+
DATE_INSPECT_PATTERN = /#<Date(?::[^>\n]*)?>/.freeze
|
|
12
|
+
TIME_INSPECT_PATTERN = /#<(?:DateTime|Time)(?::[^>\n]*)?>/.freeze
|
|
13
|
+
CLASS_INSPECT_PATTERN = /#<Class:(#{CONSTANT_NAME}|0x[0-9a-fA-F]+)(?:\s[^>\n]*)?>/.freeze
|
|
14
|
+
NAMED_OBJECT_INSPECT_PATTERN = /#<(#{CONSTANT_NAME})(?::[^>\n]*)?>/.freeze
|
|
15
|
+
OBJECT_INSPECT_PATTERN = /#<[^>\n]+>/.freeze
|
|
16
|
+
ACTIVE_RECORD_CLASS_INSPECT_PATTERN = /
|
|
17
|
+
\b(#{CONSTANT_NAME})
|
|
18
|
+
(?:
|
|
19
|
+
\s\(call\s'\1\.connection'\sto\ establish\ a\ connection\)
|
|
20
|
+
|
|
|
21
|
+
\(
|
|
22
|
+
(?:\.\.\.\s*)?[a-z_][a-z0-9_]*:\s[^,)\n]*
|
|
23
|
+
(?:
|
|
24
|
+
,\s(?:\.\.\.\s*)?[a-z_][a-z0-9_]*:\s[^,)\n]*
|
|
25
|
+
)*
|
|
26
|
+
\)
|
|
27
|
+
)
|
|
28
|
+
/x.freeze
|
|
29
|
+
|
|
30
|
+
TIME_PATTERN = /
|
|
31
|
+
\b
|
|
32
|
+
(?:
|
|
33
|
+
\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?
|
|
34
|
+
(?:\s?(?:Z|UTC|[A-Z]{2,5}|[+-]\d{2}:?\d{2}))?
|
|
35
|
+
|
|
|
36
|
+
#{WEEKDAY},?\s+\d{1,2}\s+#{MONTH}\s+\d{4}\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?
|
|
37
|
+
(?:\s+(?:UTC|[A-Z]{2,5}|[+-]\d{2}:?\d{2}))?
|
|
38
|
+
)
|
|
39
|
+
\b
|
|
40
|
+
/x.freeze
|
|
41
|
+
DATE_PATTERN = /
|
|
42
|
+
\b
|
|
43
|
+
(?:
|
|
44
|
+
\d{4}-\d{2}-\d{2}
|
|
45
|
+
|
|
|
46
|
+
#{WEEKDAY},?\s+\d{1,2}\s+#{MONTH}\s+\d{4}
|
|
47
|
+
)
|
|
48
|
+
\b
|
|
49
|
+
/x.freeze
|
|
50
|
+
RANGE_PATTERN = /
|
|
51
|
+
(^|[^\w.])
|
|
52
|
+
(?:-?\d+(?:\.\d+)?|DATE|TIME|:[a-zA-Z_]\w*|"[^"]*"|'[^']*')
|
|
53
|
+
\s*\.{2,3}\s*
|
|
54
|
+
(?:-?\d+(?:\.\d+)?|DATE|TIME|:[a-zA-Z_]\w*|"[^"]*"|'[^']*')
|
|
55
|
+
(?=$|[^\w.])
|
|
56
|
+
/x.freeze
|
|
57
|
+
|
|
58
|
+
def self.normalize(name)
|
|
59
|
+
return name unless name.is_a?(String)
|
|
60
|
+
return name if name.empty?
|
|
61
|
+
|
|
62
|
+
normalized = name.gsub(DATE_INSPECT_PATTERN, "DATE")
|
|
63
|
+
normalized.gsub!(TIME_INSPECT_PATTERN, "TIME")
|
|
64
|
+
normalized.gsub!(CLASS_INSPECT_PATTERN) do
|
|
65
|
+
class_name = Regexp.last_match(1).to_s
|
|
66
|
+
class_name.start_with?("0x") ? "CLASS" : "CLASS:#{class_name}"
|
|
67
|
+
end
|
|
68
|
+
normalized.gsub!(NAMED_OBJECT_INSPECT_PATTERN) { "OBJECT:#{Regexp.last_match(1)}" }
|
|
69
|
+
normalized.gsub!(OBJECT_INSPECT_PATTERN, "OBJECT")
|
|
70
|
+
normalized.gsub!(ACTIVE_RECORD_CLASS_INSPECT_PATTERN) { Regexp.last_match(1).to_s }
|
|
71
|
+
normalized.gsub!(ARRAY_PATTERN) { |value| array_literal?(value) ? "ARRAY" : value }
|
|
72
|
+
normalized.gsub!(HASH_PATTERN) { |value| hash_literal?(value) ? "HASH" : value }
|
|
73
|
+
normalized.gsub!(TIME_PATTERN, "TIME")
|
|
74
|
+
normalized.gsub!(DATE_PATTERN, "DATE")
|
|
75
|
+
normalized.gsub!(RANGE_PATTERN) { "#{Regexp.last_match(1)}RANGE" }
|
|
76
|
+
|
|
77
|
+
normalized
|
|
78
|
+
rescue => e
|
|
79
|
+
warn_normalization_error(e)
|
|
80
|
+
name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ARRAY_PATTERN = /\[[^\[\]\n]{0,300}\]/.freeze
|
|
84
|
+
HASH_PATTERN = /\{[^{}\n]{0,300}\}/.freeze
|
|
85
|
+
|
|
86
|
+
def self.array_literal?(value)
|
|
87
|
+
content = value[1, value.length - 2].to_s.strip
|
|
88
|
+
return true if content.empty?
|
|
89
|
+
|
|
90
|
+
content.include?(",") || content.match?(/\A(?:"|'|:|-?\d|true\b|false\b|nil\b|#<|\[|\{)/)
|
|
91
|
+
end
|
|
92
|
+
private_class_method :array_literal?
|
|
93
|
+
|
|
94
|
+
def self.hash_literal?(value)
|
|
95
|
+
content = value[1, value.length - 2].to_s.strip
|
|
96
|
+
return true if content.empty?
|
|
97
|
+
|
|
98
|
+
content.include?("=>") ||
|
|
99
|
+
content.match?(/(?:\A|,)\s*(?:"[^"]+"|'[^']+'|:[a-zA-Z_]\w*|[a-zA-Z_]\w*)\s*:/)
|
|
100
|
+
end
|
|
101
|
+
private_class_method :hash_literal?
|
|
102
|
+
|
|
103
|
+
def self.warn_normalization_error(error)
|
|
104
|
+
Datadog.logger.warn { "Unable to normalize test name: #{error.class}: #{error.message}" }
|
|
105
|
+
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
private_class_method :warn_normalization_error
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/datadog/ci/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: datadog-ci
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.35.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Datadog, Inc.
|
|
@@ -151,6 +151,7 @@ files:
|
|
|
151
151
|
- lib/datadog/ci/contrib/parallel_tests/integration.rb
|
|
152
152
|
- lib/datadog/ci/contrib/parallel_tests/patcher.rb
|
|
153
153
|
- lib/datadog/ci/contrib/patcher.rb
|
|
154
|
+
- lib/datadog/ci/contrib/rspec/anonymous_example_name.rb
|
|
154
155
|
- lib/datadog/ci/contrib/rspec/configuration/settings.rb
|
|
155
156
|
- lib/datadog/ci/contrib/rspec/documentation_formatter.rb
|
|
156
157
|
- lib/datadog/ci/contrib/rspec/example.rb
|
|
@@ -322,6 +323,7 @@ files:
|
|
|
322
323
|
- lib/datadog/ci/utils/runtime_tags_overrides.rb
|
|
323
324
|
- lib/datadog/ci/utils/stateful.rb
|
|
324
325
|
- lib/datadog/ci/utils/telemetry.rb
|
|
326
|
+
- lib/datadog/ci/utils/test_name.rb
|
|
325
327
|
- lib/datadog/ci/utils/test_run.rb
|
|
326
328
|
- lib/datadog/ci/version.rb
|
|
327
329
|
- lib/datadog/ci/worker.rb
|