Skip to content

Commit a7c5f81

Browse files
authored
DEBUG-2334 Ruby 2 compatibility for dynamic instrumentation (#4120)
1 parent 09cc6dc commit a7c5f81

File tree

3 files changed

+130
-10
lines changed

3 files changed

+130
-10
lines changed

lib/datadog/di/instrumenter.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,19 @@ def hook_method(probe, &block)
105105
serializer.serialize_args(args, kwargs)
106106
end
107107
rv = nil
108+
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
108109
duration = Benchmark.realtime do # steep:ignore
109-
rv = super(*args, **kwargs)
110+
rv = if args.any?
111+
if kwargs.any?
112+
super(*args, **kwargs)
113+
else
114+
super(*args)
115+
end
116+
elsif kwargs.any?
117+
super(**kwargs)
118+
else
119+
super()
120+
end
110121
end
111122
# The method itself is not part of the stack trace because
112123
# we are getting the stack trace from outside of the method.

spec/datadog/di/hook_method.rb

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ def hook_test_method_with_kwarg(kwarg:)
1111
kwarg
1212
end
1313

14+
def hook_test_method_with_pos_and_kwarg(arg, kwarg:)
15+
[arg, kwarg]
16+
end
17+
1418
def recursive(depth)
1519
if depth > 0
1620
recursive(depth - 1) + '-'

spec/datadog/di/instrumenter_spec.rb

+114-9
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,124 @@
102102
capture_snapshot: true}
103103
end
104104

105-
it 'invokes callback and captures parameters' do
106-
instrumenter.hook_method(probe) do |payload|
107-
observed_calls << payload
105+
let(:target_call) do
106+
expect(HookTestClass.new.hook_test_method_with_arg(2)).to eq 2
107+
end
108+
109+
shared_examples 'invokes callback and captures parameters' do
110+
it 'invokes callback and captures parameters' do
111+
instrumenter.hook_method(probe) do |payload|
112+
observed_calls << payload
113+
end
114+
115+
target_call
116+
117+
expect(observed_calls.length).to eq 1
118+
expect(observed_calls.first.keys.sort).to eq call_keys
119+
expect(observed_calls.first[:rv]).to eq 2
120+
expect(observed_calls.first[:duration]).to be_a(Float)
121+
122+
expect(observed_calls.first[:serialized_entry_args]).to eq(arg1: {type: 'Integer', value: '2'})
108123
end
124+
end
109125

110-
expect(HookTestClass.new.hook_test_method_with_arg(2)).to eq 2
126+
include_examples 'invokes callback and captures parameters'
111127

112-
expect(observed_calls.length).to eq 1
113-
expect(observed_calls.first.keys.sort).to eq call_keys
114-
expect(observed_calls.first[:rv]).to eq 2
115-
expect(observed_calls.first[:duration]).to be_a(Float)
128+
context 'when passed via a splat' do
129+
let(:target_call) do
130+
args = [2]
131+
expect(HookTestClass.new.hook_test_method_with_arg(*args)).to eq 2
132+
end
133+
134+
include_examples 'invokes callback and captures parameters'
135+
end
136+
end
137+
end
138+
139+
context 'keyword args' do
140+
context 'with snapshot capture' do
141+
let(:probe_args) do
142+
{type_name: 'HookTestClass', method_name: 'hook_test_method_with_kwarg',
143+
capture_snapshot: true}
144+
end
145+
146+
let(:target_call) do
147+
expect(HookTestClass.new.hook_test_method_with_kwarg(kwarg: 42)).to eq 42
148+
end
149+
150+
shared_examples 'invokes callback and captures parameters' do
151+
it 'invokes callback and captures parameters' do
152+
instrumenter.hook_method(probe) do |payload|
153+
observed_calls << payload
154+
end
155+
156+
target_call
157+
158+
expect(observed_calls.length).to eq 1
159+
expect(observed_calls.first.keys.sort).to eq call_keys
160+
expect(observed_calls.first[:rv]).to eq 42
161+
expect(observed_calls.first[:duration]).to be_a(Float)
162+
163+
expect(observed_calls.first[:serialized_entry_args]).to eq(kwarg: {type: 'Integer', value: '42'})
164+
end
165+
end
166+
167+
include_examples 'invokes callback and captures parameters'
168+
169+
context 'when passed via a splat' do
170+
let(:target_call) do
171+
kwargs = {kwarg: 42}
172+
expect(HookTestClass.new.hook_test_method_with_kwarg(**kwargs)).to eq 42
173+
end
174+
175+
include_examples 'invokes callback and captures parameters'
176+
end
177+
end
178+
end
179+
180+
context 'positional and keyword args' do
181+
context 'with snapshot capture' do
182+
let(:probe_args) do
183+
{type_name: 'HookTestClass', method_name: 'hook_test_method_with_pos_and_kwarg',
184+
capture_snapshot: true}
185+
end
186+
187+
let(:target_call) do
188+
expect(HookTestClass.new.hook_test_method_with_pos_and_kwarg(41, kwarg: 42)).to eq [41, 42]
189+
end
190+
191+
shared_examples 'invokes callback and captures parameters' do
192+
it 'invokes callback and captures parameters' do
193+
instrumenter.hook_method(probe) do |payload|
194+
observed_calls << payload
195+
end
196+
197+
target_call
198+
199+
expect(observed_calls.length).to eq 1
200+
expect(observed_calls.first.keys.sort).to eq call_keys
201+
expect(observed_calls.first[:rv]).to eq [41, 42]
202+
expect(observed_calls.first[:duration]).to be_a(Float)
203+
204+
expect(observed_calls.first[:serialized_entry_args]).to eq(
205+
# TODO actual argument name not captured yet,
206+
# requires method call trace point.
207+
arg1: {type: 'Integer', value: '41'},
208+
kwarg: {type: 'Integer', value: '42'}
209+
)
210+
end
211+
end
212+
213+
include_examples 'invokes callback and captures parameters'
214+
215+
context 'when passed via a splat' do
216+
let(:target_call) do
217+
args = [41]
218+
kwargs = {kwarg: 42}
219+
expect(HookTestClass.new.hook_test_method_with_pos_and_kwarg(*args, **kwargs)).to eq [41, 42]
220+
end
116221

117-
expect(observed_calls.first[:serialized_entry_args]).to eq(arg1: {type: 'Integer', value: '2'})
222+
include_examples 'invokes callback and captures parameters'
118223
end
119224
end
120225
end

0 commit comments

Comments
 (0)