Skip to content

Commit 4aae21e

Browse files
authored
Merge pull request #253 from DataDog/anmarchenko/knapsack_standalone_integration
[SDTEST-1129] Extract Knapsack instrumentation as standalone integration
2 parents acc48cb + b17763c commit 4aae21e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+677
-721
lines changed

Rakefile

-4
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ TEST_METADATA = {
7676
"knapsack_rspec" => {
7777
"knapsack_pro-7-rspec-3" => "✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby"
7878
},
79-
"knapsack_rspec_go" => {
80-
"knapsack_pro-7-rspec-3" => "✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby"
81-
},
8279
"selenium" => {
8380
"selenium-4-capybara-3" => "❌ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ❌ 3.4 / ✅ jruby"
8481
},
@@ -156,7 +153,6 @@ namespace :spec do
156153
ci_queue_minitest
157154
ci_queue_rspec
158155
knapsack_rspec
159-
knapsack_rspec_go
160156
selenium timecop
161157
].each do |contrib|
162158
desc "" # "Explicitly hiding from `rake -T`"

lib/datadog/ci.rb

+8-1
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,16 @@ def test_optimisation
406406
end
407407

408408
# Integrations
409+
410+
# Test frameworks (manual instrumentation)
409411
require_relative "ci/contrib/cucumber/integration"
410-
require_relative "ci/contrib/rspec/integration"
411412
require_relative "ci/contrib/minitest/integration"
413+
require_relative "ci/contrib/rspec/integration"
414+
415+
# Test runners (instrumented automatically when corresponding frameworks are instrumented)
416+
require_relative "ci/contrib/knapsack/integration"
417+
418+
# Additional test libraries (auto instrumented later on test session start)
412419
require_relative "ci/contrib/selenium/integration"
413420
require_relative "ci/contrib/simplecov/integration"
414421

lib/datadog/ci/configuration/settings.rb

+3-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require_relative "../contrib/instrumentation"
34
require_relative "../ext/settings"
45
require_relative "../utils/bundle"
56

@@ -8,8 +9,6 @@ module CI
89
module Configuration
910
# Adds CI behavior to ddtrace settings
1011
module Settings
11-
InvalidIntegrationError = Class.new(StandardError)
12-
1312
def self.extended(base)
1413
base = base.singleton_class unless base.is_a?(Class)
1514
add_settings!(base)
@@ -126,23 +125,11 @@ def self.add_settings!(base)
126125
define_method(:instrument) do |integration_name, options = {}, &block|
127126
return unless enabled
128127

129-
integration = fetch_integration(integration_name)
130-
integration.configure(options, &block)
131-
132-
return unless integration.enabled
133-
134-
patch_results = integration.patch
135-
next if patch_results == true
136-
137-
error_message = <<-ERROR
138-
Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]},
139-
Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}"
140-
ERROR
141-
Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})")
128+
Contrib::Instrumentation.instrument(integration_name, options, &block)
142129
end
143130

144131
define_method(:[]) do |integration_name|
145-
fetch_integration(integration_name).configuration
132+
Contrib::Instrumentation.fetch_integration(integration_name).configuration
146133
end
147134

148135
option :trace_flush
@@ -151,11 +138,6 @@ def self.add_settings!(base)
151138
o.type :hash
152139
o.default({})
153140
end
154-
155-
define_method(:fetch_integration) do |name|
156-
Datadog::CI::Contrib::Integration.registry[name] ||
157-
raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
158-
end
159141
end
160142
end
161143
end

lib/datadog/ci/contrib/contrib.rb

-31
This file was deleted.

lib/datadog/ci/contrib/cucumber/formatter.rb

+10-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative "../../ext/test"
44
require_relative "../../git/local_repository"
55
require_relative "../../utils/test_run"
6+
require_relative "../instrumentation"
67
require_relative "ext"
78

89
module Datadog
@@ -38,9 +39,9 @@ def on_test_run_started(event)
3839
test_visibility_component.start_test_session(
3940
tags: {
4041
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
41-
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s
42+
CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s
4243
},
43-
service: configuration[:service_name]
44+
service: datadog_configuration[:service_name]
4445
)
4546
test_visibility_component.start_test_module(Ext::FRAMEWORK)
4647
end
@@ -61,7 +62,7 @@ def on_test_case_started(event)
6162
# @type var tags: Hash[String, String]
6263
tags = {
6364
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
64-
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Cucumber::Integration.version.to_s,
65+
CI::Ext::Test::TAG_FRAMEWORK_VERSION => datadog_integration.version.to_s,
6566
CI::Ext::Test::TAG_SOURCE_FILE => Git::LocalRepository.relative_to_root(event.test_case.location.file),
6667
CI::Ext::Test::TAG_SOURCE_START => event.test_case.location.line.to_s
6768
}
@@ -81,7 +82,7 @@ def on_test_case_started(event)
8182
event.test_case.name,
8283
test_suite_name,
8384
tags: tags,
84-
service: configuration[:service_name]
85+
service: datadog_configuration[:service_name]
8586
)
8687
if event.test_case.match_tags?("@#{CI::Ext::Test::ITR_UNSKIPPABLE_OPTION}")
8788
test_span&.itr_unskippable!
@@ -199,7 +200,11 @@ def ok?(result, strict)
199200
end
200201
end
201202

