diff --git a/lib/datadog/di/code_tracker.rb b/lib/datadog/di/code_tracker.rb index 09e2776b71e..5ace92a17d2 100644 --- a/lib/datadog/di/code_tracker.rb +++ b/lib/datadog/di/code_tracker.rb @@ -78,10 +78,11 @@ def start registry_lock.synchronize do registry[path] = tp.instruction_sequence end - end - - DI.current_component&.probe_manager&.install_pending_line_probes(path) + # Also, pending line probes should only be installed for + # non-eval'd code. + DI.current_component&.probe_manager&.install_pending_line_probes(path) + end # Since this method normally is called from customer applications, # rescue any exceptions that might not be handled to not break said # customer applications. diff --git a/lib/datadog/di/probe.rb b/lib/datadog/di/probe.rb index 4f0ac2916b1..63bed7a7041 100644 --- a/lib/datadog/di/probe.rb +++ b/lib/datadog/di/probe.rb @@ -161,6 +161,9 @@ def location # If file is not an absolute path, the path matches if the file is its suffix, # at a path component boundary. def file_matches?(path) + if path.nil? + raise ArgumentError, "Cannot match against a nil path" + end unless file raise ArgumentError, "Probe does not have a file to match against" end diff --git a/lib/datadog/di/probe_manager.rb b/lib/datadog/di/probe_manager.rb index 256b3411f6c..31e640960d0 100644 --- a/lib/datadog/di/probe_manager.rb +++ b/lib/datadog/di/probe_manager.rb @@ -206,6 +206,9 @@ def remove_other_probes(probe_ids) # point, which is invoked for each required or loaded file # (and also for eval'd code, but those invocations are filtered out). def install_pending_line_probes(path) + if path.nil? + raise ArgumentError, "path must not be nil" + end @lock.synchronize do @pending_probes.values.each do |probe| if probe.line? diff --git a/lib/datadog/di/utils.rb b/lib/datadog/di/utils.rb index 16fb3e1d943..d9cc09bd689 100644 --- a/lib/datadog/di/utils.rb +++ b/lib/datadog/di/utils.rb @@ -12,6 +12,13 @@ module Utils # If suffix is not an absolute path, the path matches if its suffix is # the provided suffix, at a path component boundary. module_function def path_matches_suffix?(path, suffix) + if path.nil? + raise ArgumentError, "nil path passed" + end + if suffix.nil? + raise ArgumentError, "nil suffix passed" + end + if suffix.start_with?('/') path == suffix else diff --git a/spec/datadog/di/code_tracker_pending_load.rb b/spec/datadog/di/code_tracker_pending_load.rb new file mode 100644 index 00000000000..c01ade299a9 --- /dev/null +++ b/spec/datadog/di/code_tracker_pending_load.rb @@ -0,0 +1 @@ +# Nothing diff --git a/spec/datadog/di/code_tracker_pending_require.rb b/spec/datadog/di/code_tracker_pending_require.rb new file mode 100644 index 00000000000..c01ade299a9 --- /dev/null +++ b/spec/datadog/di/code_tracker_pending_require.rb @@ -0,0 +1 @@ +# Nothing diff --git a/spec/datadog/di/code_tracker_spec.rb b/spec/datadog/di/code_tracker_spec.rb index 3f4b921e7be..5b9bf0d7fde 100644 --- a/spec/datadog/di/code_tracker_spec.rb +++ b/spec/datadog/di/code_tracker_spec.rb @@ -4,10 +4,24 @@ RSpec.describe Datadog::DI::CodeTracker do di_test + before(:all) do + Datadog::DI.deactivate_tracking! + end + let(:tracker) do described_class.new end + shared_context 'when code tracker is running' do + before do + tracker.start + end + + after do + tracker.stop + end + end + describe "#start" do after do tracker.stop @@ -90,13 +104,7 @@ describe "#active?" do context "when started" do - before do - tracker.start - end - - after do - tracker.stop - end + include_context 'when code tracker is running' it "is true" do expect(tracker.active?).to be true @@ -115,6 +123,57 @@ end end + describe 'line probe installation' do + let(:component) do + instance_double(Datadog::DI::Component).tap do |component| + expect(component).to receive(:probe_manager).and_return(probe_manager) + end + end + + let(:probe_manager) do + instance_double(Datadog::DI::ProbeManager) + end + + context 'when started' do + include_context 'when code tracker is running' + + context 'when a file is required' do + it 'requests to install pending line probes' do + expect(Datadog::DI).to receive(:current_component).and_return(component) + expect(probe_manager).to receive(:install_pending_line_probes) do |path| + # Should be an absolute path + expect(path).to start_with('/') + expect(File.basename(path)).to eq('code_tracker_pending_require.rb') + end + require_relative 'code_tracker_pending_require' + end + end + + context 'when a file is loaded' do + it 'requests to install pending line probes' do + expect(Datadog::DI).to receive(:current_component).and_return(component) + expect(probe_manager).to receive(:install_pending_line_probes) do |path| + # Should be an absolute path + expect(path).to start_with('/') + expect(File.basename(path)).to eq('code_tracker_pending_load.rb') + end + load File.join(File.dirname(__FILE__), 'code_tracker_pending_load.rb') + end + end + + context "when Ruby code is eval'd" do + it 'requests to install pending line probes' do + # Matchers can be lazily loaded, force all code to be loaded here. + expect(4).to eq(4) + + expect(Datadog::DI).not_to receive(:current_component) + expect(probe_manager).not_to receive(:install_pending_line_probes) + expect(eval('2 + 2')).to eq(4) # rubocop:disable Style/EvalWithLocation + end + end + end + end + describe "#iseqs_for_path_suffix" do around do |example| tracker.start