202-
def configuration
203+
def datadog_integration
204+
CI::Contrib::Instrumentation.fetch_integration(:cucumber)
205+
end
206+
207+
def datadog_configuration
203208
Datadog.configuration.ci[:cucumber]
204209
end
205210

lib/datadog/ci/contrib/cucumber/integration.rb

+4-13
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,21 @@ module CI
99
module Contrib
1010
module Cucumber
1111
# Description of Cucumber integration
12-
class Integration
13-
include Datadog::CI::Contrib::Integration
14-
12+
class Integration < Contrib::Integration
1513
MINIMUM_VERSION = Gem::Version.new("3.0.0")
1614

17-
register_as :cucumber
18-
19-
def self.version
15+
def version
2016
Gem.loaded_specs["cucumber"]&.version
2117
end
2218

23-
def self.loaded?
19+
def loaded?
2420
!defined?(::Cucumber).nil? && !defined?(::Cucumber::Runtime).nil?
2521
end
2622

27-
def self.compatible?
23+
def compatible?
2824
super && version >= MINIMUM_VERSION
2925
end
3026

31-
# test environments should not auto instrument test libraries
32-
def auto_instrument?
33-
false
34-
end
35-
3627
def new_configuration
3728
Configuration::Settings.new
3829
end

lib/datadog/ci/contrib/cucumber/patcher.rb

-4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ module Patcher
1414

1515
module_function
1616

17-
def target_version
18-
Integration.version
19-
end
20-
2117
def patch
2218
::Cucumber::Runtime.include(Instrumentation)
2319
end
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module CI
5+
module Contrib
6+
module Instrumentation
7+
class InvalidIntegrationError < StandardError; end
8+
9+
@registry = {}
10+
11+
def self.registry
12+
@registry
13+
end
14+
15+
def self.register_integration(integration_class)
16+
@registry[integration_name(integration_class)] = integration_class.new
17+
end
18+
19+
# Manual instrumentation of a specific integration.
20+
#
21+
# This method is called when user has `c.ci.instrument :integration_name` in their code.
22+
def self.instrument(integration_name, options = {}, &block)
23+
integration = fetch_integration(integration_name)
24+
integration.configure(options, &block)
25+
26+
return unless integration.enabled
27+
28+
patch_results = integration.patch
29+
if patch_results[:ok]
30+
# try to patch dependant integrations (for example knapsack that depends on rspec)
31+
dependants = integration.dependants
32+
.map { |name| fetch_integration(name) }
33+
.filter { |integration| integration.patchable? }
34+
35+
Datadog.logger.debug("Found dependent integrations for #{integration_name}: #{dependants}")
36+
37+
dependants.each do |dependent_integration|
38+
dependent_integration.patch
39+
end
40+
else
41+
error_message = <<-ERROR
42+
Available?: #{patch_results[:available]}, Loaded?: #{patch_results[:loaded]},
43+
Compatible?: #{patch_results[:compatible]}, Patchable?: #{patch_results[:patchable]}"
44+
ERROR
45+
Datadog.logger.warn("Unable to patch #{integration_name} (#{error_message})")
46+
end
47+
end
48+
49+
# This method instruments all additional test libraries (ex: selenium-webdriver) that need to be instrumented
50+
# later in the test suite run.
51+
#
52+
# It is intended to be called when test session starts to add additional capabilities to test visibility.
53+
#
54+
# This method does not automatically instrument test frameworks (ex: RSpec, Cucumber, etc), it requires
55+
# test framework to be already instrumented.
56+
def self.instrument_on_session_start
57+
Datadog.logger.debug("Instrumenting all late instrumented integrations...")
58+
59+
@registry.each do |name, integration|
60+
next unless integration.late_instrument?
61+
62+
Datadog.logger.debug "#{name} is allowed to be late instrumented"
63+
64+
patch_results = integration.patch
65+
if patch_results[:ok]
66+
Datadog.logger.debug("#{name} is patched")
67+
else
68+
Datadog.logger.debug("#{name} is not patched (#{patch_results})")
69+
end
70+
end
71+
end
72+
73+
def self.fetch_integration(name)
74+
@registry[name] ||
75+
raise(InvalidIntegrationError, "'#{name}' is not a valid integration.")
76+
end
77+
78+
# take the parent module name and downcase it
79+
# for example for Datadog::CI::Contrib::RSpec::Integration it will be :rspec
80+
def self.integration_name(subclass)
81+
result = subclass.name&.split("::")&.[](-2)&.downcase&.to_sym
82+
raise "Integration name could not be derived for #{subclass}" if result.nil?
83+
result
84+
end
85+
end
86+
end
87+
end
88+
end

0 commit comments

Comments
 (0)